进击的荆棘头像
Linux系统——库的原理与制作之ELF文件的形成与加载的过程(下)封面图

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

●  💁‍♂️个人主页:进击的荆棘

👇作者其它专栏:

《数据结构与算法》《算法》《C++起始之路》《Linux》


目录

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

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--