解决RISC-V链接时 linker fail, relocation truncated to fit: R_RISCV_HI20

仿真平台中的程序放在地址0中运行,改为DDR地址0x80000000。结果链接中 报错relocation truncated to fit : R_RISCV_PCREL_HI20 against symbol `j’ defined in .bss section 。
折腾了很久,没搞好,终于搞好了,记录一下

基准测试/common/test.ld链接器脚本将代码放在0x80000000处。这就是芯片放置DDR DRAM的地方。在0x80000000加载的代码只有在编译 -mcmodel=medany 时才能工作。如果指定了-mcmodel=medlow,这将不起作用。

对于使用-mcmodel=medany的64位架构,代码可以链接到任何基址,但链接的全局符号遵循类似的+/- 2GiB范围限制。取决于代码在某些情况下,通常不使用负范围。

(1)编译条件:

CFLAGS + = -I $ (DIR_INC)
CFLAGS + = -D_RISCV64
CFLAGS + = --specs=nano.specs -MD

解决:
CFLAGS + = -I$(DIR_INC)
CFLAGS + = -D_RISCV64
CFLAGS + = --specs=nano.specs -MD -mcmodel=medany
增加 -mcmodel=medany之后还是报错,但是报的错都是链接的工具链下的文件,于是乎重新编译工具链

此外,如果您正在链接任何预编译的库,如C库,那么工具链必须已经配置好了-cmodel=medany,否则C库将是medlow代码,无法工作。
(2)将riscv_gnu_toolchain下面的Makefile里-mcmodel=medlow改为 -mcmodel=medany,重新make工具链,使用这个工具链再重新进行链接就通过了。

工具链按照mcmodel=medany来进行编译是之前没尝试的,所以问题一直没有解决。

<think>我们正在处理一个RISC-V链接器脚本(.ld文件)修改后出现的重定位错误。用户将起始地址从0x80000000改为0xD0000000后,出现了`R_RISCV_PCREL_HI20outofrange`错误。这个错误通常是由于程序计数器相对寻址(PC-relativeaddressing)的偏移量超出了指令能够表示的范围。在RISC-V中,`R_RISCV_PCREL_HI20`重定位类型用于`auipc`指令,该指令将20位的高位部分加载到寄存器中,然后通过一个低12位的偏移(由`R_RISCV_PCREL_LO12_I`或`R_RISCV_PCREL_LO12_S`提供)形成一个完整的32位地址。这种寻址方式允许代码与数据的位置无关(PIC)。然而,`auipc`指令形成的地址是当前指令地址加上一个20位有符号立即数(左移12位)后的结果。因此,该立即数表示的偏移范围是[-2^19,2^19-1](以2的12倍为单位),即实际地址偏移范围是[-2^19*4096,(2^19-1)*4096]字节,也就是[-2^31,2^31-4096]字节(约±2GB)。当用户将起始地址从0x80000000改为0xD0000000,新的起始地址变大了(0xD0000000-0x80000000=0x50000000=1.25GB),但是更重要的是,代码段和某些需要被PC相对访问的符号(比如全局变量)之间的距离可能超过了2GB的范围,导致重定位计算出的偏移量超出了20位有符号立即数的表示范围。解决方案通常包括:1.检查链接器脚本,确保内存布局合理,将频繁访问的数据段放在靠近代码段的位置(在同一个2GB区域内)。2.如果确实需要将代码段放在0xD0000000,那么确保所有通过PC相对方式访问的符号(比如全局变量)也位于0xD0000000开始的2GB区域内(即0xD0000000到0xF0000000之间)。3.考虑使用另一种寻址方式,比如使用`gp`(全局指针)寄存器来访问全局数据。RISC-V通常使用`gp`寄存器来指向一个64KB范围内的全局数据区域,这样可以通过`lui`和`addi`等方式访问,而不使用PC相对寻址。具体步骤:a.检查链接器脚本,确保数据段(如.data,.rodata,.bss等)被放置在代码段(.text)附近,最好在同一个2GB区域。b.如果数据段必须放在远离代码段的地方,考虑使用`-mcmodel=medany`编译选项(如果之前使用的是`-mcmodel=medlow`,它要求所有符号在2GB范围内)。`medany`允许符号在任意4GB范围内,但是注意,这改变了代码模型,可能要求使用不同的寻址方式,并且可能不能解决所有问题。c.确保使用了`gp`寄存器并正确设置。在链接器脚本中定义`__global_pointer$`符号,指向全局数据区域(通常是.sdata或.data中的一个地址)。然后,在程序启动(如crt0.S)将`gp`寄存器设置为该值。例如,在链接器脚本中:```PROVIDE(__global_pointer$=.+0x800);```注意:`__global_pointer$`应该指向一个位于全局数据区域中间的地址,这样可以通过±2KB的偏移访问数据(因为12位有符号偏移范围是[-2048,2047])。另外,检查是否有一些大的数组或结构体被放在离代码段很远的地方。可以通过链接器脚本调整这些符号的位置。如果问题仍然存在,可能需要重新组织内存布局,或者将一些数据移到离代码更近的地方。由于用户修改了起始地址,可能整个内存映射都需要重新调整,以确保代码和其访问的数据在2GB范围内。下面我们将生成相关问题。</think>### RISC-V重定位错误分析:R_RISCV_PCREL_HI20超出范围 当您将链接脚本(.ld)中的起始地址从`0x80000000`改为`0xD0000000`出现`R_RISCV_PCREL_HI20`重定位错误,这是由RISC-V的寻址限制导致的。以下是根本原因和解决方案: #### 错误原因 1. **PCREL寻址范围限制** `R_RISCV_PCREL_HI20`是用于PC相对寻址的重定位类型(如`auipc`指令),其有效寻址范围为**±2GB**($ \pm 2^{31} $字节)。 地址变更计算: $ \Delta = \mathtt{0xD0000000} - \mathtt{0x80000000} = \mathtt{0x50000000} = 1.25 \text{GB} $ 新地址`0xD0000000`与旧地址`0x80000000`的距离已超过±2GB范围,导致PC相对偏移量溢出[^1]。 2. **代码/数据分离** 若代码段(.text)在`0xD0000000`而数据段(.data/.bss)仍靠近`0x80000000`,两者距离超过2GB会触发此错误。 #### 解决方案 1. **调整内存布局**(推荐) 在链接脚本中确保所有PC相对访问的段(代码/数据)位于**同一2GB区域**内: ```ld MEMORY { RAM (rwx) : ORIGIN = 0xD0000000, LENGTH = 512M } SECTIONS { .text : { *(.text*) } > RAM .data : { *(.data*) } > RAM /* 确保数据段在同一区域 */ .bss : { *(.bss*) } > RAM } ``` 2. **使用GOT全局偏移表** 通过`-fPIC`编译选项启用位置无关代码,强制通过GOT访问远端数据: ```bash riscv64-unknown-elf-gcc -fPIC -mcmodel=medany -o program.o source.c ``` 3. **扩展寻址模型** 使用`-mcmodel=medany`编译选项放宽寻址范围至±2GB(需工具链支持): ```bash riscv64-unknown-elf-gcc -mcmodel=medany -o program.o source.c ``` 4. **优化符号访问** 对远端数据使用绝对寻址而非PC相对寻址(需修改汇编/C代码): ```c // 将 extern int var; int x = var; // 改为 volatile int *abs_var = (int*)0x80000000; int x = *abs_var; ``` #### 验证步骤 1. 检查链接脚本中各段基地址差值是否<$ |2^{31}| $ $ |\mathtt{0xD0000000} - \mathtt{0x80000000}| = \mathtt{0x50000000} > \mathtt{0x7FFFFFFF} $ 2. 使用`readelf -r`查看重定位条目: ```bash riscv64-unknown-elf-readelf -r program.elf ``` 3. 通过`objdump`反汇编确认`auipc`指令的偏移量 > 关键原则:所有通过`auipc`/`lui`访问的符号必须与PC值保持±2GB距离[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值