%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_lo
以 1b
作为参数。如何计算其结果并得到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,也就是addi指令的偏移量,linker会找到R_RISCV_PCREL_LO12_I重定位。它将使用值 .L1^B1 从重定位 (R_RISCV_PCREL_HI20) 中获取与该值的偏移量相对应的 pc 和符号。然后它会取 .L1^B1 的 pc 和找到的符号的地址 mtvec.[ 之间的相对地址的 LSB 12 位。 =14=]
在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_lo
以 1b
作为参数。如何计算其结果并得到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,也就是addi指令的偏移量,linker会找到R_RISCV_PCREL_LO12_I重定位。它将使用值 .L1^B1 从重定位 (R_RISCV_PCREL_HI20) 中获取与该值的偏移量相对应的 pc 和符号。然后它会取 .L1^B1 的 pc 和找到的符号的地址 mtvec.[ 之间的相对地址的 LSB 12 位。 =14=]