riscv-relocation

起因是在编译RISCV代码时出现R_RISCV_PCREL_HI20错误,从而查找相关资料总结得到本篇文章信息。

R_RISCV_PCREL_HI20

我们知道,c程序的编译过程分为编译和链接两步。在编译时,并不知道程序会被放到什么地址,只有等到链接时加载了链接脚本,才知道每个程序段的地址。如果有一条auipc指令想拿到一个symbol的地址,而symbol地址距离pc太远,超过了20bit范围,那这条指令就无法拿到symbol的地址了。所以在链接的时候,就只会报R_RISCV_PCREL_HI20错误。

那解决办法有两种:

第一种是将两个距离太远的地址放到20bit之内,这种修改链接脚本就好;

第二种是把地址的值放到附近,然后用load把地址拿回来,当然这种只能是在手写汇编的时候才能使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
.macro long_load r, s
j 101f
.balign 8
102: .dword \s
101:
la \r, 102b
#if __riscv_xlen == 32
lw \r, 0(\r)
#else
ld \r, 0(\r)
#endif
.endm

.section .text.metal.init.enter
.global _enter
_enter:
.cfi_startproc

/* Inform the debugger that there is nowhere to backtrace past _enter. */
.cfi_undefined ra

/* The absolute first thing that must happen is configuring the global
* pointer register, which must be done with relaxation disabled because
* it's not valid to obtain the address of any symbol without GP
* configured. The C environment might go ahead and do this again, but
* that's safe as it's a fixed register. */
.option push
.option norelax
long_load gp, __global_pointer$
.option pop

toolchain issue

另外,碰到一个toolchain的问题。比如有一段很长的switch语句,大概长这个样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
switch(region){
case 0:
__asm__("csrr %[addr], pmpaddr0"
: [addr] "=r" (*address) ::);
break;
case 1:
__asm__("csrr %[addr], pmpaddr1"
: [addr] "=r" (*address) ::);
break;
case 2:
__asm__("csrr %[addr], pmpaddr2"
: [addr] "=r" (*address) ::);
break;
......
case 15:
__asm__("csrr %[addr], pmpaddr15"
: [addr] "=r" (*address) ::);
break;
}

因为这段代码比较累赘,编译器会对他进行优化。如果不优化,可能会变成16个判断。优化之后会变成下面这个样子,大体思想是,把16个csrr指令列在下面的表里,然后根据region来计算他们的偏移,偏移的offset会存在一张rodata段的表里面,然后用jr a5跳到相应地址去,这样就没有多个分支指令,只有计算和绝对跳转指令了。

但问题就是,这里由于rodata段位于2000_0000区域,而text位于8_0000_0000区域。本来是希望跳到8_0000_2ff8去的,然而jr a5这里的a5得到的值却是0_0000_2ff8,前面的8丢了。而且编译器居然没有报错,让这个问题流到了程序运行阶段,这种问题就很难查了。

从编译的结果来看,这个本来应该是一条根据gp拿地址的指令的,可能是因为rodata段离gp太远了,根据gp拿不到,链接器就给换成了lui指令。问题就出在这个指令这里,编译器的初衷是根据gp来算偏移拿表里的offset,然而链接器把它改成了lui根据0地址计算offset,导致编译器和链接器想法冲突,导致计算的值错误。这里其实链接器应该直接报个错误,而不是主动该成lui指令,让问题流向了后级。

最后还是把rodata段改到了8_0000_0000区域,从而这里就变成了一条正常从gp计算地址的指令,就能够拿到表里的偏移了。

参考文章

https://www.sifive.com/blog/all-aboard-part-3-linker-relaxation-in-riscv-toolchain

https://www.sifive.com/blog/all-aboard-part-4-risc-v-code-models

https://twilco.github.io/riscv-from-scratch/2019/04/27/riscv-from-scratch-2.html

https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/