目标文件
Linux系统的目标文件格式都遵循ELF格式,目标文件一共分为四类
ELF文件类型 | 文件类型 |
---|---|
可重定位文件 | Linux的.o |
可执行文件 | a.out |
共享目标文件 | Linux的 .so |
核心转储文件 | coredump文件 |
目标文件有什么
目标文件主要分为两个部分:代码段和数据段,这样划分的理由有3点
- 权限管理:代码段只读,可执行;数据段,可读可写,可执行
- 缓存友好:由于程序局部性原理,加快运行速度
- 节省空间,主要是代码段,在动态链接的情况下可以重复利用
代码段主要结构
- .text:代码指令
- .init:C++中main函数之前构造函数
- .fini:C++中main函数之后析构函数
- __libc_atexit:用户注册的退出函数
- .rodata:const变量,字符串常量,虚表,type_info
数据段主要结构
- .data:初始化数据段
- .bss:未初始化数据段(不占据磁盘空间)
- .tdata:线程局部存储初始化数据段
- .tbss:线程局部存储未初始化数据段(不占据磁盘空间)
- .got:全局偏移量表,用户动态链接生成地址无关代码
- .plt:实现动态链接的延迟绑定
其他段
这些段用于静态链接,动态链接或者保存一些调试信息
- 静态链接:字符串表,符号表,重定位表
- 动态链接:.dynamic,动态字符串表,动态符号表,动态重定位表
- 调试信息:.debug,.line
静态链接
静态库是什么
静态库是一系列.o文件压缩打包在一起,没有对.o文件的结构作任何改变。
静态链接过程
- 地址和空间分配
- 符号决议
- 重定位
地址和空间分配
静态链接中地址和空间分配有两层含义,一是分配磁盘存储空间,二是分配加载后的虚拟地址空间。.bss段需要分配磁盘存储空间
- 扫描各个输入目标文件,获取各个段的长度
- 将相似段合并到一个目标文件
- 将输入目标文件的符号表合并,生成全局符号表
符号决议和重定位
符号决议
- 强符号和弱符号:初始化全局变量和函数是强符号,未初始化全局变量是弱符号(未初始化全局变量生成.o文件的时候并没有放入.bss,而是符号决议后,有了固定大小才放入.bss)
- 强引用和弱引用:对于未定义的强引用,编译器直接报错;未定义的弱引用,编译器生成一个默认值(一般为空值)
符号决议有3条规则:
- 多个强符号:报错重复定义
- 一个强符号多个弱符号:选择强符号
- 多个弱符号:选择占用空间最大的弱符号(通过COMMON块)
重定位
修正引用符号的地址
相关问题
-
重复代码消除:C++ template,class,inline函数可以每个编译单元定义一次,需要消除重复代码;如果不消除,会有如下影响
- 空间浪费
- 地址出错:相同的函数指针会有不同的地址
- 降低cache命中率
-
函数级别链接:静态链接默认是以.o文件为基本单位进行段的合并,可以开启函数级别链接,降低无用空间消耗
-
全局构造和析构:.init保存了构造函数指令,.fini保存了析构函数指令
动态链接
动态链接库是什么
动态链接库 .so文件,是由多个.o文件链接生成的一种目标文件。生成动态链接库的过程如下:
- 对每个cpp文件生成地址无关代码,
gcc -shared -fPIC
- 编译生成 .so文件,
gcc -shared -fPIC *.o
动态链接过程
动态链接在编译的时候只是确定动态库中有需要的符号,并没有发生链接,具体的链接在程序运行中
地址无关代码
由于动态库的指令是多个程序共享的,所以我们不能修改指令中的地址。所以,我们抽象出一个新的段.got
,用于保存指令中的地址相关信息。
- 模块间数据访问
- 模块间函数调用
他们的数据都需要通过.got进行访问。一般而言,如果确定不被其他模块访问的数据或者代码,最好设置为static;这样明确指定了才不会通过.got访问。不然本模块访问也要通过.got中转,降低速度。
延迟绑定
由于动态链接把链接的过程延迟到了程序运行时,这样肯定会降低程序执行速度。为了减少链接的开销,动态链接库采用延迟绑定的方式。在需要这条指令地址的时候,才链接这个地址。.plt
段就是完成延迟绑定功能
动态链接相关的段
.interp
段
保存了ld.so
的地址
.dynamic
段
动态链接相关信息:各个段位置,所依赖的共享对象,共享对象搜索路径
- 动态字符串表
- 动态符号表
- .hash:加快字符串查找速度
- 动态重定位表:用于重定位
.got
和.got.plt
动态链接过程
- 动态链接器自举
- 装载共享库,生成全局符号表
- 执行
动态库和静态库中的符号冲突
如果我们链接多个库,这些库中有相同的名字,我们只选择先出现的那个符号加入全局符号表。
Linux系统装载
- 创建一个独立的虚拟地址空间
- 寻找
.interp
段,设置动态连接器路径 - 读取可执行文件头,建立虚拟地址空间与可执行文件的映射关系
- 初始化进程环境变量
- 将CPU指令寄存器设置为可执行文件入口,启动运行。静态链接是ELF中的入口地址,动态链接是动态链接器的地址
ELF映射
上面我们提到,需要见ELF文件中的数据段和代码段映射到虚拟地址空间。这里.bss段有点特殊,它和堆段合并,并没有在数据段里面。