库其实就是个文件
下面是文件后缀
静态库:.a(linux) .lib(windows)
动态库:.so(linux) .dll(windows)
静态库的制作
ar -rc libmystdio.a my_stdio.o my_string.o
ar是归档工具,rc表示replace和create,ar跟tar有点像,把.o文件打包到一块,其他啥也不干
ar -tv libmystdio.a
动态库的制作
库的使用

ELF文件
那些文件是ELF文件?
.o目标文件
.exe可执行文件
.so动态库文件
核心转储文件
ELF文件的格式
ELF头(ELF header)
1. 节头表(Section Header Table)
- 描述目标文件(
.o
)中的各个节(section)(如.text
、.data
、.symtab
等),供静态链接器(ld
)使用 - 链接时,链接器会合并不同目标文件中的同名节(如将所有
.text
合并到代码段)成段。 - 动态库/可执行文件可以没有节头表。
2. 程序头表(Program Header Table,段头表)
- 描述可执行文件或动态库中的段(segment)(如代码段、数据段),供操作系统加载器(
execve
/ld.so
)使用。 - 加载时,操作系统根据程序头表将段映射到内存(如
LOAD
类型的段) - 动态库和可执行文件必须有程序头表(否则无法加载),可以没有节头表。
3. ELF 头
-
e_entry
字段:指定程序执行的起始地址(即_start
函数的地址),加载后会将此地址赋给 PC/RIP 寄存器。
目标文件的节详解
1. 代码与数据节
节名 | 作用 | 常见属性 |
---|---|---|
.text | 存储代码(函数、指令) | AX (可执行、只读) |
.data | 存储已初始化的全局/静态变量 | WA (可写、已初始化) |
.bss | 存储未初始化的全局/静态变量(实际不占文件空间) | WA (可写、未初始化) |
.rodata | 存储只读数据(如字符串常量、const 变量) | A (只读) |
链接后.text节和.rodata节会合并为代码段,.data节合并为数据段,.bss节合并为bss段
2. 符号与重定位节
节名 | 作用 | 链接阶段关键性 |
---|---|---|
.symtab | 符号表(函数、变量名及其地址/大小) | ✅ 静态链接必需 |
.rel.text | .text 节的重定位条目(修正代码中的地址引用) | ✅ 必需 |
.rel.data | .data 节的重定位条目(修正数据中的地址引用) | ✅ 必需 |
符号表的作用:
1.记录符号定义,符号是变量还是函数,以及对应地址
2.标记未定义符号
3.辅助重定位
重定位表的作用:
目标文件中代码和数据中对符号(函数/变量)的引用地址是临时的。重定位表指导链接器如何将这些临时地址替换为最终的虚拟地址。
3. 动态链接相关节(仅当启用-fPIC编译
时存在)
节名 | 作用 | 动态库必需 |
---|---|---|
.got | 全局偏移表(Global Offset Table) | ✅ 动态库需要 |
.plt | 过程链接表(Procedure Linkage Table) | ✅ 动态库需要 |
静态链接的过程
(1) 输入文件准备
- 目标文件(
.o
):含main的目标文件 - 静态库(
.a
):本质是多个目标文件的打包(通过ar
工具生成),例如:ar rcs libfoo.a foo.o bar.o # 将foo.o和bar.o打包为libfoo.a
(2) 链接器(ld)的工作流程
-
符号解析(Symbol Resolution)
- 合并所有目标文件的符号表(
.symtab
),检查是否存在未定义符号(标记为U
)。 - 若存在未定义符号:直接报错(如
undefined reference to 'printf'
),链接失败。
- 合并所有目标文件的符号表(
-
节合并(Section Merging)
- 将同名节(如
.text
、.data
)从不同目标文件合并为段(Segment):- 所有
.text
→ 代码段(可执行) - 所有
.data
→ 数据段(可读写) - 所有
.bss
→ BSS段(未初始化数据,不占文件空间)
- 所有
- 将同名节(如
-
分配虚拟地址(Address Assignment)
- 链接器为每个段分配最终的虚拟地址。例如:
.text 0x400000 (代码段基地址) .data 0x600000 (数据段基地址)
- 链接器为每个段分配最终的虚拟地址。例如:
-
重定位(Relocation)
- 根据重定位表(
.rel.text
/.rel.data
)修正代码和数据中对符号的引用:- 修正代码中的地址:如
call 0x0
→call 0x400100
(函数foo
的最终地址)。 - 修正数据中的指针:如
mov rax, [0x0]
→mov rax, [0x600020]
(变量var
的地址)。
- 修正代码中的地址:如
- 根据重定位表(
-
生成可执行文件
输出一个完整的ELF可执行文件(a.out
),包含程序头表(描述如何加载段到内存)。
动态链接的过程
1.生成可执行文件时的链接
- 输入文件:
- 目标文件(
main.o
) - 动态库(
.so
,如libc.so
)
- 目标文件(
- 静态链接器(ld)的操作:
- 符号解析:检查
main.o
中的未定义符号(如printf
)是否在动态库的.dynsym
中。 - 生成 PLT/GOT:
- 在可执行文件中创建
.plt
和.got.plt
节。 - 将对动态库函数的调用(如
call printf
)改为call printf@plt
。
- 在可执行文件中创建
- 记录依赖关系:在可执行文件的
.dynamic
段中注明依赖的动态库(如libc.so.6
)。
- 符号解析:检查
2. 运行时动态加载流程
(1) 加载可执行文件
- 操作系统:
- 读取可执行文件的 程序头表(Program Header Table),将代码段(
.text
)和数据段(.data
)映射到进程的虚拟地址空间。 - 从ELF头中读程序入口,将
PC
寄存器设为e_entry
(入口地址,通常是_start
)。
- 读取可执行文件的 程序头表(Program Header Table),将代码段(
(2) 加载动态库
- 动态链接器(ld.so):
- 根据 可执行文件的
.dynamic
段加载所有依赖的动态库(如libc.so
)到 随机地址(ASLR)。 - 重定位got表的里变量的地址,函数地址不重定位,有另一套机制
- 根据 可执行文件的
got表和plt表说明
1.当动态库加载进地址空间,ld.so重定位got表中变量的地址,而代码段中对动态库中变量的访问,编译的指令为访问got表中目标变量的地址,如何做到,就是mov eax【pc+偏移量】,偏移量是pc到目标变量GOT条目的偏移
2.动态库加载进地址空间,动态链接器不会重定位got表中函数项,因为
在链接时会符号解析,如果main.o中使用了动态库的函数,那么调用函数的指令会从call printf被换成call printf@plt,去跳转plt表对应函数条目
printf@plt:
jmp func@got ; 跳转got项
pushq $index ; 压入重定位表索引(如 printf 在 .rela.plt 中的位置)
jmp .plt ; 跳转到动态链接器(ld.so),重定位函数
plt表是在代码段,因为plt是不可更改的,而got在数据段,因为函数项是要动态重定位的,got表中函数项在编译时被编译成plt表中对应函数条目的push指令
第一次调用库函数时,call func@plt 跳转plt对应函数条目,跳转到got项,然后pushq 重定位表索引到队列,调用动态链接器去重定位库函数,将地址写进got,然后调用函数
第二次调用库函数,call func@plt 跳转plt表对应函数条目,跳转got项,然后直接调用函数