目标文件
什么是目标文件
源码经过编译和汇编器后生成的文件是目标文件,目标文件跟最终可执行文件很近似了,只不过是缺少了链接这一步,目标文件中少了些符号地址。
目标文件有三种类型,可执行文件,可重定向文件,共享目标文件。linux中的目标文件是ELF格式。
目标文件格式
目标文件中存有编译后的机器码,数据以及链接所需要一些信息,比如符号表,重定向表等。,目标文件中把这些信息按照不同的属性以段/节(section)的形式存储。段就是表示一个定长的区域。下图就是一个目标文件的大概格式。
ELF 头:主要描述生成文件的一些基本信息,段的位置和大小等等。
.text :存放已经编译好的机器码指令。
.rodata: 存放只读数据,比如被const定义等。
.data :已经初始化的全局和局部静态变量。局部变量是保存在栈中。
.bass :未初始化的全局和静态变量,或者已经初始化值为0的全局和静态变量。目标文件中不占有实际的空间。
.symtab: 符号表,存放程序中定义和引用的函数和全局变量信息。
.rel.text , .rel.data: 目标文件中某些部分需要重定位,即代码段和数据段中的绝对地址的引用位置是存在这块。
还存在其他的section,比如.debug,.line等等不过不是本次学习的重点,先可以省略。
Linux中objdump和readelf 都可以对目标文件信息进行查看,readelf更详细些。
readelf -S xxxx.o
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[Nr] Name Type Addr off
Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000027 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000001f0
0000000000000030 0000000000000018 I 9 1 8
[ 3] .data PROGBITS 0000000000000000 00000067
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 00000067
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .comment PROGBITS 0000000000000000 00000067
000000000000002e 0000000000000001 MS 0 0 1
[ 6] .note.GNU-stack PROGBITS 0000000000000000 00000095
0000000000000000 0000000000000000 0 0 1
[ 7] .eh_frame PROGBITS 0000000000000000 00000098
0000000000000038 0000000000000000 A 0 0 8
[ 8] .rela.eh_frame RELA 0000000000000000 00000220
0000000000000018 0000000000000018 I 9 7 8
[ 9] .symtab SYMTAB 0000000000000000 000000d0
0000000000000108 0000000000000018 10 8 8
[10] .strtab STRTAB 0000000000000000 000001d8
0000000000000016 0000000000000000 0 0 1
[11] .shstrtab STRTAB 0000000000000000 00000238
0000000000000059 0000000000000000
......
具体elf中的段格式先不进行展开说,虽然里面格式有些复杂不过不在本次学习范围内,如果有需要可以自行查书。
符号
什么是符号
在链接中函数和变量通称为符号,函数名和变量名被称为符号名 ,每个符号对应的地址 就是符号值.每一个目标文件中都有符号表,记录目标文件中用到的所有符号。
每个可重定位目标文件都有一个符号表。包含了模块定义和引用符号的一些信息。 在链接器中符号大致类型有三种:
- 定义在当前目标文件的全局符号,被其他目标文件引用。全局链接器符号对应
非静态的C函数和全局变量。 - 在当前目标文件中引用的其他文件里的全局符号。对应于定已在其他模块中的
非静态的C函数和全局变量。 - 当前目标文件定义和引用的局部符号。对应静态的c函数和静态全局变量,他们的作用于当前模块,而不被其他模块引用。
.symtab符号表不包含对应于本地非静态变量的任何符号,这些符号在运行时栈中管理。链接器并不感兴趣
符号表格式
readelf -s xxxx.o
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS SimpleSection.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 static_var.2351
7: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 static_var2.2352
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 6
11: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var
12: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM global_uninit_var
13: 0000000000000000 33 FUNC GLOBAL DEFAULT 1 func1
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
15: 0000000000000021 53 FUNC GLOBAL DEFAULT 1 main
上面展示出的符号表信息在linux中有对应的数据格式,在/usr/include/elf.h中。
Name:符号的名称,其中后面的数字是一种符号修饰,防止命名冲突。
Value : 在可执行文件中对应符号的虚拟地址;
在Ndx不是COM,表示符号在段中的偏移
在Ndx为COM,表示符号对齐。
Type:对应符号类型。
Bind:应绑定信息 ,如果Type是表示SECITON,那它一定是LOCAL.
Ndx :表示这个符号所在的段,数字就是对应所在的段位置(需要参照目标文件格式中段详情里面的 ”Nr“这一列),
除了数字,还会存在ABS,COM,UND这三种特殊定义,只有可重定向文件才会有的三个特殊段。
* ABS: 代表不需要被重定位的符号
* UNDEF: 代表在本目标文件中引用,但定义在其他地方
* COMMON: 还未被分配位置的未初始化数据目标,
只有可重定位目标文件中才有伪段
强弱符号和引用
编译器中默认函数和初始的全局变量是强符号,未初始化(包括被赋值为0的符号)的全局变量是弱符号。 Linux链接器使用下面规则来处理多重定义的符号名:
- 不允许有多个同名强符号。
- 如果一个强符号和多个弱符号同名,选择强符号。
- 如果多个弱符号同名,选择其中占用空间最大的一个。
弱符号机制允许同一个符号定义在多个文件中。但链接器本身不支持符号类型,所以如果出现同一个符号被定义成多个类型,链接器会选择占用空间最大的。
外部目标文件的符号引用在当前目标文件中最终被链接成为可执行文件,它们需要能够被正确的符号解析。如果链接器找不到对应的符号定义,则会直接抛出错误,这种被称为强引用。 弱引用的处理相对于强引用就不是那么直接抛出错误了,如果有定义则链接器将该符号引用解析,如果没有定义,链接器会默认它为0或者其他特殊符号。并不会抛出错误。
具体的强弱符号在连接中的处理方式在后面的文章中会详细说明。
资料引用
- 《深入理解计算机系统(第三版)》第七章
- 《程序员的自我修养链接、装载与库》第三章