起因是在编译RISCV代码时出现R_RISCV_PCREL_HI20
错误,从而查找相关资料总结得到本篇文章信息。
R_RISCV_PCREL_HI20
我们知道,c程序的编译过程分为编译和链接两步。在编译时,并不知道程序会被放到什么地址,只有等到链接时加载了链接脚本,才知道每个程序段的地址。如果有一条auipc
指令想拿到一个symbol的地址,而symbol地址距离pc太远,超过了20bit范围,那这条指令就无法拿到symbol的地址了。所以在链接的时候,就只会报R_RISCV_PCREL_HI20
错误。
那解决办法有两种:
第一种是将两个距离太远的地址放到20bit之内,这种修改链接脚本就好;
第二种是把地址的值放到附近,然后用load把地址拿回来,当然这种只能是在手写汇编的时候才能使用。
1 | .macro long_load r, s |
toolchain issue
另外,碰到一个toolchain的问题。比如有一段很长的switch语句,大概长这个样子
1 | switch(region){ |
因为这段代码比较累赘,编译器会对他进行优化。如果不优化,可能会变成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