1.可重定位目标文件(.o) 每个 .o 文件都是由对应的 .c 文件通过编译器和汇编器生成的,包含二进制代码数据。可以在编译时通过与其他可重定位目标文件(.o)链接起来,创建一个可执行目标文件(a.out). $ gcc -c test.c
2.可执行目标文件(a.out) 可执行目标文件由链接器生成,包含二进制代码和数据,可以被直接复制到内存并执行。 $gcc -o test test.c
3.共享目标文件(.so) 一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载进内存并链接(动态链接)。
1.可重定位目标文件格式
ELF头:包含16字节标识信息,文件类型(.o, exec, .so),机器类型,节头表的偏移,节头表的表项大小以及表项个数。 .text:已编译程序的机器代码(二进制指令)。(代码区) .rodata:只读数据,比如printf 格式串,switch跳转表等。 .data:已初始化的全局和静态变量。局部变量在运行时被保存在栈中,既不出现在.data中,也不出现在.bss节中。 .bss:未初始化的全局和静态变量。仅仅是占位符,不占据任何实际的磁盘空间。 .symtab:一个符号表(symbol table),它存放在程序中被定义和引用的函数和全局变量的信息。不包含局部变量的表目。 .rel.text:.text节的重定位信息,用于重新修改代码段的指令中的地址信息。 .rel.data:.data的重定位信息,用于对被模块使用的或定义的全局变量进行重定位信息。 .debug:一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C文件(gcc -g)。 .strtab:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以null结尾的字符串序列。 .line:原始C程序中的行号和.text节中机器指令之间的映射。 节头表 :每个节的节名和偏移量。
我们可以通过readelf来看一看可重定位目标文件的ELF头包含的信息。先整一段简单的代码
//main.o int sum(int *a,int n); int array[2]={1,2}; int mian() { int val=sum(array,2); return val; }2.ELF头 我们可以用 readelf -h 命令 来ELF头信息
说明
Magic: 魔数,用来指名该文件是一个 ELF 目标文件。第一个字节 7F 是个固定的数;后面的 3 个字节正是 E, L, F 三个字母的 ASCII 形式。从中我们可以看到基本信息,例如 文件类型,数据格式,ELF头版本号,操作系统类型(OS/ABI),ELF文件类型(这里是 可重定位目标文件),机器平台类型和目标文件的版本号 等等。可重定位文件不会进行执行,没有程序头表,所以:程序头起点,程序头大小,Number of program headers(程序头表中表项个数)皆为0。Start of section headers:节头表的偏移量。(本例中720的16进制表示为2d0,所以节头部表从0x2d0的位置开始。)该例节头表有12个节头,每个节头64字节,即节头表总大小为64*12字节字符串表索引节头:节头部表中字符串表(strtab)的索引值。3.节头表
我们可以用 readelf -S 命令 来查看节头表信息
说明
总共有12个表项每个表项64个字节,因为是链接视图,所以对应虚拟地址全为零。在ELF头中给出一个字段是指出节头标的起始位置,这里是0X2d0。就是在指令下面那一行。只有.text , .data , .bss , .rodata会分配存储空间。ELF头占64字节即0x40字节,所以.text的偏移值为0x40加上他的大小即0x21可得其后的偏移值。节头表中对所有节的起始位置和大小都记录了下来。4.节的分布 现在, 通过ELF头中节头表的信息,以及节头表中各表项里偏移和大小的值,我们可以画出文件中各节的分布 右侧数字表示节的大小,左侧数字表示偏移。(都是用16进制表示的) 节头表大小在ELF头中计算出来。
5.符号表 符号表保存了程序实现或使用的所有全局变量和函数。 说明
Value的值是符号的地址。Ndx: index,到节头部表的索引。 (UNDEF表示未定义的符号,ABS表示不该被重定义的符号,COMMON表示未被分配位置未初始化的符号)