背景:静态链接库一旦链接进去,代码和变量的 section 都合并了,因而程序运行的时候,就不依赖于这个库是否存在。但是这样有一个缺点:
-
就是相同的代码段,如果被多个程序使用的话,在内存里面就有多份;
-
而且一旦静态链接库更新了,如果二进制执行文件不重新编译, 也不随着更新。
因而就出现了另一种,动态链接库(Shared Libraries)
,不仅仅是一组对象文件的简单归档,而是多个对象文件的重新组合,可被多个程序共享。
当一个动态链接库被链接到一个程序文件中的时候,最后的程序文件并不包括动态链接库中的代码,而仅仅包括对动态链接库的引用,并且不保存动态链接库的全路径,仅仅保存动态链接库的名称。当运行这个程序的时候,首先寻找动态链接库,然后加载它。
动态链接库,就是 ELF 的第三种类型,共享对象文件(Shared Object)(目标文件、可执行文件)。
基于动态连接库创建出来的二进制文件格式还是 ELF,但是稍有不同。
-
首先,多了一个
.interp
的 Segment,这里面是ld-linux.so
,这是动态链接器,也就是说,运行时的链接动作都是它做的。 -
另外,ELF 文件中还多了两个 section,一个是.plt,过程链接表(
Procedure Linkage Table,PLT
),一个是.got.plt,全局偏移量表(Global Offset Table,GOT
)。
它们是怎么工作的,使得程序运行的时候,可以将 so 文件动态链接到进程空间的呢?
若dynamiccreateprocess
这个程序要调用 libdynamicprocess.so
里的 create_process
函数。由于是运行时才去找,编译的时候,压根不知道这个函数在哪里,所以就在 PLT
里面 建立一项 PLT[x]
。这一项也是一些代码,有点像一个本地的代理,在二进制程序里面,不直接调用 create_process
函数,而是调用 PLT[x]
里面的代理代码,这个代理代码会在运行的时候找真正的 create_process
函数。
去哪里找代理代码呢?这就用到了 GOT,这里面也会为 create_process
函数创建一项 GOT[y]
。这一项是运行时 create_process
函数在内存中真正的地址。
如果这个地址在,dynamiccreateprocess 调用 PLT[x]
里面的代理代码,代理代码调用 GOT 表中对应项 GOT[y]
,调用的就是加载到内存中的 libdynamicprocess.so 里面的 create_process 函数了。
但是 GOT 怎么知道的呢?对于 create_process
函数,GOT 一开始就会创建一项 GOT[y]
,但是这里面没有真正的地址,因为它也不知道,但是它有办法,它又回调 PLT, 告诉它,你里面的代理代码来找我要 create_process
函数的真实地址,我不知道,你想想 办法吧。
PLT 这个时候会转而调用 PLT[0]
,也即第一项,PLT[0]
转而调用 GOT[2]
,这里面是 ldlinux.so 的入口函数,这个函数会找到加载到内存中的 libdynamicprocess.so 里面的 create_process
函数的地址,然后把这个地址放在 GOT[y]
里面。下次,PLT[x]
的代理函 数就能够直接调用了。