启用-mcmodel=large时,如何强制编译器仅生成64位偏移?
问题背景
在实验大型二进制文件时发现:即便使用-mcmodel=large编译,链接阶段仍会因二进制文件中存在小代码风格的PC32重定位指令报错。根本原因是编译器自带的启动目标文件(如Scrt1.o、crtbeginS.o)以及系统动态库均采用小代码模型构建,-mcmodel=large仅能控制自定义代码的生成逻辑,无法修改这些预编译组件内的重定位指令。
复现示例
测试代码
// too_large.cc int main() { static int y = 0; asm volatile(".align 1073741824"); // 人为增大代码体积,触发重定位溢出 y += 5; return 0; }
编译与链接命令
编译阶段无报错:
g++ -mcmodel=large -c too_large.cc -o too_large.o
链接阶段抛出重定位溢出错误:
g++ too_large.o -o too_large
错误信息
/usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o: in function `_start': (.text+0x1d): relocation truncated to fit: R_X86_64_GOTPCRELX against symbol `__libc_start_main@@GLIBC_2.34' defined in .text section in /lib/x86_64-linux-gnu/libc.so.6 /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/Scrt1.o:(.eh_frame+0x20): relocation truncated to fit: R_X86_64_PC32 against `.text' /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/crti.o: in function `_init': (.init+0x7): relocation truncated to fit: R_X86_64_REX_GOTPCRELX against undefined symbol `__gmon_start__' /usr/lib/gcc/x86_64-linux-gnu/12/crtbeginS.o: in function `deregister_tm_clones': crtstuff.c:(.text+0x3): relocation truncated to fit: R_X86_64_PC32 against `.tm_clone_table' crtstuff.c:(.text+0xa): relocation truncated to fit: R_X86_64_PC32 against symbol `__TMC_END__' defined in .data section in too_large crtstuff.c:(.text+0x16): relocation truncated to fit: R_X86_64_REX_GOTPCRELX against undefined symbol `_ITM_deregisterTMCloneTable' /usr/lib/gcc/x86_64-linux-gnu/12/crtbeginS.o: in function `register_tm_clones': crtstuff.c:(.text+0x33): relocation truncated to fit: R_X86_64_PC32 against `.tm_clone_table' crtstuff.c:(.text+0x3a): relocation truncated to fit: R_X86_64_PC32 against symbol `__TMC_END__' defined in .data section in too_large crtstuff.c:(.text+0x57): relocation truncated to fit: R_X86_64_REX_GOTPCRELX against undefined symbol `_ITM_registerTMCloneTable' /usr/lib/gcc/x86_64-linux-gnu/12/crtbeginS.o: in function `__do_global_dtors_aux': crtstuff.c:(.text+0x76): relocation truncated to fit: R_X86_64_PC32 against `.bss' crtstuff.c:(.text+0x81): additional relocation overflows omitted from the output too_large: PC-relative offset overflow in GOT PLT entry for `__cxa_finalize@@GLIBC_2.2.5'
解决方法
要全程强制生成64位偏移,可从以下方向入手:
1. 自定义编译启动文件(crt)
系统自带的crt文件基于小代码模型编译,需下载GCC源码,用-mcmodel=large重新编译生成整套crt目标文件(Scrt1.o、crti.o、crtbeginS.o等),链接时指定使用这些自定义文件,替代系统默认版本。
2. 切换为静态链接
动态库(如libc.so)采用小代码模型构建,静态链接时可选择大代码模型编译的静态库(若存在),或自行编译大模型版本的系统库。链接时添加-static参数,并指定自定义大模型静态库路径。
3. 禁用不必要的crt组件
若程序不需要全局构造/析构、性能分析等crt功能,可通过编译选项跳过相关组件:
-fno-use-cxa-atexit:禁用__cxa_finalize相关逻辑-fno-exceptions:禁用异常处理,减少crt依赖-fno-unwind-tables:避免生成eh_frame相关的PC32重定位-nostartfiles:不使用默认启动文件,手动实现_start函数
手动实现_start的示例:
// minimal_start.s .global _start _start: mov $main, %rdi call *%rdi mov %rax, %rdi mov $60, %rax syscall
编译链接命令:
g++ -mcmodel=large -c too_large.cc -o too_large.o as minimal_start.s -o minimal_start.o g++ -nostartfiles too_large.o minimal_start.o -o too_large
4. 链接器选项临时适配
部分链接器支持通过选项转换PC32重定位为64位版本,如GNU ld的--emit-relocs配合--relax,但该方法稳定性不足,仅作为临时 workaround。
总结
核心矛盾是系统预编译组件采用小代码模型,无法通过单一编译选项直接覆盖。最彻底的方案是自行编译大代码模型版本的crt和系统库;若仅需快速验证,禁用不必要的crt组件或手动实现启动函数是更轻量的选择。
内容的提问来源于stack exchange,提问作者Ricky

