一、为什么需要动态链接库
1、链接在生活中的应用
链接 其实有点像我们日常生活中的标准化、模块化生产、我们有一个可以生产标准螺帽的生产线,就可以生产很多个不同的螺帽,
只有需要螺帽,我们就可以通过链接的方式、去复制一个出来,放到需要的点,大道汽车、小到信箱
2、静态链接的缺点
但是、如我们有很多个程序都要通过装载器装载到内存的里面,那里面链接好的同样的功能代码,也需要再装载一遍、再占一遍内存空间。
这就好比,假设每个人有骑自行车的需求,那我们给每个人生产一辆自行车带在身边,固然大家都有自行车用,但是马路上肯定会特别拥挤
二、链接可以分动、静、共享运行升内存
1、内存不够用
2、链接过程
3、图解动态链接过程
三、地址无关很重要,相对地址解烦恼
1、地址无关
2、地址相关
3、动态共享库无法做到地址无关
四、PLT 和 GOT,动态链接的解决方案
1、示例代码
1、首先lib.h定义了动态链接库的一个函数show_me_the_money
[root@luoahong 10]# cat lib.h #ifndef LIB_H #define LIB_H void show_me_the_money(int money); #endif
2、lib.c包含了lib.h的实际实现
[root@luoahong 10]# cat lib.c #include <stdio.h> void show_me_the_money(int money) { printf("Show me USD %d from lib.c \n", money); }
3、然后show_me_poor.c调用了lib里面的函数
[root@luoahong 10]# cat show_me_poor.c #include "lib.h" int main() { int money = 5; show_me_the_money(money); }
4、最后,我们把lib.c变异成一个动态链接库,也就是.so文件
[root@luoahong 10]# gcc lib.c -fPIC -shared -o lib.so [root@luoahong 10]# 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出来看一下
…… 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 0x0 40055b: e9 e0 ff ff ff jmp 400540 <_init+0x28> …… 0000000000400676 <main>: 400676: 55 push rbp 400677: 48 89 e5 mov rbp,rsp 40067a: 48 83 ec 10 sub rsp,0x10 40067e: c7 45 fc 05 00 00 00 mov DWORD PTR [rbp-0x4],0x5 400685: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 400688: 89 c7 mov edi,eax 40068a: e8 c1 fe ff ff call 400550 <show_me_the_money@plt> 40068f: c9 leave 400690: c3 ret 400691: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 400698: 00 00 00 40069b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0] ……
完整代码
[root@luoahong 10]# objdump -d -M intel -S show_me_poor show_me_poor: file format elf64-x86-64 Disassembly of section .init: 00000000004004a0 <_init>: 4004a0: 48 83 ec 08 sub rsp,0x8 4004a4: 48 8b 05 4d 0b 20 00 mov rax,QWORD PTR [rip+0x200b4d] # 600ff8 <__gmon_start__> 4004ab: 48 85 c0 test rax,rax 4004ae: 74 05 je 4004b5 <_init+0x15> 4004b0: e8 3b 00 00 00 call 4004f0 <.plt.got> 4004b5: 48 83 c4 08 add rsp,0x8 4004b9: c3 ret Disassembly of section .plt: 00000000004004c0 <.plt>: 4004c0: ff 35 42 0b 20 00 push QWORD PTR [rip+0x200b42] # 601008 <_GLOBAL_OFFSET_TABLE_+0x8> 4004c6: ff 25 44 0b 20 00 jmp QWORD PTR [rip+0x200b44] # 601010 <_GLOBAL_OFFSET_TABLE_+0x10> 4004cc: 0f 1f 40 00 nop DWORD PTR [rax+0x0] 00000000004004d0 <show_me_the_money@plt>: 4004d0: ff 25 42 0b 20 00 jmp QWORD PTR [rip+0x200b42] # 601018 <show_me_the_money> 4004d6: 68 00 00 00 00 push 0x0 4004db: e9 e0 ff ff ff jmp 4004c0 <.plt> 00000000004004e0 <__libc_start_main@plt>: 4004e0: ff 25 3a 0b 20 00 jmp QWORD PTR [rip+0x200b3a] # 601020 <__libc_start_main@GLIBC_2.2.5> 4004e6: 68 01 00 00 00 push 0x1 4004eb: e9 d0 ff ff ff jmp 4004c0 <.plt> Disassembly of section .plt.got: 00000000004004f0 <.plt.got>: 4004f0: ff 25 02 0b 20 00 jmp QWORD PTR [rip+0x200b02] # 600ff8 <__gmon_start__> 4004f6: 66 90 xchg ax,ax Disassembly of section .text: 0000000000400500 <_start>: 400500: 31 ed xor ebp,ebp 400502: 49 89 d1 mov r9,rdx 400505: 5e pop rsi 400506: 48 89 e2 mov rdx,rsp 400509: 48 83 e4 f0 and rsp,0xfffffffffffffff0 40050d: 50 push rax 40050e: 54 push rsp 40050f: 49 c7 c0 80 06 40 00 mov r8,0x400680 400516: 48 c7 c1 10 06 40 00 mov rcx,0x400610 40051d: 48 c7 c7 ed 05 40 00 mov rdi,0x4005ed 400524: e8 b7 ff ff ff call 4004e0 <__libc_start_main@plt> 400529: f4 hlt 40052a: 66 0f 1f 44 00 00 nop WORD PTR [rax+rax*1+0x0] 0000000000400530 <deregister_tm_clones>: 400530: b8 37 10 60 00 mov eax,0x601037 400535: 55 push rbp 400536: 48 2d 30 10 60 00 sub rax,0x601030 40053c: 48 83 f8 0e cmp rax,0xe 400540: 48 89 e5 mov rbp,rsp 400543: 77 02 ja 400547 <deregister_tm_clones+0x17> 400545: 5d pop rbp 400546: c3 ret 400547: b8 00 00 00 00 mov eax,0x0 40054c: 48 85 c0 test rax,rax 40054f: 74 f4 je 400545 <deregister_tm_clones+0x15> 400551: 5d pop rbp 400552: bf 30 10 60 00 mov edi,0x601030 400557: ff e0 jmp rax 400559: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0] 0000000000400560 <register_tm_clones>: 400560: b8 30 10 60 00 mov eax,0x601030 400565: 55 push rbp 400566: 48 2d 30 10 60 00 sub rax,0x601030 40056c: 48 c1 f8 03 sar rax,0x3 400570: 48 89 e5 mov rbp,rsp 400573: 48 89 c2 mov rdx,rax 400576: 48 c1 ea 3f shr rdx,0x3f 40057a: 48 01 d0 add rax,rdx 40057d: 48 d1 f8 sar rax,1 400580: 75 02 jne 400584 <register_tm_clones+0x24> 400582: 5d pop rbp 400583: c3 ret 400584: ba 00 00 00 00 mov edx,0x0 400589: 48 85 d2 test rdx,rdx 40058c: 74 f4 je 400582 <register_tm_clones+0x22> 40058e: 5d pop rbp 40058f: 48 89 c6 mov rsi,rax 400592: bf 30 10 60 00 mov edi,0x601030 400597: ff e2 jmp rdx 400599: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0] 00000000004005a0 <__do_global_dtors_aux>: 4005a0: 80 3d 85 0a 20 00 00 cmp BYTE PTR [rip+0x200a85],0x0 # 60102c <_edata> 4005a7: 75 11 jne 4005ba <__do_global_dtors_aux+0x1a> 4005a9: 55 push rbp 4005aa: 48 89 e5 mov rbp,rsp 4005ad: e8 7e ff ff ff call 400530 <deregister_tm_clones> 4005b2: 5d pop rbp 4005b3: c6 05 72 0a 20 00 01 mov BYTE PTR [rip+0x200a72],0x1 # 60102c <_edata> 4005ba: f3 c3 repz ret 4005bc: 0f 1f 40 00 nop DWORD PTR [rax+0x0] 00000000004005c0 <frame_dummy>: 4005c0: 48 83 3d 48 08 20 00 cmp QWORD PTR [rip+0x200848],0x0 # 600e10 <__JCR_END__> 4005c7: 00 4005c8: 74 1e je 4005e8 <frame_dummy+0x28> 4005ca: b8 00 00 00 00 mov eax,0x0 4005cf: 48 85 c0 test rax,rax 4005d2: 74 14 je 4005e8 <frame_dummy+0x28> 4005d4: 55 push rbp 4005d5: bf 10 0e 60 00 mov edi,0x600e10 4005da: 48 89 e5 mov rbp,rsp 4005dd: ff d0 call rax 4005df: 5d pop rbp 4005e0: e9 7b ff ff ff jmp 400560 <register_tm_clones> 4005e5: 0f 1f 00 nop DWORD PTR [rax] 4005e8: e9 73 ff ff ff jmp 400560 <register_tm_clones> 00000000004005ed <main>: 4005ed: 55 push rbp 4005ee: 48 89 e5 mov rbp,rsp 4005f1: 48 83 ec 10 sub rsp,0x10 4005f5: c7 45 fc 05 00 00 00 mov DWORD PTR [rbp-0x4],0x5 4005fc: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 4005ff: 89 c7 mov edi,eax 400601: e8 ca fe ff ff call 4004d0 <show_me_the_money@plt> 400606: c9 leave 400607: c3 ret 400608: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0] 40060f: 00 0000000000400610 <__libc_csu_init>: 400610: 41 57 push r15 400612: 41 89 ff mov r15d,edi 400615: 41 56 push r14 400617: 49 89 f6 mov r14,rsi 40061a: 41 55 push r13 40061c: 49 89 d5 mov r13,rdx 40061f: 41 54 push r12 400621: 4c 8d 25 d8 07 20 00 lea r12,[rip+0x2007d8] # 600e00 <__frame_dummy_init_array_entry> 400628: 55 push rbp 400629: 48 8d 2d d8 07 20 00 lea rbp,[rip+0x2007d8] # 600e08 <__init_array_end> 400630: 53 push rbx 400631: 4c 29 e5 sub rbp,r12 400634: 31 db xor ebx,ebx 400636: 48 c1 fd 03 sar rbp,0x3 40063a: 48 83 ec 08 sub rsp,0x8 40063e: e8 5d fe ff ff call 4004a0 <_init> 400643: 48 85 ed test rbp,rbp 400646: 74 1e je 400666 <__libc_csu_init+0x56> 400648: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0] 40064f: 00 400650: 4c 89 ea mov rdx,r13 400653: 4c 89 f6 mov rsi,r14 400656: 44 89 ff mov edi,r15d 400659: 41 ff 14 dc call QWORD PTR [r12+rbx*8] 40065d: 48 83 c3 01 add rbx,0x1 400661: 48 39 eb cmp rbx,rbp 400664: 75 ea jne 400650 <__libc_csu_init+0x40> 400666: 48 83 c4 08 add rsp,0x8 40066a: 5b pop rbx 40066b: 5d pop rbp 40066c: 41 5c pop r12 40066e: 41 5d pop r13 400670: 41 5e pop r14 400672: 41 5f pop r15 400674: c3 ret 400675: 90 nop 400676: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 40067d: 00 00 00 0000000000400680 <__libc_csu_fini>: 400680: f3 c3 repz ret Disassembly of section .fini: 0000000000400684 <_fini>: 400684: 48 83 ec 08 sub rsp,0x8 400688: 48 83 c4 08 add rsp,0x8 40068c: c3 ret
我们只关心整个可执行文件中的一部分内容。你应该可以看到,在main函数调用show_me_the_money的函数的时候,对应的代码是这样的:
call 400550 <show_me_the_money@plt>
这里后面一个@plt的关键字,代表了我们需要从PLT,也就是程序链接表里面找要挑用的函数,对应的地址则是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>
五、GOT全局偏移表
在动态链接对应的共享库,我们在共享库的data section里面,保存了一张全局偏移表,虽然数据部分是各个动态链接它的应用程序里面各加载一份的。所有需要引用当前共享库外部的地址的指令,
都会查询GOT,来找到当前运行程序的虚拟内存里的对应位置。而GOT表里的数据,则是在我们加载一个个共享库的时候写进去的
不同的进程,调用同样的lib.so各自里面指向最终加载的动态链接库里面的虚拟内存地址是不同的
这样,虽然不同的程序调用的同样的动态库,各自的内存地址是独立的,挑用的有都是同一个动态库,但是不需要去修改动态库里面的代码所使用的地址
而是各个程序各自维护好自己的GOT,能够找到对应的动态库就好了
1、GOT表位于共享库自己的数据段里,GOT表在内存里和对应的代码位置之间的偏移量,始终是确定的,这样我们的共享库是地址无关的代码,
2、对应的各个程序只需要在物理内存里面加载同一份代码,而我们又要通过这个可以执行程序在加载时,生成的各个不相同的GOT表,来找到它需要调用到的外部变量和函数的地址
这是一个典型的、不修改代码、而是通过修改“地址数据“来进行关联的办法,它有点像我们在C语言里面用函数指针调用对应的函数,并不是通过预先已经确定好的函数名称
来调用,而是利用当时它在内存里面的动态地址来调用
六、总结延伸
这一讲、我们终于在静态链接和程序装载之后,利用动态链接把我们的内存利用到了极致。同样功能的代码生成共享库,我们只要在内存里面保留一份就好了,
这样我们不仅能够做到代码在开发阶段的复用,也能做到代码在运行阶段的复用
实际上、在进行Linux下的程序开发的时候,我们一直会用到各种各样的动态链接库,C语言的标准库在1MB以上。我们撰写任何一个程序可能都需要用到这个库,
常见的Linux服务器里,/usr/bin下面就有成千上外个可执行文件。如果每一个都把标准库静态链接进来的,几GB乃至几十GB的磁盘空间一下子就用出去了。
如果我们服务端的多进程应用要开上千个进程,几GB的内存空间也会一下子就用甩出去了,这个问题在过去计算机的内存较少的时候更佳显著
通过动态链接这个方式,可以说彻底解决这个问题,就像共享单车一样,如果仔细经营,是一个很有社会价值的事情,但是如果粗暴地把它变成无限制地
复制生产,每个人造一辆,只会在系统内知道大量无用的垃圾
过去05-09折五讲理,我们已经把程序怎么从源码变成指令、数据、并装载到内存里面,由CPU一条条执行下去的过程讲完了,希望你有所收获,对于一个程序,是怎么跑起来的,有了一个初步的认识
来源:https://www.cnblogs.com/luoahong/p/10880416.html