反汇编目标文件时,显示的地址信息auipc
/jalr
有点任意,因为无论如何它都会被链接器重新定位。
您可以看到,在转储重定位信息时(添加-r
到您的 objdump 调用):
0000000000000000 <id>:
0: 8082 ret
0000000000000002 <add_one>:
2: 1141 addi sp,sp,-16
4: e406 sd ra,8(sp)
6: 00000097 auipc ra,0x0
6: R_RISCV_CALL id
6: R_RISCV_RELAX *ABS*
a: 000080e7 jalr ra # 6 <add_one+0x4>
e: 60a2 ld ra,8(sp)
10: 0505 addi a0,a0,1
12: 0141 addi sp,sp,16
14: 8082 ret
这些重定位条目告诉链接器以轻松的方式重定位跳转指令(RISC-V 工具链的默认设置)。这意味着它可以替换auipc
+jalr
仅与一个配对jal
指令 iff 到目标地址的距离足够短。这种替换是有利的,因为它节省了指令,即生成的程序更短。显然,它使重定位过程变得有点复杂,因为后续跳转指令的偏移量需要相应调整。
(这可以通过禁用-mno-relax
海湾合作委员会旗帜。)
为什么汇编器不能直接发出finalauipc
/jalr
/jal
不需要重新定位翻译单元本地符号的指令?毕竟,这些跳跃是与电脑相关的。
一般来说,它不能,因为只有一个翻译单元的本地视图 1) 到外部符号的宽松重定位可能会更改所有后续到内部符号的偏移量,2) 链接器甚至可能应用一些高级规则,例如其中内部符号被外部符号覆盖,因此它确实必须在链接器中重新定位。或者,另一个例子,链接器删除了一个符号。
如果您想查看重定位的地址/偏移量,您必须反汇编链接的二进制文件,例如:
000000000001015c <id>:
1015c: 8082 ret
000000000001015e <add_one>:
1015e: 1141 addi sp,sp,-16
10160: e406 sd ra,8(sp)
10162: ffbff0ef jal ra,1015c <id>
10166: 60a2 ld ra,8(sp)
10168: 0505 addi a0,a0,1
1016a: 0141 addi sp,sp,16
1016c: 8082 ret
As expected, the linker relaxes auipc
+jalr
to just jal
. Unfortunately, objdump doesn't display the raw jal
offset - 1015c
is the absolute address after adding the offset to 10162
.1
您可以自行解码第二列的二进制指令来验证:
0xffbff0ef
= 0b11111111101111111111000011101111 | split into the offset parts
=> 1 1111111101 1 11111111 | i.e. off[20], off[10:1], off[11], off[19:12]
| merge them into off[20:1]
=> 0b11111111111111111101 | left-shift by 1
=> 0b111111111111111111010 | sign-extend
=> 0b11111111111111111111111111111010
= -6
=> 0x10162 - 6
= 0x1015c
这与 objdump 输出匹配。
1 That means GNU binutils objdump doesn't display the raw jal
offset. In contrast, llvm-objdump
(LLVM 9 introduces official RISC-V support) does display the raw offset:
000000000001015e add_one:
1015e: 41 11 addi sp, sp, -16
10160: 06 e4 sd ra, 8(sp)
10162: ef f0 bf ff jal -6
10166: a2 60 ld ra, 8(sp)
10168: 05 05 addi a0, a0, 1
1016a: 41 01 addi sp, sp, 16
1016c: 82 80 ret
然而,与 GNU binutils objdump 相比,llvm-objdump
不包括结果绝对地址作为注释。它也没有注释相应的符号。因此,一般来说,GNU binutils objdump 输出可以说更有用。