● 💁♂️个人主页:进击的荆棘
👇作者其它专栏:
目录
1.什么是库
2.静态库
3.动态库
4.使用外部库
5.目标文件
6.ELF文件
7.ELF从形成到加载轮廓
8.理解链接与加载
附录:操作大全
8.理解链接与加载
8.1静态链接
#读取可执行程序最终的所有的section清单
[sjx@VM-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 有没有被修改成为具体的最终函数地址?
[sjx@VM-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 <_init+0x15>
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 <puts@plt>:
400410: ff 25 02 0c 20 00 jmpq *0x200c02(%rip) # 601018 <puts@GLIBC_2.2.5>
400416: 68 00 00 00 00 pushq $0x0
40041b: e9 e0 ff ff ff jmpq 400400 <.plt>
0000000000400420 <__libc_start_main@plt>:
400420: ff 25 fa 0b 20 00 jmpq *0x200bfa(%rip) # 601020 <__libc_start_main@GLIBC_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_main@plt>
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_clones+0x17>
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_clones+0x15>
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_clones+0x24>
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_clones+0x22>
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_aux+0x1a>
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_dummy+0x28>
40050a: b8 00 00 00 00 mov $0x0,%eax
40050f: 48 85 c0 test %rax,%rax
400512: 74 14 je 400528 <frame_dummy+0x28>
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 <puts@plt>
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 <puts@plt>
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_init+0x56>
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_init+0x40>
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有自己的起始地址和自己的长度,用来初始化内核结构中的[start,end]等范围数据,另外在用详细地址,填充页表
所以:虚拟地址机制,不光光OS要支持,编译器也要支持
8.2.2重新理解进程虚拟地址空间
ELF在被编译好之后,会把自己未来程序的入口地址记录在ELF header的Entry字段中:
[sjx@VM-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: 2's 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: 29


8.3动态链接与动态库加载
8.3.1进程如何看到动态库

8.3.2进程间是如何看到共享库的

8.3.3动态链接
8.3.3.1概要
动态链接远比静态链接要常用的多。下面这个main.exe可执行程序依赖的动态库就用到了一个C动态链接库:
[sjx@VM-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我们的可执行程序被编译器动了手脚
[sjx@VM-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)
[sjx@VM-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 | less
8.3.3.4我们的程序是如何与库具体映射起来的
注:
●动态库也是一个文件,要访问也是要被先加载,要加载也是要被打开的
●让我们的进程找到动态库的本质:也是文件操作,不过我们访问库函数,通过虚拟地址进行跳转访问,所有需要把动态库映射到进程的地址空间中

8.3.3.5我们的程序如何进行库函数调用
注:
●库已经被映射到了当前进程的地址空间中
●库的虚拟起始地址我们也已经知道了
●库中每一个方法的偏移量也知道了
●所以:访问库中任意方法,只需要知道库的起始虚拟地址+方法偏移量即可定位库中的方法
●而且:整个调用过程,是从代码区跳转到共享区,调用完毕再返回到代码区,整个过程完全在进程地址空间中进行

8.3.3.6全局偏移量表GOT(global 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=相对编址+GOT
8.3.3.7库间依赖
注:
●不仅仅有可执行程序调用库
●库也会调用其他库。库之间是有依赖的,如何做到库和库之间相互调用也是与地址无关的?
●库中也有.GOT和可执行一样。这就是为什么都是ELF的格式

由于GOT表中的映射地址会在运行时区修改,可以通过gdb调试去观察GOT表的地址变化。
详细内容可以了解使用gdb调试GOT
●由于动态链接在程序加载的时候需要对大量函数进行重定位,这显然是非常耗时的。为进一步降低开销,操作系统做出一些优化,如:延迟绑定,也叫PLT(过程连接表(PRocedure Linkage Table))。与其在程序已开始就对所有函数进行重定位,不如将这个过程推迟到函数第一次被调用的时候,因为绝大多数动态库中的函数可能在程序运行期间一次都不会被使用到
GOT中的跳转地址默认会指向一段辅助代码,它也被叫做桩代码/stup。在我们第一次调用函数的时候,这段代码会负责查询真正函数的跳转地址,并且去更新GOT表。于是我们再次调用函数的时候,就会直接跳转到动态库中真正的函数实现


动态链接实际上将链接的整个过程,如符号查询、地址的重定位从编译时推迟到了程序的运行时,虽然牺牲了一定的性能和程序加载时间,但是值得。因为动态链接能够更有效的利用磁盘空间和内存资源,极大方便了代码的更新和维护,更关键的是,它实现了二进制级别的代码复用。
解析依赖关系的时候,就是加载并完善互相之间的GOT表的过程
8.3.4总结
●静态链接的出现,提高了程序的模块化水平。对于一个大的项目,不同的人可以独立地测试和开发自己的模块。通过静态链接,生成最终的可执行文件。
●静态链接会将编译产生的所有目标文件和用到的各种库合并成一个独立的可执行文件,其中我们会去修正模块间函数的跳转地址,也被叫做编译重定位(静态重定位)
●动态链接实际上将链接的整个过程推迟到了程序加载的时候。如我们运行一个程序,操作系统会首先将程序的数据代码连同它用到的一系列动态库先加载到内存,其中每个动态库的加载地址都是不固定的,但是无论加载到什么地方,都要映射到进程对应的地址空间,然后通过.GOT方式进行调用(运行重定位,也叫动态地址重定位)
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2501_93697341/article/details/167397314




