Linux动态链接2:动态链接的相关结构

最近打算抽空学习张绍文老师的《Android开发高手课》。
想要彻底理解本地监控APP内存的框架的实现原理。
发现理解起来都没有那么容易,在阅读代码的过程中,发现C++、linux、native hook、framework等方面的功底均有所不足。
张绍文老师说过:“看再多的文章,不去思考文章所讲的内容和意图也是没用的;思考再多,不去动手真正实践也是没用的。”
“把进阶的各个主题由点到线串联起来,但这背后必然少不了一些基础的、底层的知识进行支撑”。
这里就把空缺的知识进行补足。

动态链接情况下,可执行文件的装载与静态链接情况基本一样。首先操作系统会读取可执行文件的头部,检查文件的合法性,然后从头部中的“ProgramHeader”中读取每个“Sgment”的虚拟地址、文件地址和属性,并将它们映射到进程虚拟空间的相应位置,这些步骤跟前面的静态链接情况下的装载基本无异。在静态链接情况下,操作系统接着就可以把控制权转交给可执行文件的入口地址,然后程序开始执行,一切看起来非常直观。

启动动态链接器

但是在动态链接情况下,操作系统还不能在装载完可执行文件之后就把控制权交给可执行文件,因为我们知道可执行文件依赖于很多共享对象。这时候,可执行文件里对于很多外部符号的引用还处于无效地址的状态,即还没有跟相应的共享对象中的实际位置链接起来。所以在映射完可执行文件之后,操作系统会先启动个动态链接器 (Dynamic Linker)。

动态链接器的工作

在 Linux 下,动态链接器 ld.so 实际上是一个共享对象,操作系统同样通过映射的方式将它加载到进程的地址空间中。操作系统在加载完动态链接器之后,就将控制权交给动态链接器的入口地址(与可执行文件一样,共享对象也有入口地址)。当动态链接器得到控制权之后,它开始执行一系列自身的初始化操作,然后根据当前的环境参数,开始对可执行文件进行动态链接工作。当所有动态链接工作完成以后,动态链接器会将控制权转交到可执行文件的入口地址,程序开始正式执行

动态链接相关结构

“.interp”段

那么系统中哪个才是动态链接器呢,它的位置由谁决定?
是不是所有的 “*NIX” 系统的动态链接器都位于/ib/ld.so 呢?
实际上,动态链接器的位置既不是由系统配置指定,也不是由环境参数决定,而是由 ELF 可执行文件决定。在动态链接的 ELF 可执行文件中,有一个专门的段叫做“.interp”段(“interp”是“interpreter”(解释器)的缩写)。
如果我们使用objdump丁.具来查看,可以看到“.interp”内容:

在这里插入图片描述
“.interp”的内容很简单,里面保存的就是一个字符串,这个字符串就是可执行文件所需要的动态链接器的路径,在 Linux 下,可执行文件所需要的动态链接器的路径几乎都是“/lib/ld-linuxso.2”,其他的*nix 操作系统可能会有不同的路径.
在 Linux 的系统中,/lib/ld-linux.so.2通常是一个软链接。
比如在我的机器上,它指向/lib/ld-2.6.1.so,这个才是真正的动态链接器。
在 Linux 中,操作系统在对可执行文件的进行加载的时候,它会去寻找装载该可执行文件所需要相应的动态链接器,即“.interp”段指定的路径的共享对象。

动态链接器在 Linux 下是 Glibc 的一部分,也就是属于系统库级别的,它的版本号往往跟系统中的 Glibc 库版本号是一样的,比如我的系统中安装的是 Glibc 2.6.1,那么相应的态链接器也就是/ib/ld-2.6.1.so。
当系统中的 Glibc 库更新或者安装其他版本的时候/lib/ld-linux.so.2 这个软链接就会指向到新的动态链接器,而可执行文件本身不需要修改“.interp”中的动态链接器路径来适应系统的升级。

我们也可以用这个命令来查看一个可执行文件所需要的动态链接器的路径,在 Linux下,往往是如下结果:

S readelf -l a.out I grep interpreter[Requesting program interpreter: /lb/ld-linux,so.2]
而当我们在 FreeBSD 46.2下执行这个命今时,结果是:
S readelf -l a,out l grep interpreter[Requesting programinterpreter: /usr/libexec/ld-elf,so,1
64 位的 Linux 下的可执行文件是:
s readelf -l a.out l grep interpreter[Requesting program interpreter:/lib64/1d-linux-x86-64.5o.2]

".dynamic"段

类似于“.interp”这样的段,ELF 中还有几个段也是专门用于动态链接的,比如“dynamic段和“.dynsym”段等。
要了解动态链接器如何完成链接过程,跟前面一样,从了解 ELF 文件中跟动态链接相关的结构入手将会是一个很好的途径。ELF 文件中跟动态链接相关的段有好几个,相互之间的关系也比较复杂,我们先从“dynaic”段入手。
动态链接 ELF中最重要的结构应该是“dynamic”段,这个段里面保存了动态链接器所需要的基本信息,比如依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等

“dynamic”段的结构很经典,就是我们已经碰到过的ELF中眼熟的结构数组,结构定义在“elf.h”中

typedef struct {
	EIf32_Sword d_tag;
	union {
		Elf32_Word d val;
		Elf32_Addr d ptr;
		} d_un;
} Elf32_Dyn;

Elf32_Dyn 结构由一个类型值加上一个附加的数值或指针。
对于不同的类型,后面附加的数值或者指针有着不同的含义。
我们这里列举几个比较常见的类型值(这些值都是定义在“elf.h”里面的宏),如表 7-2 所示。

定义在“elf.h”里面的宏

在这里插入图片描述

表 7-2 中只列出了一部分定义,还有一些不太常用的定义我们就暂且忽略,具体可以参考LSB 手册和 elf.h 的定义。

从上面给出的这些定义来看,“dynamic”段里面保存的信息有点像 ELF 文件头,只是我们前面看到的 ELF 文件头中保存的是静态链接时相关的内容,比如静态链接时用到的符号表、重定位表等,这里换成了动态链接下所使用的相应信息了。所以,“dynamic”段可以看成是动态链接下 ELF 文件的“文件头”。

符号表(Symbol Table)

为了完成动态链接,最关键的还是所依赖的符号和相关文件的信息。我们知道在静态链接中,有一个专门的段叫做符号表“.symtab”(Symbol Table),里面保存了所有关于该目标文件的符号的定义和引用。

动态符号表(Dynamic Symbol Table)

动态链接的符号和静态链接

动态链接的符号表示实际上它跟静态链接十分相似,
比如前面例子中的 Program1 程序依赖于 Lib.so,引用到了里面的 foobar()函数。那么对于 Program1来说,我们往往称 Program1导入(lmport)了 foobar 函数,foobar是 Program1的导入函数(ImportFunction)。

而站在 Lib.so 的角度来看,它实际上定义了 foobar0函数,并且提供给其他模块使用,我们往往称 Lib.so 导出(Export)了 foobar函数,foobar 是 Lib.so 的导出函数(Export Function)。把这种导入导出关系放到静态链接的情形下,我们可以把它们看作普通的函数定义和引用。

为了表示动态链接这些模块之间的符号导入导出关系,ELF 专门有一个叫做动态符号表(Dynamic Symbol Table)的段用来保存这些信息,这个段的段名通常叫做“dynsym(Dynamic Symbol)。

符号表和动态符号表的区别

与“.symtab”不同的是,“dynsym”只保存了与动态链接相关的符号。

动态链接重定位表

共享对象需要重定位的主要原因是导入符号的存在。

动态链接下,无论是可执行文件或共享对象,一旦它依赖于其他共享对象,也就是说有导入的符号时,那么它的代码或数据中就会有对于导入符号的引用。在编译时这些导入符号的地址未知,在静态链接中,这些未知的地址引用在最终链接时被修正。但是在动态链接中,导入符号的地址在运行时才确定,所以需要在运行时将这些导入符号的引用修正,即需要重定位。

动态链接时进程堆栈初始化信息

站在动态链接器的角度看,当操作系统把控制权交给它的时候,它将开始做链接T作,那么至少它需要知道关于可执行文件和本进程的一些信息。
比如可执行文件有几个段(“Sgment”)、每个属性、程序的入口地址 (为动态连接器需要把控制交给可执行文件) 等。这些信息往往由操作系统传递给动态链接器,保存在进程的堆栈里面。

我们在前面提到过,进程初始化的时候,堆栈里面保存了关于进程执行环境和命令行参数等信息。
事实上,堆栈里面还保存了动态链接器所需要的一些辅助信息数组(Auxiliary Vector)。
辅助信息的格式也是一个结构数组,它的结构被定义在“elf.h”。

参考资料–
《程序员的自我修养一一链接、装载与库》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员-薯片

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值