现在PC平台流行的可执行文件格式,主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),它们都是COFF(Common file format)格式的变种。
目标文件就是源代码编译后但未进行链接的那些中间文件(Windows的.obj和Linux的.o),它跟可执行文件的内容与结构很相似,所以一般跟可执行文件格式采用一种格式存储。
动态链接库(Windows的.dll和Linux的.so)及静态链接库(Windows的.lib和Linux的.a)文件都按照可执行文件格式存储。
目标文件中的内容包含编译后的机器指令代码,数据。还包括了链接时所需要的一些信息,比如符号表,调试信息,字符串等。
目标文件将这些信息按不同的属性,以“节(Section)”的形式存储,有时候也叫做“段(Segment)”,它们表示一个一定长度的区域。
(1)代码段(.text)
程序源代码编译后的机器指令
(2)数据段(.data)
已初始化了的全局变量和局部静态变量数据
(3).bss段(.bss)
未初始化的全局变量和局部静态变量
注:
未初始化的全局变量和局部静态变量默认值都为0,本来也可以被放在.data段,但是在目标文件中为它们分配空间是没有必要的。因此,统一放到.bss段,.bss段大小为0,不出现在目标文件中。而变量名以及变量的大小,与其他变量一样,放在了符号表(.symtab段)中。
(4)其他段
除了.text,.data,.bss这3个最常用的段之外,目标文件也有可能包含其他的段,用来保存与程序相关的其他信息。
例如:.symtab符号表,.debug调试信息,.dynamic动态链接信息,等等。
(1)用GCC编译SimpleScriont.c得到了目标文件SimpleSection.o
$ gcc -c SimpleSection.c
(2)使用binutils的工具objdump来查看目标文件内部结构,-h显示各个段的基本信息
$ objdump -h SimpleSection.o
其中,Size表示段的长度,File off(File Offset)表示段所在的位置,
每个段第二行表示段的属性,例如,CONTENTS表示该段在文件中存在。
根据偏移地址我们就可以画出文件结构了。
我们看到,.bss段和.note.GNU-stack段在文件中都不存在。
(3)查看代码段
$ objdump -s -d SimpleSection.o
其中,
-s用于将所有段的内容以16进制的方式打印出来,
-d用于将所有包含指令的段反汇编。
段数据:
最左边一列是偏移量,中间4列是16进制内容,最右边一列是.text段的ASCII码形式。
反汇编结果:
对照反汇编结果,可以看到.text段里包含的正是SimpleSection.c里两个函数func1()和main()的机器指令。
(4)查看数据段
我们看到54000000(0x00000054)正好是全局变量global_init_var的值84,
55000000(0x00000055)正好是局部静态变量static_var的值85。
注:
.rodata段存放的是只读数据,一般是程序里面的只读变量和字符串常量。
(5).bss段
虽然在段的基本信息里,.bss段的大小为4字节,但是.bss段的数据为空,因此不占用目标文件的空间。
.bss段基本信息:
注:
程序中,未初始化的全局变量global_uninit_var和局部静态变量static_var2共占8字节空间,可是.bss段的大小只有4字节大小。原因是,有些编译器会将全局未初始化的变量放到.bss段,有些则不然,只是预留一个未定义的全局变量符号。因此,这里的4字节,指的是static_var2。
链接的本质,就是把不同的多个目标文件粘合到一起。
每个函数或变量都必须有自己独特的名字,才能避免在链接过程中产生混乱。
在链接中,函数和符号统称为符号(Symbol),函数名或变量名称为符号名(Symbol Name)。
每个目标文件都有一个相应的符号表(Symbol Table),记录了目标文件中用到的所有符号。每个符号都有一个对应的值,叫做符号值(Symbol Value),符号值可以是符号所对应的数据在段中的偏移量,也可以是该符号的对齐属性。
符号表保存在.symtab段中,信息如下:
第1列Num表示符号表数组的下标,第2列Value就是符号值,第3列Size为符号大小,第4列第5列分别为符号类型和绑定信息,第6列暂时忽略它,第7列Ndx表示该符号所属的段,最后一列,符号名称。
我们看到func和main的Ndx为1,表示代码段,因为.text在段表中的下标为1。
其中,段表信息如下:
printf这个符号,Ndx为UND(SHN_UNDEF),没有在SimpleSection.c中定义,是被引用的。
global_init_var是已初始化的全局变量,Ndx是3,定义在.data段,偏移量为0
static_var.1553是已初始化的局部静态变量,Ndx是3,定义在.data段,偏移量为4
global_uninit_var是未初始化的全局变量,Ndx是COM(SHN_COMMON)本身并没有在.bss段中。
static_var2.1534是未初始化的局部静态变量,Ndx是4,定义在.bss段,偏移量为0
对于类型为STT_SECTION的符号,名称并没有显示,它们是Ndx所示段的段名
SimpleSection.c这个符号表示编译单元的源文件名。