relocation truncated to fit 错误源于risc-v指令寻址范围限制:jal限±1mib、beq限±2kib、auipc+addi限±2gib;常见于启动跳转超距、数据段过大、节合并过长或代码模型(medlow/medany/large)与内存布局不匹配,需结合编译选项、汇编替换(如tail)、链接脚本及objdump分析定位。

直接说结论:relocation truncated to fit 错误不是链接器坏了,而是跳转/取址指令的寻址范围不够——RISC-V 的 JAL、BEQ、LUI+AUIPC 组合等指令有硬性位宽限制,目标地址离当前 PC 超出 ±1MiB(JAL)或 ±4KiB(BEQ)时就会触发该错误。
为什么 RISC-V 容易报 relocation truncated to fit
RISC-V 指令编码严格受限:JAL 只有 20 位带符号立即数(±1MiB),BEQ 只有 12 位(±2KiB),AUIPC + ADDI 对全局变量寻址也只支持 ±2GiB(medany 模型)。一旦代码段或数据段实际布局超出这些范围,汇编器或链接器就无法把符号地址“塞进”指令字段,只能报错。
- 常见诱因:启动代码(如
start.S)里用j bootsys跳转到一个定义在 .text 后半段的函数,但两者偏移 > 1MiB - 静态初始化数据过大,导致
.data节里某个全局变量地址离引用它的指令太远 - 未启用
-ffunction-sections,多个大函数挤在同一个 section,拉大了内部跳转距离 - 用了
medlow模型却试图访问高地址空间的符号(比如内核映射区)
怎么选对代码模型(-mcmodel)
-mcmodel 决定了编译器生成何种地址计算序列,它必须和你的内存布局、运行时加载地址匹配,不能乱设。
-
-mcmodel=medlow:用LUI + ADDI计算地址,要求所有符号落在低 2GiB(RV64)或整个 4GiB(RV32)内。适合裸机固件、地址固定的小系统 -
-mcmodel=medany:用AUIPC + ADDI,支持 PC 相对 ±2GiB。这是动态链接、Linux 用户态程序的默认选择,也是最常用的安全项 -
-mcmodel=large:生成AUIPC + LD加载地址池中的 64 位地址,仅 RV64 支持,性能开销大,仅当真需要跨整个地址空间寻址时才用 - 别混用:如果链接脚本把
.text放在 0xffff800000000000,还用medlow,必然失败;此时必须用medany或large
汇编层快速修复跳转超限
对启动代码或关键 inline asm,手动替换指令比改模型更快,且不引入额外开销。
- 把短跳转
j target换成长跳转tail target(RISC-V 1.10+ 标准指令,语义同j但编码为JALR,支持全地址范围) - 把条件跳转
beq a0, a1, target拆成两步:bne a0, a1, 1f; tail target; 1: - 避免在汇编里手写
lui t0, %hi(sym); addi t0, t0, %lo(sym)—— 如果sym地址超出%hi表达能力,会静默截断。改用auipc t0, %pcrel_hi(sym); ld t0, %pcrel_lo(target)(t0) - 确认你用的 binutils 版本支持
tail:运行riscv64-unknown-elf-as --version,2.39+ 均支持
链接阶段辅助手段
有些问题光靠编译参数压不住,得让链接器配合调整布局。
- 加
-ffunction-sections -fdata-sections,再配-Wl,--gc-sections,删掉未引用的函数/数据,缩小节尺寸、缩短跳转距离 - 用
-Wl,-Ttext=0x80000000显式控制.text起始地址,避免默认位置(如 0x10000)导致后续节被挤到高位 - 检查是否意外启用了
-pie或-shared:它们强制启用medany,但如果启动代码没按 PIC 编写(比如直接写绝对地址),反而会加剧重定位失败
最容易被忽略的一点是:这个错误往往出现在你加了新功能模块之后,但你只盯着新增代码看——其实真正超限的是旧启动代码里一条 j 指令,它原本跳 0x2000 字节,现在因为新模块插入,跳距变成 0x120000,早就不在 JAL 范围内了。排查时务必用 llvm-objdump -d 看报错地址附近的机器码和符号偏移,而不是只信错误信息里的文件行号。











