目标文件里面到底有什么(2)?

来源:互联网 发布:javascript 解析二维码 编辑:程序博客网 时间:2024/04/28 03:33

转自:http://blog.csdn.net/shenziheng1/article/details/52719218

前面对于目标文件只是做了概念上的阐述,如果不彻底深入目标文件的具体细节,这样的分析就是泛泛而谈没有意义,也没有深入的理解。就象知道TCP/IP协议是基于包的结构,但是从来没有看到过包的结构是什么样的(说得好像就是我),包的头部有哪些内容?目标地址和源地址是怎样存放的?如果不能细致且深入的去了解,就会步入一个误区:很多问题其实在表面上看似很简单,实际深入内部会发现很多鲜为人知的秘密,或者发现以前自己认为理所当然的东西居然是错误的!!!我需要做的,就是改变这个现状!

1.挖掘SimpleSection.o中的信息

[cpp] view plain copy
  1. <span style="font-size:18px;">/* 
  2.  *SimpleSection.c 
  3.  * 
  4.  *linux: 
  5.  *  gcc  -c  SimpleSection.c 
  6.  * 
  7.  *Windows: 
  8.  *  cl SimpleSection.c /c /Za 
  9.  */  
  10. int printf(const char* format, ...);  
  11. int global_init_var = 84;  
  12. int global_uninit_var;  
  13.   
  14. void func1(int i)  
  15. {  
  16.     printf("%d\n",i);  
  17. }  
  18. int main(void)  
  19. {  
  20.     static int static_var = 85;  
  21.     static int static_var2;  
  22.     int a = 1;  
  23.     int b;  
  24.       
  25.     func1(static_var + static_var2 + a + b);  
  26.     return a;  
  27. }</span>  
利用GCC对源文件进行编译可以得到目标文件:
[cpp] view plain copy
  1. <span style="font-size:18px;">$ gcc -c SimpleSection.c</span>  
查看目标文件中的内容如下:
从上面的结果中,我们能够看到:SimpleSection.o中的段的数量比我们想象中的要多,除了最基本的代码段、数据段和BSS段以外,还有3个段分别是只读数据段(.rodata)、注释信息段(.comment)、堆栈提示段(.note.GNU-stack)。先不考虑这三个额外的段。想了解一下关于段的几个重要的属性,首先最容易理解的就是段的长度(Size)和段所在的位置(File Offset),每个段中的第二行“CONTENTS”/"ALLOC"等表示的就是段的各种属性。“CONTENTS”表示该段在文件中是存在的。我们能够发现在BSS段中是没有“CONTENTS”的,这表示它实际上在EFL文件中根本不存在内容。所以,在EFL文件中实际存在的也就是“.text”、".data"、“.comment”、“.rodata”这4个段了。对于本项目,他们在ELF中的结构如下图所示:

代码段
将所有段的内容以十六进制的形式打印,并对所有包含指令的段进行反汇编,得到以下结果:


“Contents of Section  .text”就是.text的数据以十六进制方式打印出来的内容,总共0x5b字节,跟前面我们了解到的".text"段中的长度相一致。最左面一列指的是偏移量,中间四列指的是十六进制内容,最后一列是该段的ASCII码形式。
对照下面反汇编的结果,我们可以很明显的看到,.text段里所包含的正是SimpleSection.c里面两个函数func1()和main()函数的指令。
数据段与只读数据段
.data段保存的是那些已经初始化了的全局静态变量或局部静态变量。前面的SimpleSection.c代码中一共有两个这样变量,分别是global_init_var和static_var。这两个变量每一个都是4个字节,总共八个字节,所以“.data”这个段的大小为8个字节。
“.rodata”中只存放只读数据。本例中存放的就是字符串常量,刚好是4个字节。单独设立“.rodata”段有很多好处,不管是在语义上支持了C++的const关键字,而且操作系统自加载时可以将“.rodata”段的属性映射为只读,这样对于这个段的任何修改操作都会作为非法操作处理,大大保证了程序的安全性。
BSS段
.bss段存放的是未初始化的全局变量和局部静态变量。如示例代码中,global_uninit_var和static_var就是被存放在.bss段,其实更准确的说法是,.bss段为他们预留了空间。
其他段
除了.data、.text、.bss这3个最常用的段之外,ELF文件也有可能包含其他的段,用来保存与程序相关的其他信息。具体如下表所示:

这些段的名字都是由"."作为前缀,表示这些表的名字是系统保留的,应用程序也可以使用一些非系统保留的名字作为段名。如我们可以在ELF文件结构中插入一个“music”段,里面存放一首MP3,当ELF文件结构运行起来以后可以读取这个段并播放该首歌。但是,应用程序自定义的段名不能使用"."作为前缀,否则容易与系统保留段名冲突

2.ELF文件结构描述


其实,通过上面的SimpleSection.o实例,我们已经基本了解到了ELF文件的轮廓,下面我们可以正式地看一下ELF的文件总体结构。如下图所示:


ELF目标文件格式的最前部是ELF文件头(File Header),它包含了描述整个文件的基本属性,比如ELF文件版本、目标机器型号、程序入口地址等。紧接着是ELF文件各个段。其中,ELF文件中与段有关的重要结构就是段表(Section Header Table),该表描述了ELF文件中语段有关的所有段的信息,比如每个段的段名、段的长度、在文件中的偏移、读写权限以及其他属性。本处,仅想详细讨论一下符号表的作用,以为他和程序的链接有着很大的关联。
连接的接口——符号

链接过程的本质就是要把多个不同的目标文件之间相互关联到一起。为了使不同目标文件之间能够在相互粘合,这些目标文件之间必须有固定的规则才可以。在链接中,目标文件之间相互拼接实际上是目标文件地址间的相互引用,即对函数以及变量的地址进行引用。比如目标文件B要用到目标文件A中的函数“foo”,那么我们就成目标文件A定义了函数“foo”,称目标文件B引用了目标文件A中的函数“foo”。在链接中,我们将函数以及变量都通称为符号,函数名或者变量名就是符号名
我们可以将符号看作是链接中的黏合剂,整个连接过程正是基于符号才能正常地完成。链接过程中很重要的一环就是符号的管理,每一个目标文件都会有一个相应的符号表,这个表里面记载着目标文件中所有要用的到的符号,每一个定义的符号有一个对应的值,叫做符号值。对于变量以及函数而言,符号值就是他们的地址。除了函数和变量之外,也会存在其他几种不常用的符号,如下面所示:

3.调试信息

目标文件里面还有可能包含调试信息。几乎所有现代的编译器都支持源代码级的调试(比如我们可以在函数里面设置断点、可以监视变量变化、可以单步执行等),前提是编译器必须提前将源代码与目标代码之间的关系(比如目标代码中的地址对应源代码中哪一行?函数和变量的类型?结构体的定义?字符串保存到那个文件里面?)。设置有些高级的编译器或调试器支持查看STL容器中的相关内容。

原创粉丝点击