现在PC平台流行的可执行文件格式(Executable)主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),它们都是通用对象文件格式(Common Object File Format,COFF)的变种。目标文件就是源代码编译后但未进行链接的那些中间文件(Windows的.obj和Linux下的.o),它跟可执行文件的内容与结构很相似,所以一般跟可执行文件格式一起采用一种格式存储。在Windows下称为PE-COFF文件格式;在Linux下,我们可以将它们称为ELF文件。
不光是可执行文件(Windows的.exe和Linux下的ELF可执行文件)按照可执行文件格式存储。动态链接库(Dynamic Linking Library)(Windows的.dll和Linux的.so)及静态链接库(Static Linking Library)(Windows的.lib和Linux的.a)文件都按照可执行文件格式存储。ELF文件标准里面把系统中采用的ELF格式的文件归为如表1所列举的4类。
ELF文件类型 | 说明 | 案例 |
可重定位文件 (Relocatable File) | 这类文件包含了代码和数据,可以被用来链接成可执行文件或者共享目标文件,静态链接库也可以归为这一类 | Linux的.o Window的.obj |
可执行文件 (Executable File) | 这类文件包含了可以执行的程序,它的代表就是ELF可执行文件,它们一般都没有扩展名 | 比如/bin/bash 文件 Windows的.exe |
共享目标文件 (Share Object File) | 这种文件包含了代码和数据,可以在以下两种情况下使用。一种是链接器可以使用这种文件跟其他的可重定位文件和共享目标文件链接,产生新的目标文件。第二种是动态链接器可以将几个这种共享文件与可执行文件结合,作为进程映像的一部分来运行。 | Linux的.so Windows的.dll |
核心转储文件 (Core Dump File) | 当进行意外终止时,系统可以将进程的地址空间的内容及终止时的一些其他信息转储到核心转储文件。 | Linux下的core dump |
表1
目标文件中的内容至少有编译后的机器指令代码、数据,除了这些,还有符号表、调试信息、字符串等,这些都是链接时所需要的一些信息。一般目标文件将这些信息按照不同的属性,以“节”(Section)的形式存储,有时候也叫“段”(Segment),在一般情况下,它们都表示一个一定长度的区域,基本上不加以区别,唯一的区别是在ELF的链接视图和装载视图的时候。一般情况下,统一将它们称为“段”。
下面我们以一个SimpleSection.c文件为例:
/*
*SimpleSection.c
*Linux:
* gcc -c SimpleSection.c
*Windows:
* c1 SimpleSection.c /c /Za
*/
int printf(const char * format,...);
int global_init_var = 84;
int global_uninit_var;
void func1(int i)
{
printf("%d\n",i);
}
int main(void)
{
static int static_var = 85;
static int static_var2;
int a = 1;
int b;
func1(static_var+static_var2+a+b);
return a;
}
objdump –h SimpleSection.o 参数“-h”就是把ELF文件的各个段的基本信息打印出来,如图1所示。从图1中可以看出,除了最基本的.text(代码段)、.data(数据段)和.bss(BSS段)外,还有.rodata(数据段)、.comment(注释信息段)、.note.GNU-stack(堆栈提示段)。段的Size、file offset很容易理解,每个段的第2行中的“CONTENTS”、“ALLOC”等表示段的各种属性,“CONTENTS”表示该段在文件中存在。我们可以看到BSS段没有“CONTENTS”属性,表示它实际上在ELF文件中不存在内容。“.note.GNU-stack”段虽然有“CONTENTS”,但它的长度为0。
图1
使用size命令,可以查看ELF文件中的代码段、数据段和BSS段的长度,如图2所示。
图2
代码段
使用命令objdump –s 可以将所有段的内容以十六进制的方式打印出来,“-d”将所有包含指令的段反汇编,如图3所示。
图3
数据段和只读数据段
.data段保存的是那些已经初始化了的全局静态变量和局部静态变量。
.rodata段存放的是只读数据,一般是程序里面的只读变量和字符串变量。另外,有时候编译器把字符串常量放到“.data”段中。
BSS段
.bss段存放的是未初始化的全局变量和局部静态变量,其实更准确的说法是.bss段为它们预留了空间。
其他段
自定义段
正常情况下,GCC编译出来的目标文件中,代码会被放到“.text”段,全局变量和静态变量会被放到“.data”和“.bss”段。GCC提供了一个拓展机制,可以指定变量所处的段。
__attribute__((section("FOO"))) int global = 42;
__attribute__((section("BAR"))) void foo()
{
}
我们在全局变量或者函数之前加上“__attribute__((section("name"))) ”属性就可以把相应的变量或函数放到以“name”作为段名的段中。