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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With