64位系统中为何无法将含.rodata段的静态对象链接到共享库?
这是个非常典型的ELF链接机制问题,涉及到64位平台下位置无关代码(PIC)的强制约束,我来帮你拆解根本原因:
1. 先搞懂R_X86_64_32重定位到底是什么
R_X86_64_32是64位x86平台下的一种重定位类型,它会生成32位绝对地址来引用目标符号(比如.rodata里的字符串)。这种重定位方式只适用于非位置无关的可执行文件——也就是那些加载地址固定的程序,因为链接器可以提前确定符号的绝对地址。
但共享对象(.so)的本质是位置无关的:它在运行时会被动态链接器加载到进程地址空间的任意位置,无法提前确定固定的32位绝对地址。更关键的是,64位平台的地址空间远超32位范围,就算侥幸用了32位地址,动态链接器也没法保证共享对象会被加载到32位地址区间内,所以链接器直接禁止这种不安全的重定位。
2. 为什么只有含.rodata段时才触发错误?
当你的静态库目标文件里没有.rodata(比如foo只是做简单的数值计算,没有字符串字面量、printf调用这类只读数据),编译器生成的代码会默认使用基于PC的相对寻址(比如R_X86_64_PC32重定位),这种寻址方式本身就是位置无关的——它通过当前指令地址和目标符号的相对偏移来定位,不管加载到哪里都能正常工作,所以链接共享对象时不会报错。
但一旦引入了.rodata段,情况就变了:编译器默认会为.rodata里的符号生成R_X86_64_32重定位。因为.rodata是只读数据,编译器默认假设它会被加载到固定地址(这在非PIC的可执行文件里是成立的),但这个假设在共享对象里完全不成立,于是链接器就抛出了你看到的错误。
3. 为什么只在64位平台出现这个问题?
32位平台和64位平台的ELF重定位规则有本质区别:
- 在32位x86平台,即使是共享对象,也允许使用32位绝对地址重定位。因为整个地址空间就是32位的,动态链接器可以通过修改重定位表来修正这些地址(虽然效率不如PIC,但至少可行)。
- 而64位平台的地址空间是64位的,
R_X86_64_32生成的32位地址根本无法覆盖整个64位地址空间,动态链接器不可能通过这种重定位来修正地址。所以链接器直接强制要求所有参与共享对象链接的目标文件必须是PIC编译的,否则就报错。
4. -fPIC是怎么解决问题的?
当你用-fPIC编译静态库的目标文件时,编译器会生成位置无关的代码和数据引用方式:
- 对于.rodata里的只读数据,会通过**全局偏移表(GOT)**来访问,使用
R_X86_64_GOTPCREL这类重定位类型——它基于当前指令地址的相对偏移来找到GOT表,再通过GOT表间接访问目标数据,完全不依赖固定的绝对地址。 - 对于函数调用,会通过**过程链接表(PLT)**或者相对寻址来实现,确保代码在任意加载地址下都能正常执行。
内容的提问来源于stack exchange,提问作者David Ramati

