前言
操作系统在执行动态链接的可执行文件时,会首先加载动态链接器,然后由动态链接器根据保存在可执行文件中的动态链接信息,完成依赖动态库的加载、符号解析以及重定位等工作。这些动态链接信息包括但不限于:
- 动态链接器路径;
- 动态链接导入导出的符号定义及引用;
- 动态链接符号重定位描述。
为了保存这些信息,ELF文件格式针对动态链接使用了一些的特殊的节。
解释器段
使用动态链接的可执行文件在加载时,需要通过动态链接器帮助加载可执行文件依赖的动态库,为了指明动态链接器的位置,动态链接的可执行文件使用了一个专门的节叫做.interp
,用以保存可执行文件依赖的动态链接器的路径:
动态段
动态段.dynamic
节是动态链接可执行文件特有的,保存了动态链接器所必需的一些信息,包括运行时依赖的共享库信息、动态链接符号表的位置、动态链接重定位表的位置以及共享库初始化代码的地址等。在Linux下,使用readelf -d
可查看.dynamic
节的内容:
动态段的本质是一个数组,保存了不同类型的链接信息,对于每个数组项的数据结构定义如下:
typedef struct {
Elf64_Sxword d_tag; // 用以标记结构描述的信息类型
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
d_tag标记类型
动态链接器利用Elf64_Dyn结构中的d_tag字段来定位动态段的不同部分,部分常见的类型罗列如下:
标记类型 | 描述 |
---|---|
DT_SYMTAB | 动态链接库符号表.dynsym 的地址 |
DT_STRTAB | 动态链接字符串表.dynstr 的地址 |
DT_STRSZ | 动态链接字符串表大小 |
DT_HASH | 动态链接哈希表.hash 地址 |
DT_SONAME | 共享库名 |
DT_RPATH | 动态链接共享库搜索路径 |
DT_INIT | 初始化代码地址 |
DT_FINIT | 结束代码地址 |
DT_NEED | 依赖的共享库信息 |
DT_REL | 动态链接重定位表地址 |
DT_RELA | 动态链接重定位表地址 |
动态符号表
静态链接中,ELF文件使用符号表.symtab
节保存了关于该文件定义的符号以及引用,类似的,动态链接使用了动态符号表.dynsym
来保存动态链接过程中各个模块之间的符号导入导出关系;与符号表.symtab
不同,动态符号表只保存与动态链接相关的符号。查看动态符号表的内容:
动态链接重定位
与静态链接类似,动态链接使用时同样需要对符号进行重定位。共享库需要重定位的原因是因为导入符号的存在。动态链接下,无论是可执行文件或共享库,一旦其需要依赖其他共享对象,即有导入的符号存在时,那么它的代码或数据中就会有对导入符号的引用。在最终程序运行时,需要对所有导入的符号进行重定位。
重定位表
动态链接重定位表用于在程序运行时对导入符号进行重定位。动态链接的文件中,存在两类重定位表:.rel.dyn
和.rel.plt
:
.rel.dyn
用于对数据引用进行修正,其修正的位置位于.got
以及数据段;.rel.plt
用于对函数引用进行修正,其修正的位置位于.got.plt
。
查看一个可执行文件的重定位表:
相关参考
- 《程序员的自我修养——链接、装载与库》
- 《深入理解计算机系统》
- 《Linux二进制分析》