RISC-V64下直接编译.c与预编译目标文件链接的重定位错误问题
重定位错误R_RISCV_HI20的原因与解决方法
问题场景
为RISC-V64架构编写的Hello World程序,直接编译.c文件可正常构建并在QEMU运行,但分步编译目标文件再链接时,出现R_RISCV_HI20重定位截断错误。
错误信息
main.o: in function `main': main.c:(.text+0x8): relocation truncated to fit: R_RISCV_HI20 against `hello' test.o: in function `test': test.c:(.text+0x8): relocation truncated to fit: R_RISCV_HI20 against `hello' collect2.exe: error: ld returned 1 exit status
成功编译命令
gcc -march=rv64gc -mabi=lp64d -nostartfiles --machine-cmodel=medany -T linker.ld crt0.o main.c putchar.a test.c -o main.elf
出错的分步编译命令
第一步:编译目标文件
gcc -march=rv64gc -mabi=lp64d -nostartfiles --machine-cmodel=medany -T linker.ld -c main.c test.c
第二步:链接
gcc -march=rv64gc -mabi=lp64d -nostartfiles --machine-cmodel=medany -T linker.ld crt0.o main.o putchar.a test.o -o main.elf
相关代码文件
main.c
#include "putchar.h" // #include "test.h" static const char *hello = "Hello, World!\n"; void main(){ // test(); putchars(hello); putchars("Hello, World!\n"); while(1); }
test.c
#include "putchar.h" static const char *hello = "Hello, World!\n"; void test(){ putchars(hello); }
putchar.c
volatile char * const UART = (char*)0x10000000; void putchars(char *str){ for(char *i = str; *i != 0; i++) *UART = *i; }
linker.ld
/* Template linker definition script for riscv64 virtIO machine */ OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", "elf64-littleriscv") OUTPUT_ARCH(riscv) /* Define the entry point */ ENTRY(_start) /* Define the memory regions */ MEMORY { /* The flash memory region */ FLASH (rwx) : ORIGIN = 0x20000000, LENGTH = 0x2000000 /* The RAM memory region */ RAM (rw) : ORIGIN = 0x80000000, LENGTH = 0x1000000 } /* Define the output sections */ SECTIONS { /* The text section contains the executable code */ .text : { /* Align to page boundary */ . = ALIGN(0x1000); /* Record the start address of this section */ _stext = .; /* Include all input files with .text sections */ *(.text) /* Record the end address of this section */ _etext = .; /* Align to page boundary */ . = ALIGN(0x1000); } > FLASH /* The rodata section contains read-only data */ .rodata : { /* Align to page boundary */ . = ALIGN(0x1000); /* Record the start address of this section */ _srodata = .; /* Include all input files with .rodata sections */ *(.rodata) *(.srodata) /* Record the end address of this section */ _erodata = .; /* Align to page boundary */ . = ALIGN(0x1000); } > FLASH /* The data section contains initialized data */ .data : { /* Align to page boundary */ . = ALIGN(0x1000); /* Record the start address of this section */ _sdata = .; /* Include all input files with .data sections */ *(.data) *(.sdata) /* Record the end address of this section */ _edata = .; /* Align to page boundary */ . = ALIGN(0x1000); } > RAM AT > FLASH .fill : { FILL(0xFFFF); . = ORIGIN(FLASH) + LENGTH(FLASH) - 1; BYTE(0xAA); } > FLASH _sdata_load = LOADADDR(.data); /* The bss section contains uninitialized data */ .bss (NOLOAD): { /* Align to page boundary */ . = ALIGN(0x1000); /* Record the start address of this section */ _sbss = .; /* Include all input files with .bss sections */ *(.bss) /* Record the end address of this section */ _ebss = .; /* Align to page boundary */ . = ALIGN(0x1000); } > RAM }
Makefile相关片段
CC = riscv64-unknown-elf-gcc AR = riscv64-unknown-elf-ar *.o: $(CC) -march=rv64gc -mabi=lp64d -nostartfiles --machine-cmodel=medany -misa-spec=20191213 -T linker.ld -c %.c crt0.o: crt0.s $(CC) -march=rv64gc -mabi=lp64d -nostartfiles --machine-cmodel=medany -misa-spec=20191213 -T linker.ld -c crt0.s putchar.a: putchar.o $(AR) r putchar.a putchar.o main.elf: putchar.a main.o crt0.o test.o $(CC) -march=rv64gc -mabi=lp64d -nostartfiles --machine-cmodel=medany -misa-spec=20191213 -T linker.ld crt0.o main.o putchar.a test.o -o main.elf
错误原因
RISC-V的R_RISCV_HI20重定位用于获取符号地址的高20位,配合R_RISCV_LO12_I/R_RISCV_LO12_S获取低12位组合成完整地址,要求符号地址与当前代码段的偏移在±2GB范围内(RV64架构下)。
分步编译时,错误地在编译目标文件阶段传入了链接专用的-T linker.ld选项。该选项会让编译器提前根据链接脚本中的内存布局(FLASH起始0x20000000)计算符号预期位置,但此时还未完成链接,编译器无法确定符号最终地址,导致生成的重定位信息不符合后续链接时的实际地址范围,最终触发截断错误。
而直接编译时,GCC合并了编译与链接阶段,-T linker.ld在正确的链接阶段生效,编译器能根据链接脚本生成适配的重定位信息,因此不会报错。
解决方法
1. 修正分步编译命令
编译目标文件时移除-T linker.ld选项,该选项仅在链接阶段使用:
第一步:编译目标文件
gcc -march=rv64gc -mabi=lp64d --machine-cmodel=medany -c main.c test.c
第二步:链接(保持原命令不变)
gcc -march=rv64gc -mabi=lp64d -nostartfiles --machine-cmodel=medany -T linker.ld crt0.o main.o putchar.a test.o -o main.elf
2. 修正Makefile规则
将通用.o目标的编译命令中的-T linker.ld移除,同时修正通配符规则为正确的模式规则:
CC = riscv64-unknown-elf-gcc AR = riscv64-unknown-elf-ar %.o: %.c $(CC) -march=rv64gc -mabi=lp64d --machine-cmodel=medany -misa-spec=20191213 -c $< -o $@ crt0.o: crt0.s $(CC) -march=rv64gc -mabi=lp64d --machine-cmodel=medany -misa-spec=20191213 -c crt0.s -o crt0.o putchar.a: putchar.o $(AR) r putchar.a putchar.o main.elf: putchar.a main.o crt0.o test.o $(CC) -march=rv64gc -mabi=lp64d -nostartfiles --machine-cmodel=medany -misa-spec=20191213 -T linker.ld crt0.o main.o putchar.a test.o -o main.elf
说明:
- 编译阶段无需
-nostartfiles,该选项是链接阶段用于跳过标准启动文件的 - 模式规则
%.o: %.c确保每个.c文件正确生成对应的.o文件
内容的提问来源于stack exchange,提问作者patvax
相关产品推荐
相关产品推荐

