目标文件:已经编译过,还没有链接
3.1 目标文件的格式
主流格式
现在PC平台流行的Executable File Format主要是COFF格式(Common File Format)包括
- windows下的PE-COFF(Portable Executable)
- Linux下ELF(Executable Linking File)
不太流行的
- Intel/Microsoft OMF (Object Module Format)
- Unix a.out
- MS-DOS .COM
DLL, lib
动态链接库DLL, Dynamic Linking Library和静态链接库Static Linking Library(静态链接库将许多目标文件打包并加上索引)都按照PE-COFF或ELF文件格式存储
ELF文件标准
文件类型 | 说明 | 实例 |
---|---|---|
Relocatable File 可重定位文件 | 1. 可以被用于链接成为共享目标文件或者可执行文件 2.包括静态链接库 | linux .o,.a win .obj,.lib |
Executable File 可执行文件 | 可执行的程序 | linux下可以没有拓展名,win .exe |
Shared Object File 共享目标文件 | 1. 可以被链接为新目标文件 2. 可以被动态链接器链接入可执行文件作为程序一部分 | linux .so win .dll |
Core Dump File核心转储文件 | 程序意外终止时可以用于保存地址空间的内容和其他信息 | Linux core dump |
目标文件小历史
a.out太过简单 COFF格式定义了段和调试数据格式
3.2 目标文件是什么样的
section 和segment在链接和装载时有细微区别
- data section主要存储初始化了的全局变量和局部静态变量
- code section主要存储代码
- BSS(Block Started by Symbol)主要存储了未初始化的全局变量和局部静态变量
- rodata段存放只读数据,主要是只读变量或者字符串常量
BSS历史
是UA-SAP汇编器中的一个伪指令,用于为符号预留一块内存空间
为何指令数据分离
- 可以分别映射为可读写和只读,保护程序数据
- 方便CPU,如缓存命中
- 程序代码共享
3.3 挖掘SimpleSection.o
3.3.1 代码段
- 汇编代码长度不一定
- 有些函数会被单独分段
3.3.2 数据段和只读数据段
- 有时编译器不会把字符串常量放在只读数据段
- 采用只读数据段可以贴合只读存储器比如ROM
3.3.3 BSS段
- 有些编译器会将未定义的未初始化变量放在bss段,有些则不会只是预留一个未定义的全局变量符号,等到最终链接为可执行文件再在bss段分配空间,这可能与强符号和弱符号,common块等有关
- 编译单元内部的静态变量的确存放在bss段
3.3.4 其他段
常用段名 | 说明 |
---|---|
.rodata1 | 同.rodata |
.comment | 存放编译器版本信息 |
.debug | debug |
.dynamic | 动态链接信息 |
.hash | 符号hash表 |
.line | 行号表 |
.note | 额外的编译器信息,比如程序公司名,发布版本号等 |
.strtab | string table |
.symtab | symbol table |
.shstrtab | section header string table |
.plt && .got | 动态链接的跳转表和全局入口表 |
.init && .fini | 程序初始化与终结代码段 |
自定义段
- 自定义段名不应该使用.作为前缀
- 可以使用objcopy将二进制文件作为程序的一个段
3.4 ELF文件结构描述
3.4.1 文件头
ELF文件头结构及相关参数被定义在/usr/include/elf.h中
typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT]; //Magic 16字节
Elf32_Half e_type; //elf文件类型
Elf32_Half e_machine; //CPU平台属性
Elf32_Word e_version; //ELF文件版本号,一般为1,ELF 文件最新版本为1.2
Elf32_Addr e_entry; /* Entry point 程序入口的虚拟地址,可重定位文件一般为0*/
Elf32_Off e_phoff; //start of program headers
Elf32_Off e_shoff; //start of section headers
Elf32_Word e_flags; //
Elf32_Half e_ehsize; //size of elf header
Elf32_Half e_phentsize; // size of program headers
Elf32_Half e_phnum; //num of program header
Elf32_Half e_shentsize; //size of section headers
Elf32_Half e_shnum; //num of section header
Elf32_Half e_shstrndx; //section header string table index
} Elf32_Ehdr; //此结构体一共52个字节
magic numbers
7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
del E L F class(0x01 32,0x02 64) endian version
==系统在加载时会确定魔数是否正确,不正确会停止加载==
文件类型
系统通过e_type而不是扩展名判断elf文件类型
3.4.2 段表
ELF段表数组的第一个元素是无效的段描述符,类型为NULL
typedef struct
{
Elf32_Word sh_name; /* 在section string table中的索引 */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset,如果该段存在于文件中,bss段offset无意义 */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Section Link */
Elf32_Word sh_info; /* Section information */
Elf32_Word sh_addralign; /* Section Address alignment 表示地址对齐数量的指数,2**sh_addralign,如果sh_addralign为0或1,则表示没有对齐要求 */
Elf32_Word sh_entsize; /* Section Entry Size,如果段包含固定大小的项,表示项的大小 */
} Elf32_Shdr;
sh_type
==段的名字只在链接和编译过程有意义,决定段的属性的是sh_type和sh_flags==
值 | 常量 | 含义 |
---|---|---|
0 | SHT_NULL | 无效段 |
1 | SHT_PROGBITS | 程序段代码段数据段等都是这种类型 |
2 | SHT_SYMTAB | 符号表 |
3 | SHT_STRTAB | 字符串表 |
4 | SHT_RELA | 重定位表 |
5 | SHT_HASH | 符号hash表 |
6 | SHT_DYNAMIC | 动态链接信息 |
7 | SHT_NOTE | 提示性信息 |
8 | SHT_NOBITS | 该段在文件中没有内容,bss |
9 | SHT_REL | 重定位信息 |
10 | SHT_SHLIB | 保留 |
11 | SHT_DNYSYM | 动态链接的符号表 |
sh_flags
值 | 常量 | 含义 |
---|---|---|
1 | SHF_WRITE | 可写 |
2 | SHF_ALLOC | 需要在进程空间中分配空间 |
3 | SHF_EXECINSTR | 可执行 |
常见表的属性
Name | sh_type | sh_flag |
---|---|---|
.bss | SH_NOBITS | SHF_ALLOC,SHF_WRITE |
.comment | SHT_PROGBITS | none |
.data | SHT_PROGBITS | SHF_ALLOC, SHF_WRITE |
.debug | SHT_PROGBITS | none |
.dynamic | SHT_DYNAMIC | SHF_ALLOC, SHF_WRITE? |
.hash | SHT_HASH | SHF_ALLOC |
.line | SHT_PROGBITS | none |
.note | SHT_NOTE | none |
.rodata | SHT_PROGBITS | SHF_ALLOC |
.shstrtab | SHT_STRTAB | none |
.strtab | SHT_STRTAB | 如果有可装载的段需要用到该字符串表,那么SHF_ALLOC |
.symtab | SHT_SYMTAB | 如果有可装载的段需要用到该字符串表,那么SHF_ALLOC |
.text | SHT_PROGBITS | SHF_ALLOC,SHF_EXECINSTR |
sh_info,sh_link
sh_type | sh_link | sh_info |
---|---|---|
SHT_DYNAMIC | 该段使用的字符串表在段表中的下标 | 0 |
SHT_HASH | 该段使用的符号表在段表中的下标 | 0 |
SHT_REL或SHT_RELA | 该段使用的相应符号表在段表中的下标 | 该重定位表所作用的段在段表中的下标 |
SHT_SYMTAB或SHT_DYNSYM | 操作系统相关 | 操作系统相关 |
other | SHN_UNDEF | 0 |
3.4.3 重定位表
代码段和数据段中对绝对地址的引用都需要重定位,每个需要重定位的表都有相应的重定位表段
sh_type为SHT_REL,sh_info表明其作用表的下标
3.4.4 字符串表
把字符串集中起来存放,然后使用字符串在表中的偏移来引用字符串
3.5 链接的接口-符号
- 函数和变量统称为Symbol,Symbol Name,Symbol Value
符号可能包含以下几种
- 变量 Symbol Value为地址
- 函数 value为地址
- 定义在本目标文件,可以被其他目标文件引用的全局符号
- 在本目标文件中引用
- 段名,value是该段的起始地址
- 局部符号,只在编译单元内部可见,这些局部符号对于链接器没有作用
- 行号信息
3.5.1 ELF符号表结构
typedef struct{
ELF32_Word st_name,//符号名在字符串表中的下标
ELF32_Addr st_value,//对应的值
ELF32_Word st_size,//符号大小,数据类型大小或者0(0或未知)
unsigned char st_info,//符号类型和绑定信息
unsigned char st_other,//0
ELF_Half st_shndx;//符号所在的段
}ELF32_Sym
st_info
==低4位表示Symbol Type,高28位表示Symbol Binding==
symbol binding
值 | 宏定义名 | 说明 |
---|---|---|
0 | STB_LOCAL | 局部 |
1 | STB_GLOBAL | 全局外部可见 |
2 | STB_WEAK | 弱引用 |
symbol type
值 | 宏定义名 | 说明 |
---|---|---|
0 | STT_NOTYPE | 未知 |
1 | STT_OBJECT | 数据对象 |
2 | STT_FUNC | 函数 |
3 | STT_SECTION | 表示一个段,该符号必须是STB_LOCAL的 |
4 | STT_FILE | 表示文件名,该符号必须是STB_LOCAL的,st_shndx一定是SHN_ABS |
st_shndx
如果符号定义在本目标文件中,则代表该符号所在段的下标
值 | 宏定义名 | 说明 |
---|---|---|
0xfff1 | SHN_ABS | 绝对的值 |
0xfff2 | SHN_COMMON | COMMON块类型符号 |
0 | SHN_UNDEF | 未定义,在其他目标文件中 |
st_value
- 可执行文件中,表示符号的虚拟地址
- 目标文件中,若不是COMMON块,表示相对于st_shndx的偏移
- COMMON块类型,表示对齐属性
3.5.2 特殊符号
有些符号被定义在==ld的链接脚本==中(只有使用ld链接才会存在)
__executable_start 程序起始地址
__etext或_etext或etext 代码段结束地址
_edata或edata 数据段结束地址
_end或end 程序结束地址
3.5.3 符号修饰与函数签名
- 20世纪70年代之前符号名与变量名或者函数名相同
- 为了防止不同语言的目标文件冲突,后来c语言符号名在前面加_,fortran在前后都加
- 现在gcc(可以通过-fleading-underscore来控制是否加下划线)在linux下不用加下划线,但是win下回家,win下的编译器也还加下划线
c++符号修饰Name Decoration,Name Mangling
gcc的修饰方法
_ZN1N1C4funcEi//N::C::func(int)
- _Z开头
- 嵌套的部分:N[len(name)][name]
- E结尾
- 函数参数列表跟在E后面,比如int就是i
也用来防止静态变量名称冲突
function signature
3.5.4 extern "C"
c++编译器将会把extern "C"大括号中的内容当做c语言代码处理
__cplusplus
c++编译器会在编译时默认定义这个宏,所以可以使用条件宏判断当前是不是c++
3.5.5 弱符号和强符号Weak Symbol,Strong Symbol
- 默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号
- 外部全局变量不是强符号也不是弱符号
选择规则
- 不允许强符号被多次定义
- 如果一个符号在某个目标文件中是强符号,其他文件中都是弱符号,那么选择强符号
- ==如果都是弱符号,选择占空间最大的,容易引发bug==
强引用和弱引用
strong ref:对外部目标文件的符号引用在链接时需要被正确决议,如果没有找到符号定义,链接器就会报错
weak ref:如果没有定义,则不报错,而是默认其为0或者是特殊值
__attribute((weakref)),gcc,设置对外部函数的引用为弱引用
作用
- 可以用于重定义库函数,或者链接拓展功能模块
- 可以使用弱引用来判断是否有-lpthread选项
int pthread_create(pthread_t *, const pthread_attr_t *, void * (*) (void *), void *) __attrbute__((weak));
int main(){
if(pthread_create){
puts("multi-thread");
}
}
3.6 调试信息
ELF文件采用DWARF(Debug With Arbitrary Record Format)标准的调试信息格式
命令和工具
file
查看相应文件格式
gcc -c
只编译不链接
objdump
-h 打印ELF文件中主要段的基本信息
-s 将段内容以16进制打印
-d 将所有包含指令的段反汇编
COTENTS属性表示该段在elf文件中实际存在
size
用于查看ELF文件text,data,bss段长度
readelf
详细查看elf文件
objcopy
objcopy -I binary -O elf32-i386 -B -i386 image.jpg image.o 可以将image.jpg作为单独的段加入image.o中
attribute((section("name")))
放在全局变量或者函数前面可以将其指定到对应name的段中
nm
用于查看elf文件的符号表
c++file
可以解析gcc被修饰的名称
UnDecorateSymbolName
解析visual c++被修饰的名称
extern "C"
c++编译器将会把extern "C"大括号中的内容当做c语言代码处理
__cplusplus
c++编译器会在编译时默认定义这个宏,所以可以使用条件宏判断当前是不是c++
__attribute((weakref))
gcc,设置对外部函数的引用为弱引用
strip
linux,去除elf文件调试信息