【Linux】动静态库链接原理
📝前言:
这篇文章我们来讲讲Linux——动静态库链接原理
🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏
目录
- 一,目标文件
- 二,ELF文件
- ELF文件格式的特点
- 1. ELF形成可执行
- 2. ELF可执行加载
- 具体查看
- Section查看
- Segment查看
- ELF Header查看
- 三,理解链接与加载
- 1. 静态链接与静态库加载
- 查看编译后的符号表
- 符号表
- 查看反汇编目标文件的内容
- 2. ELF加载与进程地址空间
- 静态链接总结
- 3. 动态链接与动态库加载
- 动态链接器
- 库间的依赖
- PLT
一,目标文件
我们都知道,形成可执行需要经过 编译 + 链接两个步骤。当.c
文件经过编译后形成的.o
文件就叫做可重定位/可重定向目标文件。
当我们只有一个.c
文件被修改时,我们只需要对修改的文件进行重新编译就行了,其他文件不需要。
二,ELF文件
.o
文件,动静态库,可执行文件,内核转储(core dumps)都是ELF格式的二进制文件。
ELF文件格式的特点
ELF
文件被划分成很多个节
ELF Header
:描述文件的全局属性,主要作用是定位⽂件的其他部分Program Header Table
列举了所有有效的段(segments
)和他们的属性。表里记着每个段的开始的位置和位移(offset)、长度。(链接阶段,有合并以后才会生成Program Headers
)【segments
是什么后面讲】- 用来描述整个ELF文件
Section
就是节,不同的数据会被存储到不同的节中。如代码节存储了可执行代码,数据节存储了全局变量和静态数据等Section Header Table
用来描述每个节的信息
1. ELF形成可执行
- 将多份 C/C++ 源代码,翻译成为⽬标
.o
⽂件 - 将多份
.o
⽂件section进行合并(合并是:链接的过程之一)
简单来说,就是把多个.o
文件 中具有相同特性的Section
合并成一个大的Segment
2. ELF可执行加载
- 一个ELF文件在加载到内存的时候,也会把这个文件中具有相同特性(比如:把只读的代码段和只读数据合并)的
Section
合并,形成segment
- 这个合并⼯作也已经在形成ELF的时候,合并⽅式已经确定了,具体合并原则被记录在了ELF的 程序头表(Program header table) 中
为什么要将Section合并?
- 为了减少页面碎片,提高内存使用效率。如果不进行合并,假设页面大小为 4096 字节(内存块基本大小,加载,管理的基本单位),如果
.text
部分为4097字节,.init
部分为 512 字节,那么它们将占用 3 个页面(.text
两个 +.init
一个),而合并后,它们只需 2 个页面。- 将具有相同属性的
section
合并成⼀个大的segment
,可以实现不同的访问权限,从而优化内存管理和权限访问控制
具体查看
Section查看
查看可执行程序的Section
(我的可执行名称叫test
):
readelf -S test
我们可以看到Section header table
对每个Section
的描述
查看可执行程序的Segment
:
readelf -l test
在图片中,我们就可以看到有哪些Section
被合并成了一个Segment
提几个重要的Section
:
text
节 :保存了程序代码指令的代码节。data
节 :保存了初始化的全局变量和局部静态变量等数据。.rodata
节 :保存了只读的数据,如一行C语⾔代码中的字符串。.bss
节 :为未初始化的全局变量和局部静态变量预留位置(对于未初始化的全局变量,我们没必要真正开辟空间,只需要在.bss
里面描述出有多少未初始化的就行).symtab
节 : Symbol Table 符号表,就是源码里面那些函数名、变量名和代码的对应关系。.got.plt
节 (全局偏移表 - 过程链接表):.got节保存了全局偏移表。.got
节和.plt
节⼀起提供了对导⼊的共享库函数的访问⼊⼝,由动态链接器在运行时进行修改。
Segment查看
我们还可以看到其他信息:
我们可以看到Program Header table
对每个段的描述
你会不会很好奇,为什么可执行程序既有Section
又有Segment
?
其实这只是ELF 文件提供 2 个不同的视图/视角来让我们理解这两个部分:
Section
是链接视图(Linking View),面向开发者/工具链。用于编译和链接阶段,供编译器、链接器和调试工具使用Segment
是执行视图(Execution View),面向操作系统。用于程序加载和运行时,指导操作系统如何将文件映射到内存
ELF Header查看
用命令:
readelf -h test
- 我们可以看到
ELF Header
保存着一些大小 / 入口信息,用于定位⽂件的其他部分。 - 系统通过
Magic
来判断文件是不是ELF的格式。Entry point
(标识可执行程序的入口地址【虚拟地址】)
三,理解链接与加载
1. 静态链接与静态库加载
因为静态库就是都是.o
文件打包的,并且静态库在形成可执行的时候,会把库中的函数实现直接拷贝一份到可执行里面。所以研究静态链接,本质上是在研究.o
文件是如何链接的。
test.c
文件内容
1 #include "mystring.h"
2
3 int main()
4 {
5 char* msg = (char*)"hello world
";
6 print(msg); // 调用自定义的print
7 return 0;
8 }
查看编译后的符号表
符号表
符号表用于记录了目标文件中定义和引用的符号相关信息,如:函数名、变量名、全局常量名等。
会用一个长字符串表来存储,像这样:
然后通过