RISC-V64裸机设置超32位RAM起始地址报错求解决方案
问题描述
我正在使用链接脚本编译RISC-V64裸机程序,需要将RAM起始地址设置为超过32位的值,但修改链接脚本中的地址后出现以下错误:
(.text._start+0x0): relocation truncated to fit: R_RISCV_PCREL_HI20 against symbol
__global_pointer$' defined in *ABS* section in build/bm_uart_test.qemu.elf /tmp/ccU40vjX.o: in function.L0 ':
uart.c:(.text.uart_init+0x14): relocation truncated to fit: R_RISCV_PCREL_HI20 againstuart_base' /tmp/ccU40vjX.o: in functionuart_putc':
uart.c:(.text.uart_putc+0xa): relocation truncated to fit: R_RISCV_PCREL_HI20 against `uart_base'
collect2: error: ld returned 1 exit status
当前使用的编译选项:
AOPS = -march=rv64gc -mabi=lp64d -mcmodel=medany COPS = -Wall -O2 -static -nostdlib -nostartfiles -ffreestanding -mstrict-align CFLAGS += $(AOPS) $(COPS) -I include/ LOPTS = -lgcc -Wl,--relax,--orphan-handling=warn,--no-dynamic-linker,--gc-sections,--print-gc-sections,--strip-discarded LOPTS_QEMU = $(LOPTS) -T bmmap.qemu.ld CPOPS = -O binary --gap-fil 0
我目前的理解是medany内存模型要求所有全局符号必须位于±2GiB范围内,由于我的地址超过32位,链接器无法生成可达地址导致报错。请问我的理解是否正确?有什么解决方法?
解答
你的理解完全正确。RISC-V的medany内存模型确实限制了PC相对寻址的符号必须位于当前指令的±2GiB范围内,而你设置的RAM地址超出了这个区间,导致R_RISCV_PCREL_HI20重定位失败——因为HI20+LO12的PC相对寻址组合只能覆盖±2GiB的地址空间。
具体解决方法:
修改全局符号的访问方式
对于uart_base这类外设或大内存地址,避免直接用PC相对寻址访问,改用绝对寻址:// 原写法(触发PC相对寻址) *(volatile uint32_t *)(uart_base + OFFSET) = val; // 修改后(强制生成绝对寻址指令) uintptr_t uart_addr = uart_base; *(volatile uint32_t *)(uart_addr + OFFSET) = val;也可以直接用汇编指令加载绝对地址:
lui t0, %hi(uart_base) addi t0, t0, %lo(uart_base)处理
__global_pointer$全局指针问题
这个指针用于RISC-V的小数据区域访问,默认依赖PC相对寻址。可以通过添加编译选项-mno-save-restore -mno-small-data禁用小数据优化,让编译器直接生成绝对寻址访问全局变量的指令,不再依赖该指针。调整链接脚本布局
如果部分代码/数据可以放在±2GiB范围内,就将它们安排在低地址段,保留PC相对寻址的便利性;超出32位的大内存区域则通过绝对寻址访问。如果整个程序都要放在超32位地址,必须确保所有全局符号的访问都使用绝对寻址。确认
--relax选项的作用
你已经在链接选项中添加了--relax,它能让链接器优化部分重定位,但无法突破PC相对寻址的±2GiB限制,仍需配合代码修改才能彻底解决问题。
内容的提问来源于stack exchange,提问作者Mimis

