目录
静态库
动态库
目标文件
ELF文件
ELF形成可执行
ELF可执行加载
ELF加载
全局偏移量表GOT(global offset table)
库是写好的,成熟的,可以复用的代码
现实中每个程序都要依赖很多的基础的底层库,不可能都是从零开始的
库有两种:
- 静态库 .a[Linux]、.lib[windows]
- 动态库 .so[Linux]、.dll[windows]
静态库
程序在编译链接的时候把库的代码链接到可执行文件中,程序运行的时候将不再需要静态库
一个可执行程序可能用到多个库,这些库可能有的是静态库,有的是动态库,而我们编译默认链接为动态库,只有在该库下找不到动态.so的时候才会采用同名静态库。
若是想使用静态库我们可以使用gcc的 -static 强转设置链接静态库
静态库的生成:
ar -rc libmylib.a mylib.o
若我们有一个自己写的库叫mylib,那么我们该如何使用它呢?
若头文件和库文件都被我们安装到系统的路径下
/usr/lib、/usr/lib64、/usr/local/lib等等
gcc main.c -lmystdio
头文件和库文件和我们自己的源文件在同一个路径下
gcc main.c -L. -lmylib
头文件和库文件都有自己独立的路径
gcc main.c -I头⽂件路径 -L库⽂件路径 -lmylib
- -L:指定库路径
- -I:指定头文件搜索路径
- -l:指定库名
动态库
程序在运行的时候才去链接动态库,多个程序可以共享使用库的代码
一个与动态库链接的可执行程序仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接
动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘的空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间
动态库的生成:
gcc -fPIC -c main.c -o mylib.o
gcc -o libmylib.so mylib.o -shared
动态库需要使用位置无关码PIC,因此需要加上 -fPIC选项
动态库的命名约定通常以lib开头,后缀为.so
- shared:表示生成共享库格式
- fPIC:产生位置无关码
- 库名规则:libxxx.so
动态库使用:
若头文件和库文件都被我们安装到系统的路径下
gcc main.c -lmylib
头文件和库文件和我们自己的源文件在同一个路径下
gcc main.c -L. -lmylib
头文件和库文件都有自己独立的路径
gcc main.c -I头⽂件路径 -L库⽂件路径 -lmylib
我们可以使用ldd命令来查看库或者可执行程序的依赖
ldd libmylib.so
目标文件
在编译之后会产生扩展名为.o的文件,它们被称作目标文件
若是我们有多个源文件,需要修改其中一个,那么我们只需要编译这一个为目标文件再一起链接,这样就不需要浪费时间重新编译整个工程
目标文件是一个二进制文件,文件的格式是ELF,是对二进制代码的一种封装
ELF文件
以下四种都是ELF文件
- 可重定位文件:即xxx.o文件。包含适合于与其他⽬标⽂件链接来创建可执⾏⽂件或者共享⽬标⽂件的代码和数据
- 可执行文件:即可执行程序
- 共享目标文件:即 xxx.so⽂件
- 内核转储:存放当前进程的执⾏上下⽂,⽤于dump信号触发
一个ELF文件由四个部分组成
- ELF头(ELF header):描述⽂件的主要特性。其位于⽂件的开始位置,它的主要⽬的是定位⽂件的其他部分
- 程序头表(Program header table):列举了所有有效的段(segments)和他们的属性。表⾥ 记着每个段的开始的位置和位移(offset)、⻓度,毕竟这些段,都是紧密的放在⼆进制⽂件中,需要段表的描述信息,才能把他们每个段分割开
- 节头表(Section header table):包含对节(sections)的描述
- 节(Section):ELF⽂件中的基本组成单位,包含了特定类型的数据。ELF⽂件的各种信息和数据都存储在不同的节中,如代码节存储了可执⾏代码,数据节存储了全局变量和静态数据等
常见的节有:
- 代码节(.text):⽤于保存机器指令,是程序的主要执⾏部分
- 数据节(.data):保存已初始化的全局变量和局部静态变量
ELF形成可执行
1. 首先将多份C/C++源代码翻译称为目标.o文件
2. 将多份.o文件Section进行合并
ELF可执行加载
⼀个ELF会有多种不同的Section,在加载到内存的时候,也会进⾏Section合并,形成segment
合并原则:相同属性,⽐如:可读,可写,可执⾏,需要加载时申请空间等。这样,即便是不同的Section,在加载到内存中,可能会以segment的形式,加载到⼀起
Section合并的主要原因是为了减少⻚⾯碎⽚,提⾼内存使⽤效率
ELF加载
用来初始化的数据也是从ELF的各个segment来,每个segment有自己的起始地址和长度,用来初始化内核结构中[start, end]等范围的数据
程序调用库方法:只需要知道库的起始虚拟地址+方法偏移量即可定位库中的⽅法
全局偏移量表GOT(global offset table)
因为.data区域是可读写的,所以可以⽀持动态进⾏修改
完