
动、静态链接

相对地址

要实现动态链接共享库,也并不困难,和前面的静态链接里的符号表和重定向表类似,还是和前面一样,我们还是拿出一小段代码来看一看。
首先,lib.h 定义了动态链接库的一个函数 show_me_the_money。
// lib.h#ifndef LIB_H#define LIB_Hvoid show_me_the_money(int money);#endif
lib.c 包含了 lib.h 的实际实现。
// lib.c#include <stdio.h>void show_me_the_money(int money){printf("Show me USD %d from lib.c \n", money);}
然后,show_me_poor.c 调用了 lib 里面的函数。
// show_me_poor.c#include "lib.h"int main(){int money = 5;show_me_the_money(money);}
最后,我们把 lib.c 编译成了一个动态链接库,也就是 .so 文件。
$ gcc lib.c -fPIC -shared -o lib.so$ gcc -o show_me_poor show_me_poor.c ./lib.so
你可以看到,在编译的过程中,我们指定了一个 -fPIC 的参数。这个参数其实就是 Position Independent Code 的意思,也就是我们要把这个编译成一个地址无关代码。
然后,我们再通过 gcc 编译 show_me_poor 动态链接了 lib.so 的可执行文件。在这些操作都完成了之后,我们把 show_me_poor 这个文件通过 objdump 出来看一下。
$ objdump -d -M intel -S show_me_poor
……0000000000400540 <show_me_the_money@plt-0x10>:400540: ff 35 12 05 20 00 push QWORD PTR [rip+0x200512] # 600a58 <_GLOBAL_OFFSET_TABLE_+0x8>400546: ff 25 14 05 20 00 jmp QWORD PTR [rip+0x200514] # 600a60 <_GLOBAL_OFFSET_TABLE_+0x10>40054c: 0f 1f 40 00 nop DWORD PTR [rax+0x0]0000000000400550 <show_me_the_money@plt>:400550: ff 25 12 05 20 00 jmp QWORD PTR [rip+0x200512] # 600a68 <_GLOBAL_OFFSET_TABLE_+0x18>400556: 68 00 00 00 00 push 0x040055b: e9 e0 ff ff ff jmp 400540 <_init+0x28>……0000000000400676 <main>:400676: 55 push rbp400677: 48 89 e5 mov rbp,rsp40067a: 48 83 ec 10 sub rsp,0x1040067e: c7 45 fc 05 00 00 00 mov DWORD PTR [rbp-0x4],0x5400685: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]400688: 89 c7 mov edi,eax40068a: e8 c1 fe ff ff call 400550 <show_me_the_money@plt>40068f: c9 leave400690: c3 ret400691: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]400698: 00 00 0040069b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0]……
我们还是只关心整个可执行文件中的一小部分内容。你应该可以看到,在 main 函数调用 show_me_the_money 的函数的时候,对应的代码是这样的:
call 400550 <show_me_the_money@plt>
这里后面有一个 @plt 的关键字,代表了我们需要从 PLT,也就是程序链接表(Procedure Link Table)里面找要调用的函数。对应的地址呢,则是 400550 这个地址。
那当我们把目光挪到上面的 400550 这个地址,你又会看到里面进行了一次跳转,这个跳转指定的跳转地址,你可以在后面的注释里面可以看到,GLOBAL_OFFSET_TABLE+0x18。这里的 GLOBAL_OFFSET_TABLE,就是我接下来要说的全局偏移表。
400550: ff 25 12 05 20 00 jmp QWORD PTR [rip+0x200512] # 600a68 <_GLOBAL_OFFSET_TABLE_+0x18>
在动态链接对应的共享库,我们在共享库的 data section 里面,保存了一张全局偏移表(GOT,Global Offset Table)。虽然共享库的代码部分的物理内存是共享的,但是数据部分是各个动态链接它的应用程序里面各加载一份的。所有需要引用当前共享库外部的地址的指令,都会查询 GOT,来找到当前运行程序的虚拟内存里的对应位置。而 GOT 表里的数据,则是在我们加载一个个共享库的时候写进去的。
不同的进程,调用同样的 lib.so,各自 GOT 里面指向最终加载的动态链接库里面的虚拟内存地址是不同的。
这样,虽然不同的程序调用的同样的动态库,各自的内存地址是独立的,调用的又都是同一个动态库,但是不需要去修改动态库里面的代码所使用的地址,而是各个程序各自维护好自己的 GOT,能够找到对应的动态库就好了。

我们的 GOT 表位于共享库自己的数据段里。GOT 表在内存里和对应的代码段位置之间的偏移量,始终是确定的。这样,我们的共享库就是地址无关的代码,对应的各个程序只需要在物理内存里面加载同一份代码。而我们又要通过各个可执行程序在加载时,生成的各不相同的 GOT 表,来找到它需要调用到的外部变量和函数的地址。
这是一个典型的、不修改代码,而是通过修改“地址数据”来进行关联的办法。它有点像我们在 C 语言里面用函数指针来调用对应的函数,并不是通过预先已经确定好的函数名称来调用,而是利用当时它在内存里面的动态地址来调用。




