assembly - 理解用于函数调用的auipc+jalr序列

标签 assembly riscv

我正在尝试阅读 RISC-Vgcc 生成的程序集我发现 gcc创建 auipc 的序列+ jalr对于某些函数调用,我不明白它是如何工作的。这是一个简单的例子。考虑以下 C源文件:

unsigned long id(unsigned long x) {
    return x;
}

unsigned long add_one(unsigned long x) {
    return id(x)+1;
}

我用 gcc -O2 -fno-inline -c test.c 编译它我得到以下汇编代码:
$ objdump -d test.o

test.o:     file format elf64-littleriscv


Disassembly of section .text:

0000000000000000 <id>:
   0:   00008067            ret

0000000000000004 <add_one>:
   4:   ff010113            addi    sp,sp,-16
   8:   00113423            sd      ra,8(sp)
   c:   00000317            auipc   t1,0x0
  10:   000300e7            jalr    t1
  14:   00813083            ld      ra,8(sp)
  18:   00150513            addi    a0,a0,1
  1c:   01010113            addi    sp,sp,16
  20:   00008067            ret

让我困惑的是偏移处的两条线 0x0c0x10 ,这是函数 id 的地方应该被调用。根据spec , auipc t1,0x0应该写 PC + 0x0<<12 (等于 PC )到 t1然后 jalr t1 (扩展为 jalr ra,t1,0 )跳转到存储在 t1 中的地址并将返回地址存储到 ra .所以我们最终跳转到 auipc线(偏移 0x0c),而不是 id 的入口点.这里发生了什么?

最佳答案

反汇编目标文件时,显示地址信息在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指令如果到目标地址的距离足够短。这种替换是有利的,因为它节省了指令,即生成的程序更短。显然,它使重定位过程有点复杂,因为需要相应调整后续跳转指令的偏移量。

(这可以通过 -mno-relax GCC 标志禁用。)

为什么汇编器不能直接发出final auipc/jalr/jal不需要重新定位的翻译单元本地符号的说明?毕竟,这些跳跃是与 pc 相关的。

一般来说,它不能,因为仅使用一个翻译单元的本地 View 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

正如预期的那样,链接器放松 auipc + jalr只是 jal .不幸的是,objdump 不显示原始 jal偏移量 - 1015c10162加上偏移量后的绝对地址.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 这意味着 GNU binutils objdump 不显示原始 jal抵消。相比之下,llvm-objdump (LLVM 9 引入了官方的 RISC-V 支持)确实显示了原始偏移量:
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 输出可以说更有用。

关于assembly - 理解用于函数调用的auipc+jalr序列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43956612/

相关文章:

c++ - num++ 可以是 'int num' 的原子吗?

汇编语言 - 标志设置让我头疼

assembly - 如何在汇编中打印笑脸字符?

string - 从汇编中的读取输入中删除尾随换行字符

operating-system - 为什么xv6-riscv中没有GDT?

assembly - 为什么从子程序返回时使用 JALR 而不是 JAL

debugging - 使用 Buildroot 生成 RISC-V Linux GDB

riscv - RISC-V RV32I 软浮点库调用 __muldf3 中的 MUL 和 MULHU 指令

assembly - 汇编 x86 中整数之间的除法

riscv - 非 Zynq FPGA 上的火箭芯片