考虑 Linux 系统上的以下两个文件:
使用消息.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!";
void print_message() {
std::cout << message << '\n';
}
我们可以将use_message.cpp编译为目标文件,将libmessage.cpp编译为共享库,并将它们链接在一起,如下所示:
$ 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中的全局偏移表,使其指向use_message
message
内部的新版本
由 转储的相关搬迁readelf
是:
使用消息
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。
重定位编号 1 和 2 之间存在依赖性:对libmessage.so定义的更新message
必须在该值复制到use_message之前进行,否则use_message将不会指向正确的位置。
我的问题是:申请搬迁的顺序是如何规定的?ELF 文件中是否有编码指定这一点?或者在 ABI 中?或者动态链接器只是期望计算出重定位本身之间的依赖关系,并确保写入给定内存地址的任何重定位都在从同一位置读取的任何重定位之前运行?静态链接器是否仅输出重定位,以便可执行文件中的重定位始终可以在共享库重定位之后处理?
我认为重定位解析顺序没有由标准指定。动态加载器定义顺序。为了支持复制重定位,主可执行文件最后被重定位。链接器仅为可执行链接(-no-pie/-pie)生成复制重定位,并且了解动态加载器语义。
引用 https://maskray.me/blog/2021-01-18-gnu-indirect-function#relocation-resolving-order:
有两部分:模块内的顺序和两个模块之间的顺序。
glibc rtld 以反向搜索顺序(反向 l_initfini)处理重定位,其中 rtld 本身有一个特殊情况。主可执行文件需要最后处理才能处理 R_*_COPY。如果A有一个ifunc引用B,通常B需要在A之前重定位。如果没有ifunc,共享对象的解析顺序可以是任意的。
假设我们有以下依赖树。
l_initfini包含main、dep1.so、dep2.so、dep4.so、dep3.so、libc.so.6、ld.so。重定位解析顺序为 ld.so (bootstrap)、libc.so.6、dep3.so、dep4.so、dep2.so、dep1.so、main、ld.so。
在模块内,glibc rtld 按顺序解析重定位。假设DT_RELA(.rela.dyn)和DT_PLTREL(.rela.plt)都存在,glibc逻辑如下:
穆斯尔
ldso/dynlink.c
有:FreeBSD rtld 使用更复杂的顺序,这使得某些 ifunc 代码更加健壮。
顺便说一句,
use_message
(使用 -fPIE 可重定位文件)由于 GCC 需要复制重定位HAVE_LD_PIE_COPYRELOC
。对于 Clang 和 GCC 的其他架构,PIE 模式不会导致复制重定位。