.rel.dyn节详解
.rel.dyn 节是 ELF (Executable and Linkable Format) 文件格式中的一个关键组成部分,专门服务于动态链接时的数据重定位。简单来说,它负责在程序运行时,修正那些无法在编译或静态链接时确定地址的全局数据引用。
1. 基本概念
.rel.dyn 节是动态链接重定位表的一部分,其内部条目记录了程序加载进内存后,哪些位置的指令或数据需要被动态链接器修改。与 .rel.plt 节(主要负责函数调用重定位)不同,.rel.dyn 聚焦于处理除函数调用以外的数据重定位问题,例如对全局变量、静态变量等数据符号的地址引用。
2. 作用和功能
.rel.dyn 节承担着几个核心职责:
- 数据重定位:为全局变量、静态变量等数据符号提供运行时地址修正。
- 动态链接支持:在程序启动时,动态链接器(如
ld-linux.so)会读取此节的信息,完成对符号引用的实际地址绑定。
- GOT表更新:许多重定位条目指向
.got (Global Offset Table,全局偏移表) 中的特定位置,动态链接器会根据这些信息计算出正确的地址并填入GOT表项中,后续代码通过访问GOT来间接获取数据地址。
3. 与.rel.plt的区别
理解两者的区别有助于明确它们的应用场景:
.rel.dyn:处理数据引用的重定位,主要针对全局数据变量、静态变量的地址。
.rel.plt:处理函数调用的重定位,主要针对外部函数(如库函数)的地址。
4. 数据结构
.rel.dyn 节在文件中的实质是一个结构体数组。其元素类型根据架构和重定位类型不同,分为 Elf32_Rel、Elf64_Rel 或 Elf32_Rela、Elf64_Rela。最常见的两种结构定义如下:
// REL类型(不含加数)
typedef struct {
Elf32_Addr r_offset; // 需要重定位的位置(通常是GOT表中的地址)
Elf32_Word r_info; // 低8位表示重定位类型,高24位表示符号表索引
} Elf32_Rel;
// RELA类型(包含加数)
typedef struct {
Elf64_Addr r_offset; // 需要重定位的位置
Elf64_Xword r_info; // 重定位类型和符号表索引的信息
Elf64_Sxword r_addend; // 加数(用于RELA类型)
} Elf64_Rela;
每个字段的含义如下:
r_offset:指定内存中哪个位置的内容需要被修改。对于可执行文件或共享库,这通常是 .got 表中某个条目的虚拟地址。
r_info:一个复合字段,其低位字节定义了重定位的计算方式(如相对寻址、绝对寻址),高位部分则指向动态符号表 (.dynsym) 中的一个条目,标识被引用的符号。
r_addend(仅 RELA 类型存在):一个常量加数,用于参与最终地址的计算。REL 类型则认为加数为0。
5. 常见重定位类型
在不同的处理器架构上,.rel.dyn 节中会出现不同的重定位类型。以下是两种常见架构的示例:
- x86_64架构:
R_X86_64_GLOB_DAT:用于重定位指向全局数据符号的指针。
R_X86_64_RELATIVE:用于重定位与加载地址相关的相对地址。
- ARM架构:
R_ARM_GLOB_DAT:ARM架构下的全局数据重定位。
R_ARM_RELATIVE:ARM架构下的相对重定位。
6. 工作原理
动态链接器在加载程序时,会遵循以下流程处理 .rel.dyn 节:
- 解析 ELF 文件头,找到程序头表,进而定位到
.dynamic 节。
- 从
.dynamic 节中读取关键信息,包括 .rel.dyn 节的地址和大小。
- 遍历
.rel.dyn 节中的每一个重定位条目 (Elfxx_Rel/Rela)。
- 对于每个条目:
- 根据
r_info 找到对应的符号。
- 结合符号的实际加载地址、重定位类型 (
r_info 低位) 和可能的加数 (r_addend) 计算出最终的目标地址。
- 将这个地址写入由
r_offset 指定的内存位置(通常是 .got 表的某个槽位)。
- 完成所有重定位后,程序中所有通过 GOT 访问的外部数据便拥有了正确的地址。
7. 实际应用示例
考虑一个简单的跨模块数据引用场景:
module_a.c:
extern int global_var; // 声明一个外部全局变量
int *ptr_to_global = &global_var; // 一个指向该外部变量的指针
module_b.c:
int global_var = 42; // 实际定义该全局变量
在编译 module_a.c 时,编译器无法知道 global_var 最终会被放在内存的哪个地址,因此它只能为 ptr_to_global 生成一个占位符,并在 .rel.dyn 节中创建一个重定位条目,指示动态链接器:“请将 global_var 的实际地址填到 ptr_to_global 对应的 GOT 槽位里”。程序加载时,动态链接器就会完成这个“填空”工作。
8. 查看.rel.dyn节的方法
我们可以使用 readelf 工具来探查一个 ELF 文件的重定位信息:
readelf -r <可执行文件或共享库> # 查看所有重定位节(.rel.dyn, .rel.plt)的详细信息
readelf -S <可执行文件或共享库> # 查看节头表,确认.rel.dyn节的偏移和大小
.rel.dyn 节是现代操作系统动态链接机制的基石之一,它使得程序模块间的数据共享和地址无关性成为可能。理解它的工作原理,对于深入掌握链接、加载及底层系统编程至关重要。如果你对更多底层技术细节感兴趣,欢迎到 云栈社区 交流探讨。
|