动态链接
经过前面一些列的学习,可以知道一个程序运行时,可执行文件映像是需要被载入到内存中的。为了复用,很多程序使用了相同的静态库,那么这些静态库都是需要被分别链接合并到各个程序的可执行文件的。那么在磁盘上,其实有很多同一个静态库的内容的副本。 各个程序同时运行时,内存中也存在多个同一个静态库的内容的副本。很显然,浪费了很多空间, 也增加了许多载入内存的时间。同一个静态库,比如是C语言的基础库的静态版本,还有许多自研的共享库。
因此,出现了动态链接的技术。 在编译时,不链接依赖模块的库,在运行时才进行链接。在程序运行的初期,系统对依赖的库进行链接,当所依赖的库都存在磁盘中的时候,链接完成。链接的整体过程和静态链接的过程相似, 都包含了符号解析,地址重定位等步骤。 完成动态链接主要工作的是一个称为动态链接器的程序。
基础命令
1 | gcc -fPIC -shared -o common.so common.c // 命令行创建动态链接库 |
我们可以通过前面学习到的objdump命令查看可执行文件的符号表, 我们发现 common.c中的函数及变量依旧是 UND的。他们只有在动态库被加载之后才能确定地址,也就是装载时重定位。
装载时重定位,需要修改指令的地址,那么多个程序间的指令就不可以共享了,由于不同程序的数据部分是分开的则没有这个问题。
地址无关代码
为了使得共享库的指令部分可以共享,需要把可变的指令分离出来,放到一个全局偏移表中,即got段。got段放在数据部分, 各个程序都有一个副本。指定部分存储变量或者函数在got表中的偏移地址,然后找到got表中对应偏移的内容,即变量/函数的地址。
1 | readelf -d foo.so | grep TEXTREL // 没有输出就是地址无关的so |
延迟加载技术
got段中的各个项, 只会在第一次调用的时候进行重定位填充。后面就不再加载。而不是一开始就计算好全部的重定位地址。 got段用于存储变量地址, got.plt段用于存储函数地址。
动态链接相关结构
.interp段
静态链接的文件在装载到内存之后,指令寄存器地址指向程序入口地址,控制权会转交给程序。而动态链接文件,需要先加载一个动态链接器,然后转交给动态链接器加载程序依赖的动态库,最后才转交给程序运行。
动态链接器的信息由elf中的.interp段保存。
1 | 查看各个段的内容 |
.dynamic段
保存动态链接器需要的基本信息, 依赖哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、初始化代码的地址等。
1 | readelf -d exec_file |
其他
动态符号表、动态符号字符串表、符号哈希表
1 | objdump -T exec_file // 动态符号表 |
显示加载动态库
动态库支持动态加载、删除。
可以使用 dlopen/dlclose/dlsym函数操作。灵活的替换使用的库。
行动,才不会被动!
欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。