Linux系统——库的原理与制作之ELF文件的形成与加载的过程(下)

📅 发布时间:2026/10/13 12:54:52
Linux系统——库的原理与制作之ELF文件的形成与加载的过程(下)
● ‍♂️个人主页进击的荆棘作者其它专栏《数据结构与算法》《算法》《C起始之路》《Linux》目录1.什么是库2.静态库3.动态库4.使用外部库5.目标文件6.ELF文件7.ELF从形成到加载轮廓8.理解链接与加载附录操作大全8.理解链接与加载8.1静态链接#读取可执行程序最终的所有的section清单 [sjxVM-0-6-centos test]$ readelf -S main.exe There are 30 section headers, starting at offset 0x1960: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000400238 00000238 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.ABI-tag NOTE 0000000000400254 00000254 0000000000000020 0000000000000000 A 0 0 4 [ 3] .note.gnu.build-i NOTE 0000000000400274 00000274 0000000000000024 0000000000000000 A 0 0 4 [ 4] .gnu.hash GNU_HASH 0000000000400298 00000298 000000000000001c 0000000000000000 A 5 0 8 [ 5] .dynsym DYNSYM 00000000004002b8 000002b8 0000000000000060 0000000000000018 A 6 1 8 [ 6] .dynstr STRTAB 0000000000400318 00000318 000000000000003d 0000000000000000 A 0 0 1 [ 7] .gnu.version VERSYM 0000000000400356 00000356 0000000000000008 0000000000000002 A 5 0 2 [ 8] .gnu.version_r VERNEED 0000000000400360 00000360 0000000000000020 0000000000000000 A 6 1 8 [ 9] .rela.dyn RELA 0000000000400380 00000380 0000000000000018 0000000000000018 A 5 0 8 [10] .rela.plt RELA 0000000000400398 00000398 0000000000000048 0000000000000018 AI 5 23 8 [11] .init PROGBITS 00000000004003e0 000003e0 000000000000001a 0000000000000000 AX 0 0 4 [12] .plt PROGBITS 0000000000400400 00000400 0000000000000040 0000000000000010 AX 0 0 16 [13] .text PROGBITS 0000000000400440 00000440 0000000000000192 0000000000000000 AX 0 0 16 [14] .fini PROGBITS 00000000004005d4 000005d4 0000000000000009 0000000000000000 AX 0 0 4 [15] .rodata PROGBITS 00000000004005e0 000005e0 000000000000002b 0000000000000000 A 0 0 8 [16] .eh_frame_hdr PROGBITS 000000000040060c 0000060c 000000000000003c 0000000000000000 A 0 0 4 [17] .eh_frame PROGBITS 0000000000400648 00000648 0000000000000114 0000000000000000 A 0 0 8 [18] .init_array INIT_ARRAY 0000000000600e10 00000e10 0000000000000008 0000000000000008 WA 0 0 8 [19] .fini_array FINI_ARRAY 0000000000600e18 00000e18 0000000000000008 0000000000000008 WA 0 0 8 [20] .jcr PROGBITS 0000000000600e20 00000e20 0000000000000008 0000000000000000 WA 0 0 8 [21] .dynamic DYNAMIC 0000000000600e28 00000e28 00000000000001d0 0000000000000010 WA 6 0 8 [22] .got PROGBITS 0000000000600ff8 00000ff8 0000000000000008 0000000000000008 WA 0 0 8 [23] .got.plt PROGBITS 0000000000601000 00001000 0000000000000030 0000000000000008 WA 0 0 8 [24] .data PROGBITS 0000000000601030 00001030 0000000000000004 0000000000000000 WA 0 0 1 [25] .bss NOBITS 0000000000601034 00001034 0000000000000004 0000000000000000 WA 0 0 1 [26] .comment PROGBITS 0000000000000000 00001034 000000000000002d 0000000000000001 MS 0 0 1 [27] .symtab SYMTAB 0000000000000000 00001068 0000000000000618 0000000000000018 28 47 8 [28] .strtab STRTAB 0000000000000000 00001680 00000000000001d5 0000000000000000 0 0 1 [29] .shstrtab STRTAB 0000000000000000 00001855 0000000000000108 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), l (large), p (processor specific) #main.o和code.o的.text被合并了是main.exe的第13个section #如何证明上面的说法 #关于main.o或code.o call后面的00 00 00 00 有没有被修改成为具体的最终函数地址 [sjxVM-0-6-centos test]$ objdump -d main.exe #反汇编main.exe只查看代码段信息包含源代码 main.exe: file format elf64-x86-64 Disassembly of section .init: 00000000004003e0 _init: 4003e0: 48 83 ec 08 sub $0x8,%rsp 4003e4: 48 8b 05 0d 0c 20 00 mov 0x200c0d(%rip),%rax # 600ff8 __gmon_start__ 4003eb: 48 85 c0 test %rax,%rax 4003ee: 74 05 je 4003f5 _init0x15 4003f0: e8 3b 00 00 00 callq 400430 __gmon_start__plt 4003f5: 48 83 c4 08 add $0x8,%rsp 4003f9: c3 retq Disassembly of section .plt: 0000000000400400 .plt: 400400: ff 35 02 0c 20 00 pushq 0x200c02(%rip) # 601008 _GLOBAL_OFFSET_TABLE_0x8 400406: ff 25 04 0c 20 00 jmpq *0x200c04(%rip) # 601010 _GLOBAL_OFFSET_TABLE_0x10 40040c: 0f 1f 40 00 nopl 0x0(%rax) 0000000000400410 putsplt: 400410: ff 25 02 0c 20 00 jmpq *0x200c02(%rip) # 601018 putsGLIBC_2.2.5 400416: 68 00 00 00 00 pushq $0x0 40041b: e9 e0 ff ff ff jmpq 400400 .plt 0000000000400420 __libc_start_mainplt: 400420: ff 25 fa 0b 20 00 jmpq *0x200bfa(%rip) # 601020 __libc_start_mainGLIBC_2.2.5 400426: 68 01 00 00 00 pushq $0x1 40042b: e9 d0 ff ff ff jmpq 400400 .plt 0000000000400430 __gmon_start__plt: 400430: ff 25 f2 0b 20 00 jmpq *0x200bf2(%rip) # 601028 __gmon_start__ 400436: 68 02 00 00 00 pushq $0x2 40043b: e9 c0 ff ff ff jmpq 400400 .plt Disassembly of section .text: 0000000000400440 _start: 400440: 31 ed xor %ebp,%ebp 400442: 49 89 d1 mov %rdx,%r9 400445: 5e pop %rsi 400446: 48 89 e2 mov %rsp,%rdx 400449: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 40044d: 50 push %rax 40044e: 54 push %rsp 40044f: 49 c7 c0 d0 05 40 00 mov $0x4005d0,%r8 400456: 48 c7 c1 60 05 40 00 mov $0x400560,%rcx 40045d: 48 c7 c7 3d 05 40 00 mov $0x40053d,%rdi 400464: e8 b7 ff ff ff callq 400420 __libc_start_mainplt 400469: f4 hlt 40046a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 0000000000400470 deregister_tm_clones: 400470: b8 3f 10 60 00 mov $0x60103f,%eax 400475: 55 push %rbp 400476: 48 2d 38 10 60 00 sub $0x601038,%rax 40047c: 48 83 f8 0e cmp $0xe,%rax 400480: 48 89 e5 mov %rsp,%rbp 400483: 77 02 ja 400487 deregister_tm_clones0x17 400485: 5d pop %rbp 400486: c3 retq 400487: b8 00 00 00 00 mov $0x0,%eax 40048c: 48 85 c0 test %rax,%rax 40048f: 74 f4 je 400485 deregister_tm_clones0x15 400491: 5d pop %rbp 400492: bf 38 10 60 00 mov $0x601038,%edi 400497: ff e0 jmpq *%rax 400499: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) 00000000004004a0 register_tm_clones: 4004a0: b8 38 10 60 00 mov $0x601038,%eax 4004a5: 55 push %rbp 4004a6: 48 2d 38 10 60 00 sub $0x601038,%rax 4004ac: 48 c1 f8 03 sar $0x3,%rax 4004b0: 48 89 e5 mov %rsp,%rbp 4004b3: 48 89 c2 mov %rax,%rdx 4004b6: 48 c1 ea 3f shr $0x3f,%rdx 4004ba: 48 01 d0 add %rdx,%rax 4004bd: 48 d1 f8 sar %rax 4004c0: 75 02 jne 4004c4 register_tm_clones0x24 4004c2: 5d pop %rbp 4004c3: c3 retq 4004c4: ba 00 00 00 00 mov $0x0,%edx 4004c9: 48 85 d2 test %rdx,%rdx 4004cc: 74 f4 je 4004c2 register_tm_clones0x22 4004ce: 5d pop %rbp 4004cf: 48 89 c6 mov %rax,%rsi 4004d2: bf 38 10 60 00 mov $0x601038,%edi 4004d7: ff e2 jmpq *%rdx 4004d9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) 00000000004004e0 __do_global_dtors_aux: 4004e0: 80 3d 4d 0b 20 00 00 cmpb $0x0,0x200b4d(%rip) # 601034 _edata 4004e7: 75 11 jne 4004fa __do_global_dtors_aux0x1a 4004e9: 55 push %rbp 4004ea: 48 89 e5 mov %rsp,%rbp 4004ed: e8 7e ff ff ff callq 400470 deregister_tm_clones 4004f2: 5d pop %rbp 4004f3: c6 05 3a 0b 20 00 01 movb $0x1,0x200b3a(%rip) # 601034 _edata 4004fa: f3 c3 repz retq 4004fc: 0f 1f 40 00 nopl 0x0(%rax) 0000000000400500 frame_dummy: 400500: 48 83 3d 18 09 20 00 cmpq $0x0,0x200918(%rip) # 600e20 __JCR_END__ 400507: 00 400508: 74 1e je 400528 frame_dummy0x28 40050a: b8 00 00 00 00 mov $0x0,%eax 40050f: 48 85 c0 test %rax,%rax 400512: 74 14 je 400528 frame_dummy0x28 400514: 55 push %rbp 400515: bf 20 0e 60 00 mov $0x600e20,%edi 40051a: 48 89 e5 mov %rsp,%rbp 40051d: ff d0 callq *%rax 40051f: 5d pop %rbp 400520: e9 7b ff ff ff jmpq 4004a0 register_tm_clones 400525: 0f 1f 00 nopl (%rax) 400528: e9 73 ff ff ff jmpq 4004a0 register_tm_clones 000000000040052d test: 40052d: 55 push %rbp 40052e: 48 89 e5 mov %rsp,%rbp 400531: bf f0 05 40 00 mov $0x4005f0,%edi 400536: e8 d5 fe ff ff callq 400410 putsplt 40053b: 5d pop %rbp 40053c: c3 retq 000000000040053d main: 40053d: 55 push %rbp 40053e: 48 89 e5 mov %rsp,%rbp 400541: b8 00 00 00 00 mov $0x0,%eax 400546: e8 e2 ff ff ff callq 40052d test 40054b: bf ff 05 40 00 mov $0x4005ff,%edi 400550: e8 bb fe ff ff callq 400410 putsplt 400555: 5d pop %rbp 400556: c3 retq 400557: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1) 40055e: 00 00 0000000000400560 __libc_csu_init: 400560: 41 57 push %r15 400562: 41 89 ff mov %edi,%r15d 400565: 41 56 push %r14 400567: 49 89 f6 mov %rsi,%r14 40056a: 41 55 push %r13 40056c: 49 89 d5 mov %rdx,%r13 40056f: 41 54 push %r12 400571: 4c 8d 25 98 08 20 00 lea 0x200898(%rip),%r12 # 600e10 __frame_dummy_init_array_entry 400578: 55 push %rbp 400579: 48 8d 2d 98 08 20 00 lea 0x200898(%rip),%rbp # 600e18 __init_array_end 400580: 53 push %rbx 400581: 4c 29 e5 sub %r12,%rbp 400584: 31 db xor %ebx,%ebx 400586: 48 c1 fd 03 sar $0x3,%rbp 40058a: 48 83 ec 08 sub $0x8,%rsp 40058e: e8 4d fe ff ff callq 4003e0 _init 400593: 48 85 ed test %rbp,%rbp 400596: 74 1e je 4005b6 __libc_csu_init0x56 400598: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1) 40059f: 00 4005a0: 4c 89 ea mov %r13,%rdx 4005a3: 4c 89 f6 mov %r14,%rsi 4005a6: 44 89 ff mov %r15d,%edi 4005a9: 41 ff 14 dc callq *(%r12,%rbx,8) 4005ad: 48 83 c3 01 add $0x1,%rbx 4005b1: 48 39 eb cmp %rbp,%rbx 4005b4: 75 ea jne 4005a0 __libc_csu_init0x40 4005b6: 48 83 c4 08 add $0x8,%rsp 4005ba: 5b pop %rbx 4005bb: 5d pop %rbp 4005bc: 41 5c pop %r12 4005be: 41 5d pop %r13 4005c0: 41 5e pop %r14 4005c2: 41 5f pop %r15 4005c4: c3 retq 4005c5: 90 nop 4005c6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 4005cd: 00 00 00 00000000004005d0 __libc_csu_fini: 4005d0: f3 c3 repz retq Disassembly of section .fini: 00000000004005d4 _fini: 4005d4: 48 83 ec 08 sub $0x8,%rsp 4005d8: 48 83 c4 08 add $0x8,%rsp 4005dc: c3 retq 最终 1.两个.o的代码段合并到了一起并进行了统一的编址 2.链接的时候会修改.o中没有确定的函数地址在合并完成之后进行相关call地址完成代码调用静态链接就是把库中的.o进行合并和上述过程一致所以链接其实就是将编译之后的所有目标文件连同用到的一些静态库运行时库结合拼装成一个独立的可执行文件。其中就包括之前提到的地址修正当所有模块组合在一起之后链接器会根据我们的.o文件或静态库中的重定位表找到那些需要被重定位的函数全局变量从而修正它们的地址。这就是静态链接的过程​所以链接过程中会对.o中外部符号进行地址重定位8.2ELF加载与进程地址空间8.2.1虚拟地址/逻辑地址问题●一个ELF程序在没有被加载到内存的时候有无地址●进程mm_struct、vm_area_struct在进程刚刚创建的时候初始化数据从哪来答案●一个ELF程序在没有被加载到内存的时候本来就有地址虚拟内存地址不是物理地址当代计算机工作的时候都采用“平坦模式”进行工作。所有页要求ELF对自己的代码和数据进行统一编址下面是objdump -d反汇编之后的代码​最左侧的就是ELF的虚拟地址严格意义上叫做逻辑地址其实地址偏移量但是我们认为其实地址是0所以虚拟地址在程序还没有加载到内存的时候就已经把可执行程序进行统一编址了●进程mm_struct、vm_area_struct在进程刚刚创建的时候初始化数据从哪来从ELF各个segment来每个segment有自己的起始地址和自己的长度用来初始化内核结构中的[startend]等范围数据另外在用详细地址填充页表所以虚拟地址机制不光光OS要支持编译器也要支持8.2.2重新理解进程虚拟地址空间ELF在被编译好之后会把自己未来程序的入口地址记录在ELF header的Entry字段中[sjxVM-0-6-centos test]$ readelf -h main.exe ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2s complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x400440 Start of program headers: 64 (bytes into file) Start of section headers: 6496 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 9 Size of section headers: 64 (bytes) Number of section headers: 30 Section header string table index: 298.3动态链接与动态库加载8.3.1进程如何看到动态库8.3.2进程间是如何看到共享库的8.3.3动态链接8.3.3.1概要动态链接远比静态链接要常用的多。下面这个main.exe可执行程序依赖的动态库就用到了一个C动态链接库[sjxVM-0-6-centos ~]$ ldd main.exe linux-vdso.so.1 (0x00007ffc08db8000) libc.so.6 /lib64/libc.so.6 (0x00007fd39c648000) /lib64/ld-linux-x86-64.so.2 (0x00007fd39ca16000) # ldd命令用于打印程序或库文件所依赖的共享库列表libc.so是C的运行时库里面提供了常用的标准输入输出文件字符串处理等功能为什么编译器默认不适用静态链接静态链接会将编译产生的所有目标文件连同用到的各种库合并形成一个独立的可执行文件不需要额外的依赖就可以运行。照理说应该更加方便静态链接最大的问题在于生成的文件体积大且相当耗费内存资源。随着软件复杂度的提升操作系统也越来越臃肿不同的软件就有可能都包含了相同的功能和代码显然会浪费大量的硬盘空间。此时动态链接就显现了它的优势我们可以将需要共享的代码单独提取出来保存成一个独立的动态链接库等到程序运行时再将它们加载到内存这样就可以节省空间因为同一个模块再内存中只需要保留一份副本可以被不同的进程所共享。动态链接如何工作动态链接实际上将链接的整个过程推迟到了程序加载的时候。当我们去运行一个程序操作系统会首先将程序的数据代码连同它用到的一系列动态库先加载到内存其中每个动态库的加载地址都是不固定的操作系统会根据当前地址空间的使用情况为它们动态分配一段内存。当动态库被加载到内存以后一旦它的内存地址被确定就可以去修正动态库中的那些函数跳转地址了8.3.3.2我们的可执行程序被编译器动了手脚[sjxVM-0-6-centos ~]$ ldd /usr/bin/ls linux-vdso.so.1 (0x00007ffc6dd50000) libselinux.so.1 /lib64/libselinux.so.1 (0x00007f6b67cf5000) libcap.so.2 /lib64/libcap.so.2 (0x00007f6b67af0000) libacl.so.1 /lib64/libacl.so.1 (0x00007f6b678e7000) libc.so.6 /lib64/libc.so.6 (0x00007f6b67519000) libpcre.so.1 /lib64/libpcre.so.1 (0x00007f6b672b7000) libdl.so.2 /lib64/libdl.so.2 (0x00007f6b670b3000) /lib64/ld-linux-x86-64.so.2 (0x00007f6b67f1c000) libattr.so.1 /lib64/libattr.so.1 (0x00007f6b66eae000) libpthread.so.0 /lib64/libpthread.so.0 (0x00007f6b66c92000) [sjxVM-0-6-centos ~]$ ldd main.exe linux-vdso.so.1 (0x00007ffe1df2f000) libc.so.6 /lib64/libc.so.6 (0x00007f2df9fc2000) /lib64/ld-linux-x86-64.so.2 (0x00007f2dfa390000)在C/C程序中当程序开始执行时它首先并不会直接跳转到main函数。实际上程序的入口点是_start这是一个由C运行时库通常是glibc或链接器如ld提供的特殊函数。在_start函数中会执行一系列初始化操作包括1.设置堆栈为程序创建一个初始的堆栈环境2.初始化数据段将程序的数据段如全局变量和静态变量从初始化数据段复制到相应的内存位置并清零未初始化的数据段3.动态链接关键一步_start函数会调用动态链接器的代码来解析和加载程序所依赖的动态库shared libraries。动态链接器会处理所有的符号解析和重定位确保程序中的函数调用和变量访问能够正确地映射到动态库中的实际地址动态链接器●动态链接器如ld-linux.so负责在重新运行时加载动态库●当程序启动时动态链接器会解析程序中的动态库依赖并加载这些库到内存中环境变量和配置文件●Linux系统通过环境变量如LD_LIBRARY_PATH和配置文件如/etc/ld.so.conf及其子配置文件来指定动态库的搜索路径●这些路径会被动态链接器在加载动态库时搜索缓存文件●为提高动态库的加载效率Linux系统会维护一个名为/etc/ld.so.cache的缓存文件●该文件包含了系统中所有已知动态库的路径和相关信息动态链接器在加载动态库时会首先搜索这个缓存文件4.调用__libc_start_main一旦动态链接完成_start函数会调用__libc_start_main这是glibc提供的一个函数。__libc_start_main函数负责执行一些额外的初始化工作如设置信号处理函数、初始化线程库若使用了线程等5.调用main函数最好__libc_start_main函数会调用main函数此时程序的执行控制权才正式交给用户编写的代码6.处理main函数的返回值当main函数返回时__libc_start_main会负责处理这个返回只并最终调用_exit函数终止程序上述过程描述了C/C程序在main函数之前执行的一系列操作但这些操作对大多数程序员来说是透明的。程序员通常只需要关注main函数中的代码而不需要关心底层的初始化过程。然而了解底层细节有助于更好的理解程序的执行流程和调试问题8.3.3.3动态库中的相对地址动态库为了随时进行加载为了支持并映射到任意进程的任意位置对动态库中的方法统一编址采用相对编址的方案进行编址其实可执行程序也一样都要遵守平坦模式只不过exe是直接加载的# ubuntu下查看任意⼀个库的反汇编objdump -S /lib/x86_64-linux-gnu/libc-2.31.so | less# Cetnos下查看任意⼀个库的反汇编$ objdump -S /lib64/libc-2.17.so | less8.3.3.4我们的程序是如何与库具体映射起来的注●动态库也是一个文件要访问也是要被先加载要加载也是要被打开的●让我们的进程找到动态库的本质也是文件操作不过我们访问库函数通过虚拟地址进行跳转访问所有需要把动态库映射到进程的地址空间中8.3.3.5我们的程序如何进行库函数调用注●库已经被映射到了当前进程的地址空间中●库的虚拟起始地址我们也已经知道了●库中每一个方法的偏移量也知道了●所以访问库中任意方法只需要知道库的起始虚拟地址方法偏移量即可定位库中的方法●而且整个调用过程是从代码区跳转到共享区调用完毕再返回到代码区整个过程完全在进程地址空间中进行8.3.3.6全局偏移量表GOTglobal offset table注●由于我们的程序运行之前先把所有库加载并映射所以库的起始虚拟地址都应该提前知道●然后对我们加载到内存中的程序的库函数调用进行地址修改在内存中二次完成地址设置加载地址重定位●注意到修改的是代码区不是说代码区在进程中是只读的吗能修改吗如何修改所以动态链接采用的做法是在.data可执行程序或库自己中专门预留一片区域用来存放函数的跳转地址被叫做全局偏移表GOT表中每一项都是本运行模块要引用的一个全局变量或函数的地址●因为.data区域是可读写所以可以支持动态进行修改$ readelf -S a.out ... [24] .got PROGBITS 0000000000003fb8 00002fb8 0000000000000048 0000000000000008 WA 0 0 8 ... $ readelf -l a.out # .got在加载的时候会和.data合并成为⼀个segment然后加载在⼀起 ... 05 .init_array .fini_array .dynamic .got .data .bss ...1.由于代码段只读不能直接修改代码段。但有了GOT表代码便可以被所有进程共享。但在不同进程的地址空间中各动态库的绝对地址、相对位置都不同。反映到GOT表上就是每个进程的每个动态位都有独立的GOT表所以进程间不能共享GOT表2.在单个.so下由于GOT表与.text的相对位置是固定的完全可以利用CPU的相对寻址来找到GOT表3.在调用函数的时候会首先查表然后根据表中的地址来进行跳转这些地址在动态库加载的时候会被修改为真正的地址4.这种方式实现的动态链接就被叫做PIC地址无关码。即动态库不需要做任何修改被加载到任意内存地址都能够正常运行并且能够被所有进程共享这就是为什么之前需要指定编译器-fPIC参数的原因PIC相对编址GOT8.3.3.7库间依赖注●不仅仅有可执行程序调用库●库也会调用其他库。库之间是有依赖的如何做到库和库之间相互调用也是与地址无关的●库中也有.GOT和可执行一样。这就是为什么都是ELF的格式由于GOT表中的映射地址会在运行时区修改可以通过gdb调试去观察GOT表的地址变化。详细内容可以了解使用gdb调试GOT●由于动态链接在程序加载的时候需要对大量函数进行重定位这显然是非常耗时的。为进一步降低开销操作系统做出一些优化如延迟绑定也叫PLT过程连接表PRocedure Linkage Table。与其在程序已开始就对所有函数进行重定位不如将这个过程推迟到函数第一次被调用的时候因为绝大多数动态库中的函数可能在程序运行期间一次都不会被使用到GOT中的跳转地址默认会指向一段辅助代码它也被叫做桩代码/stup。在我们第一次调用函数的时候这段代码会负责查询真正函数的跳转地址并且去更新GOT表。于是我们再次调用函数的时候就会直接跳转到动态库中真正的函数实现动态链接实际上将链接的整个过程如符号查询、地址的重定位从编译时推迟到了程序的运行时虽然牺牲了一定的性能和程序加载时间但是值得。因为动态链接能够更有效的利用磁盘空间和内存资源极大方便了代码的更新和维护更关键的是它实现了二进制级别的代码复用。解析依赖关系的时候就是加载并完善互相之间的GOT表的过程8.3.4总结●静态链接的出现提高了程序的模块化水平。对于一个大的项目不同的人可以独立地测试和开发自己的模块。通过静态链接生成最终的可执行文件。●静态链接会将编译产生的所有目标文件和用到的各种库合并成一个独立的可执行文件其中我们会去修正模块间函数的跳转地址也被叫做编译重定位静态重定位●动态链接实际上将链接的整个过程推迟到了程序加载的时候。如我们运行一个程序操作系统会首先将程序的数据代码连同它用到的一系列动态库先加载到内存其中每个动态库的加载地址都是不固定的但是无论加载到什么地方都要映射到进程对应的地址空间然后通过.GOT方式进行调用运行重定位也叫动态地址重定位