受《CSAPP》P453启发,想实际的看看ELF文件的内容,所以做了简单的尝试,希望不虚此行。
采用的程序demo是:
swap.c
extern int buf[]; int *bufp0 = &buf[0]; int *bufp1; void swap() { int temp; bufp1 = &buf[1]; temp = *bufp0; *bufp0 = *bufp1; *bufp1 = temp; }
main.c
#include <stdio.h> void swap(); int buf[2] = {1, 2}; static int foo = 99; int f(){ static int x = 1; return x; } int g(){ static int x = 2; return x; } int main() { swap(); printf("Hello World.\n"); return 0; }
下面通过命令readelf命令来探寻ELF内部。
?
可以看到ELF头的一些信息:采用补码,小段序;目标文件的类型是可重定位目标文件;机器类型是Intel80386;ELF header的大小是52B;有30个section header,共40B;等等。
可以看到ELF中各个section的索引号,起始地址,大小,标志字段。只读数据段15,已初始化全局变量24,未初始化全局变量25,符号表28,字符串表29,代码段13.(其他的现在还不懂)。接下来结合上面的代码看符号表信息。
Ndx列表明每个符号所在的段,其中有三个伪段(pseudo section):ABS表示不该被重定位的符号,UNDEF表示在本模块引用,却在其他模块定义,COMMON表示还未分配位置的未初始化对象。
main.c, swap.c代表源文件名,类型是ABS。
f, g是在main.c中定义的俩函数,type=FUNC, bind=GLOBAL, 表示全局函数,所在的section是13(.text),起始地址分别为80483d4,de,大小都是10B.
变量foo, buf都是初始化的全局变量,在24号段(.data)中,大小分别是4,8B,不同在于foo有static修饰,所以bind=LOCAL,对于buf0,buf1也是类似情况.
特别值得关注的是定义在函数f, g中的有static修饰的同名局部变量x,在符号表中有唯一的local linker symbols:x.1688, x.1691都在全局初始化段中,bind=LOCAL.
接下来看swap.o的ELF信息:
可以看到类型是可重定位目标文件。(REL)
与前面readelf -a a.out很大不同的地方在于这里多了一些.rel.text .rel.data等可重定位的section,这是有目标文件的类型决定的,可重定位目标文件存在的意义就是被链接到其他目标文件中,构建可执行目标文件,所以就需要在ELF中表明哪些符号需要进行地址的修改。调用外部函数或者引用全局变量的指令都需要修改,本地函数调用是相对地址,所以不需要修改。上述代码段位置列表在这里显然表示的就是swap()里面需要重定位的对象,其中操纵了1次buf符号,3次bufp1,
2次bufp0, 所以出现了对应的条目,并且根据偏移量可以更好的理解。在重定位数据段中就只有我们引用的外部变量buf。
在swap.o的符号表中,bu所在的section是UND Type=NOTYPE,表明其是extern的。bufp0是以初始化的全局变量,32位系统指针大小是4B,所在的section number是3(.data).特别要注意的是bufp1是未初始化未分配位置的对象,所以Ndx=COM, value值指明的是对齐要求。
总结:通过以上的分析可以更清晰的理解可执行文件的内存布局, 不同对象所在的section,从而指导自己的程序设计。