动态链接
什么是动态链接
因为静态链接所带来的磁盘和内存空间浪费的问题,发展出动态链接。
动态链接大致理解起来就是不把系统库和自己的代码链接到一个可执行文件,而是将两者分开成两个独立的文件,当程序执行需要系统库时,再将两个文件进行链接。在内存中完成链接的过程就是动态链接,这些用于动态链接的系统库成为共享库。
以下是动态链接和静态链接的过程图
动态链接图可以看出两个文件不再包含单独的testLib.o,而是将testLib.o单独一个内存空间,系统将func1.o文件和testLib.o装入内存,进行动态链接。完成后系统将控制权交给程序入口点,程序开始执行。接下来func2.o想要执行,因为此时内存中已经有testLib.o文件直接进行链接即可。
位置无关代码
可以加载而无须重定位的代码成为位置无关代码(PIC),它是共享库必须具有的属性,通过GCC传递-fpic参数可以生成。
由于一个程序(或者共享库)的数据段和代码段的相对距离总是保持不变的,因此,指令和变量之间的距离是一个运行时的常量,与绝对地址无关。于是就有了全局偏移表(Global Offset Table GOT),它位于数据段头,用于保存全局变量和库函数的引用,每个条目占八个字节,在加载时会进行重定位并填入符号的绝对地址。
实际上,为了引入RELRO保护机制(为了解决延迟绑定的安全问题),GOT被拆分为.got节和.got.plt节两个字节,不需要延迟绑定的前者用于保存全局变量引用,加载到内存后被标记为已读;需要延迟绑定的后者则用于保存函数引用,具有读写权限。
延迟绑定
延迟绑定是为了解决动态链接时,需要重定位的符号多了以后,影响链接器性能问题。基本思想是当函数第一次被调用时,动态链接器才进行符号查找、重定位等操作,如果未被调用则不进行绑定。
ELF文件通过过程链接表(PLT)和GOT的配合来实现延迟绑定,每个被调用的库函数都有一组对应的PLT和GOT。
位于代码段plt节的PLT是一个数组,每个条目占16个字节。其中PLT【0】用于跳转到动态链接器,PLT[1]用于调用系统启动函数_libc_start_main(),从PLT[2]开始是被动用的各个函数条目。
位于数据段got.plt节的GOT也是一个数组,每个条目占8个字节。其中GOT[0]和GOT[1]包含动态链接器在解析函数地址时所需要的两个地址(.dynamic和relor条目),GOT[2]是动态链接器ld-linux.so的入口点,从GOT[3]开始就是被调用的各个函数条目,这些条目默认指向对应PLT条目的第二条指令,完成绑定后才会被修改成函数的实际地址