.dynsym节详解
.dynsym节是ELF(Executable and Linkable Format)文件格式中的动态符号表,用于存储动态链接过程中需要的符号信息。它是动态链接机制的核心组成部分之一,确保了程序在运行时能够正确解析和链接所需的外部符号。
1. 基本概念
.dynsym节是ELF文件中的动态符号表(Dynamic Symbol Table),其设计初衷是专门服务于动态链接过程中的符号导入和导出。与静态链接阶段使用的符号表不同,.dynsym节的一个关键特性是它在程序运行时依然会被加载到内存中,为动态链接器提供必要的符号信息,这是动态链接能够实现的基础。
2. 与.symtab的区别
理解.dynsym与.symtab之间的区别,有助于我们把握ELF文件在链接和运行阶段的不同需求。它们的主要区别体现在以下几个方面:
-
作用范围
- .symtab:包含程序中定义和引用的所有符号信息,涵盖了局部符号和全局符号。它的主要用途是支持调试和静态链接,信息非常全面。
- .dynsym:仅包含与动态链接相关的符号信息,可以看作是.symtab的一个子集。它只关注那些需要在运行时被解析的符号(如从共享库导入的函数或变量)。
-
内存分配
- .symtab:在节区头中通常不被标记为
ALLOC属性,这意味着在程序运行时,这部分数据不会被加载到内存中,以节省内存空间。
- .dynsym:被明确标记为
ALLOC属性,因此它会在程序启动时随其他必要节区一同加载到内存中,供动态链接器实时访问。
-
生命周期
- .symtab:主要服务于编译和链接阶段。在生成最终的可执行文件或共享库后,完全可以使用
strip命令将其删除,以减小二进制文件的大小,而不会影响程序的正常运行。
- .dynsym:由于程序运行时依然需要,因此它不能被删除。它是动态链接功能得以实现的必要条件。
想要深入理解这些底层机制,可以到我们的计算机基础板块查看更多关于编译原理和操作系统相关的文章。
3. 数据结构
.dynsym节在文件中的实际存储格式是一个由Elf32_Sym或Elf64_Sym结构体组成的数组,每个结构体描述一个符号。具体结构定义如下:
typedef struct {
Elf32_Word st_name; // 符号名称在字符串表中的索引
Elf32_Addr st_value; // 符号的值(地址或位置偏移量)
Elf32_Word st_size; // 符号的大小
unsigned char st_info; // 符号类型和绑定属性
unsigned char st_other; // 符号可见性
Elf32_Half st_shndx; // 符号所在的节区索引
} Elf32_Sym;
typedef struct {
Elf64_Word st_name; // 符号名称在字符串表中的索引
unsigned char st_info; // 符号类型和绑定属性
unsigned char st_other; // 符号可见性
Elf64_Half st_shndx; // 符号所在的节区索引
Elf64_Addr st_value; // 符号的值(地址或位置偏移量)
Elf64_Xword st_size; // 符号的大小
} Elf64_Sym;
各字段的详细说明如下:
st_name:这是一个索引值,指向.dynstr(动态字符串表)中该符号名称字符串的起始位置。如果值为0,则表示该符号没有名称。
st_value:符号的值。对于可执行文件或共享库中定义的符号(导出符号),这个值通常是符号在虚拟内存中的地址。对于未定义的引用符号(导入符号),此值可能为0或包含其他重定位信息。
st_size:符号所代表对象的大小,以字节为单位。例如,一个全局数组的大小。
st_info:这是一个复合字段,同时编码了符号的绑定属性(Binding,如全局、局部)和符号类型(Type,如函数、数据对象)。
st_other:主要用于定义符号的可见性(Visibility),例如STV_DEFAULT、STV_HIDDEN等。
st_shndx:指示该符号定义在哪个节区中。有一些特殊值,如SHN_UNDEF表示未定义(即导入符号),SHN_ABS表示绝对值符号。
4. 符号类型和绑定属性
st_info字段是理解符号行为的关键,它可以通过一系列预定义的宏来解析和构建:
#define ELF32_ST_BIND(i) ((i)>>4)
#define ELF32_ST_TYPE(i) ((i)&0xf)
#define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf))
常见的绑定属性(Binding)有:
STB_LOCAL:局部符号,仅在定义它的目标文件内可见。
STB_GLOBAL:全局符号,对所有要合并的目标文件可见,是外部引用的主要对象。
STB_WEAK:弱符号,类似于全局符号,但链接器在找不到全局定义时不会报错,可以有一个默认的弱定义。
常见的符号类型(Type)有:
STT_NOTYPE:未指定类型。
STT_OBJECT:符号关联的是一个数据对象,如变量、数组。
STT_FUNC:符号关联的是一个函数或可执行代码。
STT_SECTION:符号与一个节区相关联,主要用于重定位。
STT_FILE:符号是一个文件名,通常是源文件名称。
5. 作用和功能
.dynsym节在动态链接过程中扮演着不可替代的角色,其主要功能包括:
-
符号导入导出
- 它记录了所有导入符号(Undefined,即本模块需要但定义在其他共享库中的符号)和导出符号(Defined,即本模块定义并提供给其他模块使用的符号)。
-
动态链接支持
- 它为动态链接器(如
ld-linux.so)提供进行符号解析所必需的信息。链接器通过查找.dynsym,将程序中对符号的引用(在.rela.dyn或.rela.plt中描述)绑定到具体的地址上。
-
运行时符号查找
- 像
dlsym()这样的函数,其运行时查找符号的能力正是依赖于内存中的.dynsym节。它与.dynstr(存名字)、.gnu.hash(加速查找)等节协同工作,共同支撑起动态符号解析的整个流程。
6. 相关节区
.dynsym节并非孤立存在,它通常与ELF文件中的其他几个节区紧密配合,形成一个完整的动态链接信息体系:
.dynstr:动态字符串表。.dynsym中的st_name字段是索引,实际符号的名称字符串就存储在这个节区里。
.gnu.hash 或 .hash:符号哈希表。它允许动态链接器通过哈希算法快速定位到.dynsym中的特定符号,极大地提高了符号解析的效率。
.rela.dyn 和 .rela.plt:重定位表。它们记录了哪些位置(在数据段或过程链接表中)的地址需要在加载时或延迟绑定时,根据.dynsym中的符号地址进行修正。
.gnu.version:符号版本信息表。在现代系统中,它与.dynsym中的符号一一对应,用于处理共享库的版本兼容性问题。
7. 实际应用示例
我们可以使用readelf工具来直观地查看一个共享库(如Glibc)的.dynsym节内容:
$ readelf -s libc-2.23.so
Symbol table '.dynsym' contains 2415 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND __libc_stack_end@GLIBC_2.1 (36)
2: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND _rtld_global@GLIBC_PRIVATE (37)
3: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND __libc_enable_secure@GLIBC_PRIVATE (37)
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _IO_stdin_used
从输出中可以看到,前几个符号(Ndx为UND)都是“未定义”的导入符号,它们依赖外部定义。Name字段后的@符号及版本信息,则引出了下一节的内容。
8. 符号版本控制
在支持符号版本控制的系统(如使用GNU工具链的Linux)中,.dynsym节的功能得到了进一步扩展,与版本控制节区配合使用:
.gnu.version:这个节区与.dynsym表项一一对应,每个条目(一个Elfxx_Half)指定了对应符号的版本标识符。
.gnu.version_r:版本需求节区,它描述了该文件需要的共享库版本及其定义。
通过这套机制,可以确保程序在运行时链接到正确版本的符号,避免因共享库更新导致的接口不兼容问题。这种机制对于二进制兼容性和系统安全维护至关重要,也是安全/渗透/逆向领域分析软件依赖和漏洞时经常需要考察的部分。
总而言之,.dynsym节是现代ELF格式动态链接系统的基石。它精确地描述了程序在运行时需要“知道”哪些外部符号,以及自己提供了哪些符号,使得复杂的模块化软件能够高效、灵活地运行。更多深入的编程和系统知识,欢迎在云栈社区交流探讨。