ELF重定位的应用顺序是如何规定的?附Linux实践案例
ELF重定位的执行顺序规则
示例代码与编译步骤
我们先看两个测试文件:
use_message.cpp
#include <iostream> extern const char* message; void print_message(); int main() { std::cout << message << '\n'; print_message(); }
libmessage.cpp
#include <iostream> const char* message = "Meow!"; // 字符串字面量的绝对地址在.so中需要运行时重定位 void print_message() { std::cout << message << '\n'; }
将代码编译为可执行文件与共享库的命令如下:
$ g++ use_message.cpp -c -pie -o use_message.o $ g++ libmessage.cpp -fPIC -shared -o libmessage.so $ g++ use_message.o libmessage.so -o use_message
运行时重定位操作分析
message的定义位于libmessage.so中,执行use_message时动态链接器会完成以下重定位操作:
- 用字符串数据的加载地址更新
libmessage.so中的message定义 - 将
libmessage.so中的message定义复制到use_message的.bss段中 - 更新
libmessage.so中的全局偏移表(GOT),使其指向use_message中message的新版本
通过readelf导出的重定位信息对应如下:
use_message(可执行文件)
Offset Info Type Sym. Value Sym. Name + Addend 000000004150 000c00000005 R_X86_64_COPY 0000000000004150 message + 0
此条目对应上述重定位操作2。
libmessage.so(共享库)
Offset Info Type Sym. Value Sym. Name + Addend 000000004040 000000000008 R_X86_64_RELATIVE 2000 000000003fd8 000b00000006 R_X86_64_GLOB_DAT 0000000000004040 message + 0
这两个条目分别对应重定位操作1和3。
这里存在明确的依赖:必须先完成libmessage.so中message的地址修正(操作1),才能执行use_message中的复制操作(操作2),否则use_message会获得错误的地址值。
核心问题解答
ELF重定位的顺序规则来源与执行逻辑
ABI规范的硬性规定
重定位的执行顺序由System V AMD64 ABI这类规范明确定义,比如:- 优先处理共享库内部的
R_X86_64_RELATIVE类型重定位:这类重定位用于修正共享库加载后的基址偏移,是所有符号地址修正的基础,必须最先完成。 R_X86_64_COPY类型重定位(即示例中的复制操作)被明确要求在共享库的符号定义完成修正后执行,因为它依赖共享库中符号的最终加载地址。
- 优先处理共享库内部的
动态链接器的标准化处理
动态链接器(如ld.so)不需要自行分析重定位之间的依赖关系,而是严格遵循ABI规范定义的顺序处理不同类型的重定位:- 先完成所有共享库的基础地址修正(
RELATIVE类重定位) - 接着处理共享库的符号定义类重定位(如
GLOB_DAT) - 再处理可执行文件的
COPY类重定位 - 最后处理剩余的符号引用重定位
- 先完成所有共享库的基础地址修正(
静态链接器的输出约束
静态链接器在生成可执行文件和共享库时,会按照ABI要求组织重定位表的条目顺序,确保依赖项的重定位被优先处理。比如共享库的RELATIVE重定位会被放在重定位表的靠前位置,可执行文件的COPY重定位则会被安排在需要依赖的共享库符号重定位之后,不会出现顺序颠倒的情况。
内容的提问来源于stack exchange,提问作者TartanLlama
相关产品推荐
相关产品推荐

