C 从源文件到可执行文件 共经过4个步骤 :预处理(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking)
1. 预处理
首先源代码文件(.c/.cpp)和相关头文件(.h/.hpp)被预处理器cpp预编译成.i文件(C++为.ii)。预处理命令为:
gcc –E hello.c –o hello.i
预编译过程主要处理那些源代码中以#开始的预编译指令,主要处理规则如下:
将所有的#define删除,并且展开所有的宏定义;处理所有条件编译指令,如#if,#ifdef等;处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。该过程递归进行,及被包含的文件可能还包含其他文件。删除所有的注释//和 /**/;添加行号和文件标识,如#2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号信息;保留所有的#pragma编译器指令,因为编译器须要使用它们。
2. 编译
编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件(.s)。编译的命令为:
gcc –S hello.i –o hello.s
或者从源文件直接输出汇编代码文件:
gcc –S hello.c –o hello.s
现在版本的GCC把预编译和编译两个步骤合并成一个步骤,由程序cc1来完成(C++为cc1plus)。
3. 汇编
汇编就是将汇编代码转变成机器可以执行的命令,生成目标文件(.o),汇编器as根据汇编指令和机器指令的对照表一一翻译即可完成。
汇编的命令为:
gcc –c hello.s –o hello.o
或者从源文件直接输出目标文件:
gcc –c hello.c –o hello.o
4. 链接
链接就是链接器ld将各个目标文件组装在一起,解决符号依赖,库依赖关系,并生成可执行文件。链接的命令为:
ld –static crt1.o crti.o crtbeginT.o hello.o –start-group –lgcc –lgcc_eh –lc-end-group crtend.o crtn.o
一般我们使用一条命令就可以完成上述4个步骤:gcc hello.c
实际上gcc只是一些其它程序的包装,它会根据不同参数去调用预编译编译程序cc1、汇编器as、链接器ld。
目标文件的section说明:
段名
说明
.text
存放编译后的机器指令
.data
存放已初始化的全局静态变量和局部静态变量
.rodata
存放只读数据,如全局const变量、字符串常量
.bss
存放未初始化的全局静态变量和局部静态变量
.symtab
符号表,记录符号信息
.rel.xxx
重定位表,记录.xxx段中需要重定位定位符号
链接过程的本质就是要把多个不同目标文件按照section归类并粘合成为一个整体,目标文件之间相互拼合实际上是目标文件之间对地址的引用,即对函数和变量的地址的引用。在链接中,我们将函数和变量统称为符号(Symbol),函数名和变量名就是符号名(Symbol Name),我们可以将符号看做是链接中的粘合剂,整个链接过程正是基于符号才能够正确完成。每个目标文件都会有一个符号表(Symbol Table),即上图的.symtab段,这个表里记录了目标文件所用到的所有符号。每个定义的符号有一个对应的值,叫做符号值(Symbol Value),对于变量和函数来说,符号值就是它们的地址。
(静态链接)链接器一般采用一种叫做两步链接的方法:
空间与地址分配。链接器扫描所有的输入目标文件,将它们的段进行合并,计算出输出文件中各个段合并后的长度和位置,建立映射关系;并且将输入目标文件的符号表中的所有符号定义和符号引用收集起来,统一放到一个全局的符号表。符号解析和重定位。使用上一步收集到的所有信息,读取输入文件中段的数据、重定位信息,并且进行符号解析与重定位、调整代码中的位置等,这一步是链接过程的核心。 在静态链接中,除了链接源代码生成的目标文件,还需要链接其它静态库,如C语言静态库libc。其实静态库可以简单地看成是一组目标文件的集合,即很多目标文件经过压缩打包后形成的一个文件。链接器在链接静态库的时候是以目标文件为单位的,只有引用了静态库中某个目标文件中定义的符号,才会把改目标文件链进来。
动态链接基本上分为3步:
动态链接器自举。当操作系统将进程控制权交给动态链接器时,动态链接器的自举代码开始执行。自举代码获得动态链接器本身的重定位表和符号表,将它们重定位后,才可以使用自己的全局变量和静态变量。装载共享对象。完成自举以后,动态链接器将可执行文件和链接器本身的符号表都合并到一个全局符号表。然后链接器开始寻找可执行文件所依赖的共享对象,并将这些共享对象的名字放入到一个装载集合中。链接器开始从集合里取一个所需要的共享对象的名字,打开相应的文件并读取ELF文件头和.dynamic段,然后将它对应的代码段和数据段映射到进程空间。如果这个ELF共享对象还依赖于其他的共享对象,那么将所依赖的共享对象放入到装载集合中。如此循环知道所有依赖的共享对象都被装载进来为止。重定位和初始化。链接器开始重新遍历可执行文件和每个共享对象的重定位表,将他们的GOT中每个需要重定位的位置进行修正。重定位完成之后如果某个共享对象有.init段,那么动态链接器就会执行.init段中的代码,用以实现共享对象特有的初始化过程,比如共享对象中的C++全局/静态对象的构造。 动态链接还有一种更加灵活的模块加载方式,叫做显式运行时链接,也就是让程序自己在运行时控制加载指定的模块,并且可以在不需要该模块时将其卸载,这种共享对象往往被称为动态装载库,可以用来实现诸如插件、驱动等功能。 —————————————————————————————————————————————————————————————————— 至于链接脚本.ld http://blog.csdn.net/skyflying2012/article/details/9295951 另外,根据一段时间以来的工作经验,Andes平台提供了一种很好的sag->ld的机制,很大程度上提高了生产力。 只需根据程序实现架构完成.sag文件,而后平台会根据.sag文件自动生成.ld Pref: (www.eepw.com.cn/article/263192.htm http://www.eepw.com.cn/article/266369.htm?qxk)参考:
http://www.cnblogs.com/glacierh/p/4678229.html?utm_source=tuicool&utm_medium=referral
http://blog.csdn.net/zvvzxzko2006/article/details/48519845(section表)
http://www.eetop.cn/blog/html/14/56214-43328.html
http://www.cnblogs.com/kekec/p/3238741.html(编译链接的举例说明)
http://blog.csdn.net/dlfer11/article/details/50351676(链接器的说明)
