深入浅出计算机组成原理学习笔记:第十讲
一、为什么需要动态链接库
1、链接在生活中的应用
链接 其实有点像我们日常生活中的标准化、模块化生产、我们有一个可以生产标准螺帽的生产线,就可以生产很多个不同的螺帽,
只有需要螺帽,我们就可以通过链接的方式、去复制一个出来,放到需要的点,大道汽车、小到信箱
2、静态链接的缺点
但是、如我们有很多个程序都要通过装载器装载到内存的里面,那里面链接好的同样的功能代码,也需要再装载一遍、再占一遍内存空间。
这就好比,假设每个人有骑自行车的需求,那我们给每个人生产一辆自行车带在身边,固然大家都有自行车用,但是马路上肯定会特别拥挤
二、链接可以分动、静、共享运行升内存
1、内存不够用
2、链接过程
3、图解动态链接过程
三、地址无关很重要,相对地址解烦恼
1、地址无关
2、地址相关
3、动态共享库无法做到地址无关
四、PLT 和 GOT,动态链接的解决方案
1、示例代码
1、首先lib.h定义了动态链接库的一个函数show_me_the_money
1 2 3 4 5 6 7 | [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的实际实现
1 2 3 4 5 6 7 8 | [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里面的函数
1 2 3 4 5 6 7 | [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文件
1 2 | [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出来看一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | …… 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] …… |
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | [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的函数的时候,对应的代码是这样的:
1 | call 400550 <show_me_the_money@plt> |
这里后面一个@plt的关键字,代表了我们需要从PLT,也就是程序链接表里面找要挑用的函数,对应的地址则是400550这个地址
那么当我们把目录挪到上面的400550这个地址,你会看到里面进行了一次跳转,这个跳转指定国的跳转地址,你可以在后面的注释里可以看到
GLOBAL_OFFSET_TABLE+0x18。这里的GLOBAL_OFFSET_TABLE,就是我们接下来的要说的全局偏移表
1 | 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一条条执行下去的过程讲完了,希望你有所收获,对于一个程序,是怎么跑起来的,有了一个初步的认识
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构