%pcrel_hi 和 %pcrel_lo 实际上做了什么?

What do %pcrel_hi and %pcrel_lo actually do?

riscv-asm-manual控制和状态寄存器部分,有一个例子:

.equ RTC_BASE,      0x40000000
.equ TIMER_BASE,    0x40004000

# setup machine trap vector
1:      auipc   t0, %pcrel_hi(mtvec)        # load mtvec(hi)
        addi    t0, t0, %pcrel_lo(1b)       # load mtvec(lo)
        csrrw   zero, mtvec, t0

...
# break on interrupt
mtvec:
        csrrc  t0, mcause, zero
        bgez t0, fail       # interrupt causes are less than zero
        slli t0, t0, 1      # shift off high bit
...

我猜%pcrel_hi(mtvec)计算出mtvec和当前PC之间的hi-distance(这里是1符号的地址)。假设1符号地址为0x80010000,mtvec符号地址为0x80020040。那么%pcrel_hi(mtvec) = (0x80020040 - 0x80010000) >> 12 = 0x00010,所以auipc的结果就是0x00010 << 12 + PC = 0x00010000 + 0x80010000 = 0x80020000.

但是 %pcrel_lo1b 作为参数。如何计算其结果并得到mtvec的最终地址? addi t0, t0, %pcrel_lo(mtvect) 似乎是直观的代码,但实际上不是。为什么?

正如 peter Cordes 在他的评论、您的 link 和 this one 中所指出的那样。对于 addi,使用标签而不是直接使用 symbol,因为 addi 必须包含 pc 和 symbol 之间的相对地址的低 12 位有效位。然而,PC 必须与用于 pcrel_high 的相同,因为从 binutils 的角度来看,不能保证这两条指令将相互跟随(这可能会导致不同的计算)。因此,为提供合适的电脑而选择的解决方案是使用标签。

现在对于你例子中的1b,它不是一个计算结果。所有计算都在 linker 级别完成,assembler 只负责生成必要的重定位信息。 1b 表示 backward 标签 1。数字标签用于本地引用。对本地标签的引用后缀为 'f' 用于前向引用或 'b' 用于后向引用(它也存在于您的 link 中)。

如果你 assemble 这个程序集文件,你会看到标签将根据你的 assembler 版本更改为类似 .L1XXX 的内容,然后如果你执行 riscv-objdump -r 你会看到你有一个 R_RISCV_PCREL_LO12_I 这个标签在对应于 addi 的偏移量上。

基本上你会有这样的东西:

OFFSET           TYPE              VALUE 
0000000000000000 R_RISCV_PCREL_HI20  mtvec
0000000000000000 R_RISCV_RELAX     *ABS*
0000000000000004 R_RISCV_PCREL_LO12_I  .L1^B

在此示例中,偏移量 0 是 .L1^B1(已转换的标签 1)的偏移量。因此 linker 将使用此重定位来计算将用于 auipc 的值。

然后对于偏移量4,也就是ad​​di指令的偏移量,linker会找到R_RISCV_PCREL_LO12_I重定位。它将使用值 .L1^B1 从重定位 (R_RISCV_PCREL_HI20) 中获取与该值的偏移量相对应的 pc 和符号。然后它会取 .L1^B1 的 pc 和找到的符号的地址 mtvec.[ 之间的相对地址的 LSB 12 位。 =14=]