Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I see conditional jumps to relative offset zero (a.k.a. jumps to the next instruction) in disassembly output?

Context: I know the -- or at least, an -- answer to this question; I'm posting it here because a few hours ago, when I didn't, I couldn't turn anything up by searching for these keywords.


Sometimes when I view disassembly output I will see a conditional jump to the next line, which does not make sense -- either you go there because the check failed and you didn't jump, or you go there because the check passed and you did jump there.

For instance, if I compile the following C++ snippet

#include <stdexcept>

bool f(int x, int y, int z)
{
  switch(x)
  {
    case 0: return z < y;
    case 1: return y < z;
    default: throw std::runtime_error("bad x");
  }
}

down to an .o file and then use objdump -C -d to disassemble it, a portion of the disassembly looks like this:

0000000000000000 <f(int, int, int)>:
   0:   f3 0f 1e fa             endbr64
   4:   39 d6                   cmp    %edx,%esi
   6:   0f 9f c0                setg   %al
   9:   85 ff                   test   %edi,%edi
   b:   75 03                   jne    10 <f(int, int, int)+0x10>
   d:   c3                      ret
   e:   66 90                   xchg   %ax,%ax
  10:   83 ff 01                cmp    $0x1,%edi
  13:   0f 85 00 00 00 00       jne    19 <f(int, int, int)+0x19>
  19:   39 d6                   cmp    %edx,%esi
  1b:   0f 9c c0                setl   %al
  1e:   c3                      ret

The instruction 0f 85 00 00 00 00, a conditional jump to relative offset 0, is shown in the disassembly as jne 19. This seems meaningless as it just appears to branch to the next instruction regardless of the condition.

What is really going on here?

like image 557
Daniel McLaury Avatar asked Sep 21 '25 04:09

Daniel McLaury


1 Answers

If we look at more of the disassembly we notice something;

Disassembly of section .text:

0000000000000000 <f(int, int, int)>:
   0:   f3 0f 1e fa             endbr64
   4:   39 d6                   cmp    %edx,%esi
   6:   0f 9f c0                setg   %al
   9:   85 ff                   test   %edi,%edi
   b:   75 03                   jne    10 <f(int, int, int)+0x10>
   d:   c3                      ret
   e:   66 90                   xchg   %ax,%ax
  10:   83 ff 01                cmp    $0x1,%edi
  13:   0f 85 00 00 00 00       jne    19 <f(int, int, int)+0x19>
  19:   39 d6                   cmp    %edx,%esi
  1b:   0f 9c c0                setl   %al
  1e:   c3                      ret

Disassembly of section .text.unlikely:

0000000000000000 <f(int, int, int) [clone .cold]>:
   0:   55                      push   %rbp
   1:   bf 10 00 00 00          mov    $0x10,%edi
   6:   53                      push   %rbx
   7:   50                      push   %rax
   8:   e8 00 00 00 00          call   d <f(int, int, int) [clone .cold]+0xd>
   d:   48 8d 35 00 00 00 00    lea    0x0(%rip),%rsi        # 14 <f(int, int, int) [clone .cold]+0x14>

[snip]

The code that we should be jumping to is in a separate section, .text.unlikely.

The 00 00 00 00 relative address for the jump is a placeholder that will be filled in by the linker to point at wherever it ends up placing the code from .text.unlikely. There is some metadata in the .o file that objdump is not displaying that tells it to do this.

If instead of compiling to an .o file, you compile all the way to an executable before disassembling, you won't have this issue, as the linker will have already done its job and filled in the address.

You can also bypass this issue by simply having g++ stop at the level of assembly code without producing the .o file at all using the -S flag. However, this has a downside versus using objdump, namely that objdump can demangle names whereas g++ -S apparently cannot.

Note: For C and C++ programmers using Compiler Explorer, you can see the differences by selecting either "compile to binary object," "link to binary," or neither.


Update (since comments aren't meant to be permanent or whatever the weird rule is):

In the comments, Peter Cordes suggests passing -r (a.k.a --reloc) to objdump to add information about relocatable code. Trying this on the example above changes the line

  13:   0f 85 00 00 00 00       jne    19 <f(int, int, int)+0x19>

above to

  13:   0f 85 00 00 00 00       jne    19 <f(int, int, int)+0x19>
        15: R_X86_64_PC32       .text.unlikely-0x4

So it still shows a jump to the wrong location, but it adds in some stuff at the end that Peter explains how to read in the comments:

the relocation entry says to fill in these 4 bytes with the distance between this address and .text.unlikely-0x4, where .text.unlikely is the start of that section

like image 165
Daniel McLaury Avatar answered Sep 23 '25 15:09

Daniel McLaury