目标文件
源代码经过编译器编译后生成的文件,是已经编译后的可执行文件格式,只是还没有经过链接,与真正的可执行文件在结构上稍有不同。
目标文件在不同的段中保存代码、数据和辅助信息
ELF 文件结构
- 文件头
包含了描述整个文件的基本属性ELF
文件版本- 目标机器型号
- 程序入口地址
- 段表的位置、长度和段的数量
- 各个段
应用程序可以自定义段,段名以.
作为前缀的是系统保留段.text
保存代码.code
保存程序的指令.data
保存程序中已初始化的全局变量和静态变量.bss
未初始化的全局变量和静态变量,并没有内容,在文件中不占据空间.rodata
保存只读数据.debug
存放调试信息,调试信息在目标文件和可执行文件中占用很大的空间,发布时需去掉
- 段表(Section Header Table)
描述了ELF
文件所包含的所有段的信息,段的结构是由段表决定的- 段表内容
- 段名
- 段长
- 文件中的偏移
- 读写权限
- 段的其他属性
- 重定位表
同时也是 ELF 的一个段- 每个在链接时需要重定位的代码段和数据段,都有一个相对应的重定位表
- 字符串表
同时也是 ELF 的一个段 - 符号表 (Symbol Table/.symtab)
- 符号名
函数名或变量名就是符号名 - 符号值
函数或变量的地址 - 符号大小
- 符号所在段
- 符号类型和绑定信息
- 符号类型
- 未知类型
- 数据对象
变量或数组等 - 函数或可执行代码
- 段
- 文件名
该目标文件所对应的的源文件名
- 绑定信息
- 局部符号
- 全局符号
- 弱引用
- 符号类型
- 符号名
- 段表内容
链接的接口——符号
链接过程中,目标文件之间的连接就是对符号(函数和变量)的地址的引用
链接过程值关心全局符号的相互粘合,其他符号对于其他目标文件来说是不可见的,在链接过程中无关紧要。
特殊符号
链接器链接可执行文件时,会定义许多特殊的符号,我们可以在程序中直接声明并引用
部分特殊符号:
__executable_start
__etext/_etext/etext
_edata/edata
_end/end
符号修饰与函数签名
强符号和弱符号
强符号和弱符号是针对定义来说的
- 强符号
函数和初始化的全局变量为强符号 - 弱符号
未初始化的全局变量为弱符号 - 处理规则
- 不允许强符号被定义多次
- 一个符号在某个目标文件中是强符号,其他文件中是弱符号,链接器会选择强符号
- 一个符号在所有目标文件中都是弱符号,链接器选择占用空间最大的一个
- 强引用和弱引用
- 强引用
对外部目标文件的符号引用,如果未找到定义,链接器报错 - 弱引用
对于未定义的弱引用,链接器不报错
- 强引用
- 作用
弱符号和弱引用对于库来说十分有用,库定义的弱符号可以被用户定义的强符号覆盖,或将扩展功能模块的引用定义为弱引用,扩展模块和程序链接一起时,功能可以使用,去掉功能模块,程序也可以正常链接,只是少了相应的功能,使程序功能组合更方便
可执行文件格式
可执行文件格式 PE
(Windows)和 ELF
(Linux)都是 COFF
格式的变种,目标文件可以看成是可执行文件的一种
-
可执行文件
EXE
和ELF
-
动态链接库
.dll
和.so
-
静态链接库
.lib
和.a
静态链接库可以理解为包含很多目标文件的文件包
为什么把程序指令和数据分开存放?
源代码被编译后主要分为两种段:程序指令和程序数据
- 分权限,数据域可读写,指令域只读,防止程序指令被改写
- 因为现代 CPU 的缓存被设计为数据和指令缓存分离,提高缓存的命中率
- (最重要)当运行多个该程序的副本时,只需要保存一份该程序的指令域,只读数据也是可以共享的,节省大量的内存。