目录
1、动静态库同时存在时,默认使用动态库;若是要使用静态库,编译时加上-static选项;只存在静态库时,无论加不加-static都默认使用静态库
全局偏移量表GOT(global offset table)
一、库的概念
在计算机编程中,“库”(Library)是一组预先编写好的代码组件(如函数、类、数据结构等)的集合,用于提供特定功能或解决特定领域的问题。库的核心目标是复用代码,避免开发者重复造轮子,提高开发效率和代码质量。
- 静态库 .a[Linux]、.lib[windows]
- 动态库 .so[Linux]、.dll[windows]
见一见库:
// ubuntu 动静态库// C$ ls -l /lib/x86_64-linux-gnu/libc -2.31 .so-rwxr-xr-x 1 root root 2029592 May 1 02:20 /lib/x86_64-linux-gnu/libc-2.31.so$ ls -l /lib/x86_64-linux-gnu/libc.a-rw-r--r-- 1 root root 5747594 May 1 02:20 /lib/x86_64-linux-gnu/libc.a//C++$ ls /usr/lib/gcc/x86_64-linux-gnu/ 9 /libstdc++.so -llrwxrwxrwx 1 root root 40 Oct 24 2022 /usr/lib/gcc/x86_64-linuxgnu/9/libstdc++.so -> ../../../x86_64-linux-gnu/libstdc++.so .6$ ls /usr/lib/gcc/x86_64-linux-gnu/ 9 /libstdc++.a/usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a// Centos 动静态库// C$ ls /lib64/libc -2.17 .so -l-rwxr-xr-x 1 root root 2156592 Jun 4 23:05 /lib64/libc-2.17.so[whb@bite-alicloud ~]$ ls /lib64/libc.a -l-rw-r--r-- 1 root root 5105516 Jun 4 23:05 /lib64/libc.a// C++$ ls /lib64/libstdc++.so .6 -llrwxrwxrwx 1 root root 19 Sep 18 20 : 59 /lib64/libstdc++.so .6 ->libstdc++.so .6.0.19$ ls /usr/lib/gcc/x86_64-redhat-linux/ 4.8.2 /libstdc++.a -l-rw-r--r-- 1 root root 2932366 Sep 30 2020 /usr/lib/gcc/x86_64-redhatlinux/4.8.2/libstdc++.
二、静态库
2.1、静态库的制作
动静态库中不要包含main函数
动静态库的都是源文件对应的.o文件
假设现有两个用户,一个为库和头文件的编写者writer,另一个为使用者user。那么因为静态库就是所有.o文件,所以需要把.o文件传给user,此外还需要将头文件传过去,这样才能找到声明以及后面正确地预处理(预处理展开头文件)
以上为writer下的初始文件,现在将.c文件全部编译成.o文件,可以正常运行:
之后传给user使用,这就相当于user用户在使用库了,现演示:
将writer中的头文件和.o文件传过来,可以正常运行
打包 ar -rc libmyc.a *.o
静态库的本质就是所有的.o文件打了一个包所形成的
打包的方法:ar -rc lib###.a *.o (lib###.a中###为实际库名称,rc表示replace和create,也就是打包的.o文件中已经存在就替换,不存在就创建进包中)
在writer中打包:
这样打包之后是一种.a文件,这是归档文件,不需要使用者解包,直接gcc/g++链接即可
之后让user使用这个打包的.a文件(名称定为libmyc.a),当.o文件很多时打包很方便。打包的内容是全部的.o文件,user直接使用即可:
寻找静态库命令 -L接路径,-l(小写L)接库名称
但是发现无法链接,这是因为系统在默认的路径下找指定的库,而我们写的库libmyc.a是找不到的,要使用命令来查找:gcc usercode.c -o usercode -L. -lmyc(-L后面接上库的路径,-l接上要使用的库的真正名称即去掉lib和后缀.a之后的名称)
每次分别传头文件和库文件很繁琐,直接将它们打个包(tar)给user使用(头文件和.o文件在同一个文件中),之后user使用这个打包的lib文件:
在user2中将这个压缩包解压,之后形成一个目录:
在lib中整理头文件和库文件:
开始使用这个库:
寻找头文件-I(大写i)接上路径
出现头文件找不到,这是因为头文件不在当前目录下,而在/lib/include中,要使用命令 -I(大写i) 接上指定路径
又出现了链接问题,要使用-L -l(小写l)来找到我们自己写的库
库也是需要安装在系统中的,看看系统中的头文件(/usr/include)以及库(/lib64)
而这些库文件也是需要安装的,若是我们将我们写的库和头文件拷贝进系统对应位置,那么就不需要指定路径了,只需要-l(小写l)接上要找的库的名称
系统默认的库文件libc.a不用这个选项,是因为系统默认去链接这个库文件
写到makefile下
接下来要在指定路径下形成并且直接打包,这样写:
先make生成.a和.o文件之后make output生成目录以及压缩包:
三、动态库
3.1、动态库的制作
生成动态.o文件的命令要加-fPIC
开篇说了,静、动态库的本质都是.o文件,所以动态库也是要编译而来的,对应命令:
gcc -fPIC -c *.c(由.c生成可以动态链接的.o文件)
动态库打包
动态库打包命令:
gcc -shared -o libmyc.so *.o(不同于静态库的ar)
将静态库的makefile做一个备份,之后将动态库的这些相对于静态库生成打包的小变化写到makefile中:
之后make和make output生成压缩包,传给zhangsan使用:
开始使用:
之后运行usercode,找不到共享目标文件,文件不存在
这是什么原因?
3.2、使用动态库形成的可执行文件为什么不能执行
我们ldd看一下usercode依赖的库:
找不到,为什么?
上述的gcc后面的一堆命令只是告诉了gcc动态库在哪,所以这里的gcc可以链接成功;而没有告诉系统,使用到动态库的文件在执行时会去系统中找动态库再将需要的库加载到内存中,所以显示找不到,形成的可执行文件无法运行
为什么使用静态库的能运行,因为在执行前链接时,所需要的静态库都会拷贝到可执行文件中,所以可以直接执行
解决方法:
1、将使用到的动态库拷贝到系统的/lib64路径下:
2、在/lib64下建立一个指向libmyc.so的软连接,在运行时通过软链接找到这个库(运行时去系统中/lib64中找到的是所需动态库的软连接,之后通过软链接找到动态库)
3、导入环境变量,在运行程序时OS除了在系统中找之外,还会在环境变量LD_LIBRARY_PATH下找。那么将动态库的路径导入这个环境变量就可运行了
导入环境变量是内存级的,所以要一劳永逸就需要将导入的内容写到配置文件中
4、 新增配置文件,在/etc/ld.so.conf.d/配置文件下建立一个配置文件,里面的内容为所需动态库所在的路径,之后再重新加载配置文件ldconfig就可以运行了
3.3、几个结论
1、动静态库同时存在时,默认使用动态库;若是要使用静态库,编译时加上-static选项;只存在静态库时,无论加不加-static都默认使用静态库
加上-static
若是只有动态库,加上-static则出错
删掉动态库,则默认使用静态库
2、在Linux系统下安装的库大部分默认都是动态库
动态库的默认地位是Linux在效率、灵活性和可维护性之间权衡的结果。它适应了开源生态中协作开发、快速迭代的需求,同时通过技术手段(如符号版本控制、包管理)解决了潜在的问题(如依赖冲突)。静态库则作为补充,在特定场景下发挥作用。
3、库和应用程序比为1:n
1:n 关系的本质:通过库实现代码复用和功能抽象,降低软件开发的复杂度和成本。 类比: 库像“工具箱”,应用程序像“工匠”。一个工具箱(库)可以被多个工匠(应用)使用,每个工匠只需专注自己的作品(应用逻辑)。 延伸思考: 现代软件开发中,库的层级可能更复杂(如库依赖其他库),但1:n的基本原则仍然适用。 微服务架构中,服务之间的调用类似库与应用的关系,但通过网络通信实现(而非内存调用)。
4、VS不仅仅能够生成可执行文件,还能生成动静态库
为什么需要生成库(动态/静态)? (1) 代码复用 场景:多个项目需要共享相同的功能(如数学计算、日志记录、网络通信)。 问题:如果每个项目都重复实现这些功能,会导致: 代码冗余:相同的逻辑分散在多个项目中。 维护困难:修复一个漏洞需要更新所有相关项目。 解决方案:将通用功能封装成库,供多个项目调用。 (2) 模块化开发 职责分离:库专注于单一功能(如加密、图形渲染),应用程序只需调用库的接口,无需关心实现细节。 团队协作:不同团队可以独立开发库和应用,提高开发效率。 (3) 性能与资源优化 动态库(.dll):多个程序共享同一份内存中的库代码,节省内存和磁盘空间。 静态库(.lib):编译时直接嵌入代码,避免运行时依赖,适合嵌入式系统或对稳定性要求高的场景。
四、ELF文件
以下四种都是ELF文件:
- 可重定位文件(Relocatable File) :即 xxx.o 文件。包含适合于与其他目标文件链接来创 建可执行文件或者共享目标文件的代码和数据。
- 可执行文件(Executable File) :即可执行程序。
- 共享目标文件(Shared Object File) :即 xxx.so文件。
- 内核转储(core dumps) ,存放当前进程的执行上下文,用于dump信号触发、
- ELF头(ELF header) :描述文件的主要特性。其位于文件的开始位置,它的主要目的是定位文件的其他部分。
- 程序头表(Program header table) :列举了所有有效的段(segments)和他们的属性。表里记着每个段的开始的位置和位移(offset)、长度,毕竟这些段,都是紧密的放在二进制文件中, 需要段表的描述信息,才能把他们每个段分割开。
- 节头表(Section header table) :包含对节(sections)的描述。
- 节(Section ):ELF文件中的基本组成单位,包含了特定类型的数据。ELF文件的各种信息和数据都存储在不同的节中,如代码节存储了可执行代码,数据节存储了全局变量和静态数据等。

五、ELF从形成到加载轮廓
5.1、ELF形成可执行文件
第一步将多份源代码编译成.o文件,第二部将ELF格式的.o文件的section进行合并
5.2、ELF可执行文件加载
readelf -S /usr/bin/ls
There are 30 section headers, starting at offset 0x1c3e8: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
0000000000000038 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002d0 000002d0
0000000000000c18 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400ee8 00000ee8
0000000000000572 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 000000000040145a 0000145a
0000000000000102 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000401560 00001560
0000000000000090 0000000000000000 A 6 2 8
[ 9] .rela.dyn RELA 00000000004015f0 000015f0
00000000000000d8 0000000000000018 A 5 0 8
[10] .rela.plt RELA 00000000004016c8 000016c8
0000000000000ac8 0000000000000018 AI 5 24 8
[11] .init PROGBITS 0000000000402190 00002190
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000004021b0 000021b0
0000000000000740 0000000000000010 AX 0 0 16
[13] .text PROGBITS 00000000004028f0 000028f0
000000000001014a 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 0000000000412a3c 00012a3c
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 0000000000412a60 00012a60
0000000000003cce 0000000000000000 A 0 0 32
[16] .eh_frame_hdr PROGBITS 0000000000416730 00016730
0000000000000754 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 0000000000416e88 00016e88
0000000000002704 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 000000000061a328 0001a328
0000000000000008 0000000000000008 WA 0 0 8
[19] .fini_array FINI_ARRAY 000000000061a330 0001a330
0000000000000008 0000000000000008 WA 0 0 8
[20] .jcr PROGBITS 000000000061a338 0001a338
0000000000000008 0000000000000000 WA 0 0 8
[21] .data.rel.ro PROGBITS 000000000061a340 0001a340
0000000000000a68 0000000000000000 WA 0 0 32
[22] .dynamic DYNAMIC 000000000061ada8 0001ada8
0000000000000200 0000000000000010 WA 6 0 8
[23] .got PROGBITS 000000000061afa8 0001afa8
0000000000000048 0000000000000008 WA 0 0 8
[24] .got.plt PROGBITS 000000000061b000 0001b000
00000000000003b0 0000000000000008 WA 0 0 8
[25] .data PROGBITS 000000000061b3c0 0001b3c0
0000000000000240 0000000000000000 WA 0 0 32
[26] .bss NOBITS 000000000061b600 0001b600
0000000000000d20 0000000000000000 WA 0 0 32
[27] .gnu_debuglink PROGBITS 0000000000000000 0001b600
0000000000000010 0000000000000000 0 0 4
[28] .gnu_debugdata PROGBITS 0000000000000000 0001b610
0000000000000cb8 0000000000000000 0 0 1
[29] .shstrtab STRTAB 0000000000000000 0001c2c8
000000000000011a 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)
总共有三十个节,每个节中存放的都是特定类型的数据,通过下标(即每个节的偏移量和大小来找确定位置)
readelf -l 文件名(查看合并之后的即segment):
Elf file type is EXEC (Executable file)
Entry point 0x404324
There are 9 program headers, starting at offset 64Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x000000000001958c 0x000000000001958c R E 200000
LOAD 0x000000000001a328 0x000000000061a328 0x000000000061a328
0x00000000000012d8 0x0000000000001ff8 RW 200000
DYNAMIC 0x000000000001ada8 0x000000000061ada8 0x000000000061ada8
0x0000000000000200 0x0000000000000200 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x0000000000016730 0x0000000000416730 0x0000000000416730
0x0000000000000754 0x0000000000000754 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x000000000001a328 0x000000000061a328 0x000000000061a328
0x0000000000000cd8 0x0000000000000cd8 R 1Section to Segment mapping:
Segment Sections...下面就是30个section合并成了8个segment,例如03中不同的节.data .bss都被合并进了一个段segment
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .data.rel.ro .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .data.rel.ro .dynamic .got
为什么要合并section
程序头表和节头表分别有什么作用?
ELF提供了两个视图让我们理解


5.3、ELF header
// 查看⽬标⽂件$ readelf -h hello.oELF Header:Magic: 7f 45 4 c 46 02 01 01 00 00 00 00 00 00 00 00 00Class: ELF64 # ⽂件类型Data: 2' s complement, little endian # 指定的编码⽅式Version: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: REL (Relocatable file) # 指出 ELF⽂件的类型Machine: Advanced Micro Devices X86 -64 # 该程序需要的体系结构Version: 0x1Entry point address: 0x0 # 系统第⼀个传输控制的虚拟地址,在那启动进程。假如⽂件没有如何关联的⼊⼝点,该成员就保持为0 。Start of program headers: 0 (bytes into file)Start of section headers: 728 (bytes into file)Flags: 0x0Size of this header: 64 (bytes) # 保存着 ELF 头大小 ( 以字节计数 )Size of program headers: 0 (bytes) # 保存着在文件的程序头表(program header table )中⼀个⼊⼝的⼤⼩Number of program headers: 0 # 保存着在程序头表中⼊⼝的个数。因此,e_phentsize 和 e_phnum 的乘机就是表的⼤⼩ ( 以字节计数 ). 假如没有程序头表,变量为 0 。Size of section headers: 64 (bytes) # 保存着 section 头的⼤⼩ ( 以字节计数) 。⼀个 section 头是在 section 头表的⼀个⼊⼝Number of section headers: 13 # 保存着在 section header table中的入口数目。 e_shentsize 和 e_shnum 的乘积就是 section 头表的大小 ( 以字节计数 )。 假如⽂件没有section 头表,值为 0 。Section header string table index: 12 # 保存着跟 section 名字字符表相关⼊⼝的section 头表 (section header table) 索引。// 查看可执⾏程序$ gcc *.o$ readelf -h a.outELF Header:Magic: 7f 45 4 c 46 02 01 01 00 00 00 00 00 00 00 00 00Class: ELF64Data: 2' s complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: DYN (Shared object file)Machine: Advanced Micro Devices X86 -64Version: 0x1Entry point address: 0x1060Start of program headers: 64 (bytes into file)Start of section headers: 14768 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 13Size of section headers: 64 (bytes)Number of section headers: 31Section header string table index: 30
六、理解连接与加载
6.1、静态连接
静态链接就是.o文件进行合并的过程
objdump -d 目标文件 : 反汇编目标文件的代码段进行查看
call表示要调用哪个函数的目标地址,发现code.c的e8后面为全0,这是因为还没有进行链接,找不到库里面的printf函数;同样,hello.c找不到run函数定义以及printf函数定义
也就是说在链接之前,多个.o文件彼此不知道对方;有一个现象可以佐证,就是在code.里面随便定义一个函数,这个函数没有定义只有code.c的调用,此时发现编译也可以成功,就是因为还没有链接,这个函数的call调用地址为0,发现不了错误
删掉f函数,恢复成之前的样子,来看目标文件的符号表:
puts就是printf函数的实现,发现code.c的符号表中这个实现未定义,说明找不到,也就是未链接;hello.c里面puts和run也是未定义
将它们静态链接成main.exe查看这个链接之后的目标文件的符号表
FUNC标明这是函数;
run和main函数都可以找到(未显示UND),fputs函数找不到,这涉及到动态链接,后面说
函数名前面的数字就是合并之后的下标,readelf -S 查看section
发现合并在了.text代码段
反汇编main.exe的代码段,查看函数调用情况

6.2、ELF加载与进程地址空间
6.2.1、虚拟地址/逻辑地址
一个elf程序,在加载到内存中前,也存在地址,这个地址是各个section进行合并之后的统一编址,在磁盘上,这种地址叫做逻辑地址
6.2.2、重新理解进程虚拟地址空间
首先各个.o文件进行合并,即section合并形成segment,再进行统一编址,这也就是逻辑地址。之后这个ELF程序要执行,先为进程申请内核数据结构例如task_struct以及mm_struct以及vm_area_struct并且使用统一编址的起始地址和各个segment的偏移量等等对内核数据结构进行初始化。之后逐步将内容加载到物理内存,在磁盘上的逻辑地址也作为内容加载到了物理内存,在物理内存中,对加载进来的内容又进程编址,这个地址为物理地址。之后将虚拟地址和物理地址的映射关系填入该进程的页表中。程序开始执行时,因为elf程序的header里面早就记录了entry point address,那么将这个虚拟地址load到cpu的EIP寄存器中,就可以开始运行程序了
注意cpu中只用虚拟地址,也就是进程内核数据结构中的地址,之后才通过页表的映射关系去物理内存中取指令
下图为初始化mm_struct
也就是用segment的起始地址和偏移量去初始化各个划分区域的start和end
6.3、动态库链接和动态库加载
6.3.1、进程如何看到动态库
与静态库一样,动态库也是elf程序,也会有自己的进程,映射到虚拟地址空间的共享区,通过共享区的地址以及页表找到加载到物理内存中的代码数据
6.3.2、进程间是如何共享库的
不同进程的共享区会有到它们共享的那个库的映射,共享库只需要加载到物理内存并且建立好映射关系即可;当我们写的可执行程序运行到调用库函数的时候,此时会从代码区跳转到共享区进行库函数调用
动态库中的代码在内存中不会重复出现
6.3.3、动态链接
动态链接相较于静态链接更加常用,ldd查看一个程序依赖的共享库,可以看看main.exe依赖的共享库
为什么动态链接更常用?
我们的可执行程序被编译器动了手脚
动态链接器:◦ 动态链接器(如ld-linux.so)负责在程序运⾏时加载动态库。◦ 当程序启动时,动态链接器会解析程序中的动态库依赖,并加载这些库到内存中。环境变量和配置⽂件:◦ Linux系统通过环境变量(如LD_LIBRARY_PATH)和配置⽂件(如/etc/ld.so.conf及其⼦配置 ⽂件)来指定动态库的搜索路径。◦ 这些路径会被动态链接器在加载动态库时搜索。 缓存⽂件:◦ 为了提⾼动态库的加载效率,Linux系统会维护⼀个名为/etc/ld.so.cache的缓存⽂件。◦ 该⽂件包含了系统中所有已知动态库的路径和相关信息,动态链接器在加载动态库时会⾸先搜索这个缓存⽂件。这也是为什么之前可通过导环境变量以及配置相关配置文件来找到动态库,因为可执行程序会先通过_start调用动态链接器ld,而ld会在通过环境变量和配置文件来指定动态库的搜索路径
动态库中的相对地址


我们的程序和动态库是怎么映射起来的
struct file
是 Linux 内核中用于管理已打开文件的一种数据结构,它并不是进程特有的内核数据结构,而是属于文件系统层的通用数据结构,并且在使用时不需要用动态库中的字段初始化)

-
“找到磁盘文件的数据块”:
内核通过struct file
→dentry
→ext2_inode
→i_block
,定位到libc.so
在磁盘上的数据块位置。 -
“库加载”:
动态库(libc.so
)被加载到内核页缓存(物理内存的一部分,图中 “库的代码和数据”)。注意:图中 “备注” 提到 “库内容其实就是在文件内核缓冲区中”,本质是 Linux 的 “页缓存(Page Cache)” 机制 —— 文件内容先缓存到内核态内存,再映射到用户进程空间。 -
“映射”:
进程访问共享区虚拟地址时,页表会将其转换为物理内存地址,实际访问内核缓存的动态库内容。
通过页表(Page Table) 建立 “进程虚拟地址 ↔ 物理内存(内核页缓存)” 的映射。 -
“得到库起始虚拟地址”:
动态链接器(ld.so
)完成加载后,会在进程虚拟地址空间的共享区分配一段连续虚拟地址(由vm_area_struct
标记vm_start
),作为libc.so
的起始地址,让进程代码能通过该地址调用库函数。
我们的程序怎样进行库函数调用

全局偏移量表GOT(global offset table)
$ readelf -S a.out...[24] .got PROGBITS 0000000000003fb8 00002fb80000000000000048 0000000000000008 WA 0 0 8...$ readelf -l a.out # .got 在加载的时候,会和 .data 合并成为⼀个 segment ,然后加载在⼀起……05 .init_array .fini_array .dynamic .got .data .bss...
此时有了got表,在代码区记录的就是got地址和所调用的库函数在got表中的偏移量(在编译时确定),并且got表中各个库函数的偏移量也是编译时确定的,那么就不会再改变。当需要调用库函数时,就通过got地址以及库函数在表中的偏移量找到起始地址以及库函数偏移量。
data代码区是可写的,那么加载完毕后,当替换成虚拟起始地址时,就可以在got表中进行替换
库间依赖

plt
objdump -S main.exe,看到库方法后面是plt,这是为什么?
