GCC、编译的过程大致可以分为:
预处理,编译,汇编,生成可重定位目标文件,链接,生成可执行文件六个步骤;下面我将一一解答。
一、预处理
预处理又叫做预编译,我们想了解的也就是这个阶段到底做了一些什么:
1、将所有的#define删除,并且展开所有的宏定义,也就是我们常说的宏替换;
2、处理所有条件的预编译指令,比如:"#if" "#ifdef" "#endif"等等;
3、处理#include的预编译指令,把包含的文件都插入到该预编译指令的位置;注意,这个过程是递归生成的,因为一个被包含文件中 可能还包含其他的文件;
4、删除所有的注释,就是我们常用的"//"和"/* */";
5、添加行号和文件名标识,比如#2 “hello.c” 2,方便调编译的时候显示行号信息,以及调试的时候显示错误还有警告的位置行号
6、 保留所有的#prgama 编译器指令,因为编译器要使用它们。
二:编译
这个阶段比较简单,大致包含四个方面:词法分析,语法分析,语义分析以及优化后生成的相应的汇编代码文件。
Linux下可以用 gcc - S hello.i -o hello.s
由于现在版本的gcc把预编译和编译合成一个部分,所以我们可以用 ccl hello.c一步完成
三:汇编
汇编就是将汇编语言变成机器语言
linux下用 gcc -c hello.s -o hello.o
四:生成可重定位目标文件
也就是所谓的obj文件
夹在ELF头和节头部表之间的都是节。一个典型的ELF可重定位目标文件包含下面几个节:
- .text:已编译程序的机器代码。
- .rodata:只读数据,比如printf语句中的格式串和开关(switch)语句的跳转表。
- .data:已初始化的全局C变量。局部C变量在运行时被保存在栈中,既不出现在.data中,也不出现在.bss节中。
- .bss:未初始化的全局C变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。目标文件格式区分初始化和未初始化变量是为了空间效率在:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。
- .symtab:一个符号表(symbol table),它存放在程序中被定义和引用的函数和全局变量的信息。一些程序员错误地认为必须通过-g选项来编译一个程序,得到符号表信息。实际上,每个可重定位目标文件在.symtab中都有一张符号表。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的表目。
- .rel.text:当链接噐把这个目标文件和其他文件结合时,.text节中的许多位置都需要修改。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面调用本地函数的指令则不需要修改。注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非使用者显式地指示链接器包含这些信息。
- .rel.data:被模块定义或引用的任何全局变量的信息。一般而言,任何已初始化全局变量的初始值是全局变量或者外部定义函数的地址都需要被修改。
- .debug:一个调试符号表,其有些表目是程序中定义的局部变量和类型定义,有些表目是程序中定义和引用的全局变量,有些是原始的C源文件。只有以-g选项调用编译驱动程序时,才会得到这张表。
- .line:原始C源程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译驱动程序时,才会得到这张表。
- .strtab:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以null结尾的字符串序列。
旁注:为什么未初始化的数据称为.bss?
用术语.bss来表示未初始化的数据是很普遍的。它起始于IBM 704汇编语言(大约在1957年)中”块存储开始(Block Storage Start)“指令的首字母缩写,并沿用至今。一个记住区分.data和.bss节的简单方法是把“bss”看成是“更好地节省空间(Better Save Space)!“的缩写。
链接包括两步:1、(1)合并.obj的各个段
(2)符号解析
(3)给符号分配虚拟地址
2、符号重定向
1、(1)合并.obj 的各个段
在生成可重定位目标文件之后,每个段都会生成多个。比如说:将文件中所有的.data 合并成一个.text合并成一个节。意思就是说将所有相同位置的都合并成一个
(2)符号解析
这是一个比较复杂的过程,简单来说就是找到每一个符号定义的位置。我们都知道,例如函数名。我们在一个文件中使用另一个文件的函数,必须要在前面声明函数名。但是我们要想用这个函数,就必须找到函数的定义处,也就是编写很函数的地方去用。所以这个符号解析的主要目的就是去找符号的定义处;另外,我们直接定义的符号对应于.obj的各个段,而引用的符号则对应于一个"UND"(undefine),在我们符号解析之后,所有的"UND"都不会再出现。
(3)给符号分配虚拟地址
从这里我们可以看出,在这之前,我们所有的步骤都没有给符号分配任何地址空间,所以所谓的编译,汇编等等阶段我们的符号是没有地址空间的。从一个试验中,我记得是给一个符号代码段分配了0xffffffXX的地址空间,我们知道,在虚拟地址空间里面,这些区域的地址属于内核区。可是我们用户定义的东西怎么会在内核区,显然这个地址也是错误的,我们可以理解为当时并没有给符号分配地址空间。所以我们只有在链接的第一阶段才给符号的代码段分配虚拟地址空间
2、符号重定向
有了上面分配虚拟地址空间的解释,这个过程就比较容易理解了,这个重定向就是重新分配虚拟地址空间,确切的说是第一次分配虚拟地址空间。也就是把上一个阶段分配的虚拟地址空间给到符号的虚拟地址栏。
六、生成可执行文件
未学完,待。。。。。。。