整个过程Preprocessor:将.c文件转换成.i文件,使用的gcc命令为:gcc–E,对应预处理命令cpp;编译器:转换.c/。h文件转换为.s文件,使用的gcc命令为:gcc–S,对应编译命令cc–S;assembler:将.s文件转换成.o文件,使用的gcc命令为:gcc–c,对应的汇编命令为as;链接器:将.o文件转换为可执行程序,使用的gcc命令为:gcc,对应的链接命令为ld;loader:将可执行程序加载到内存中并执行,loader和ld-linux.so。详细过程1.预编译在正式编译阶段之前进行。预处理阶段根据放置在文件中的预处理指令修改源文件的内容。宏定义指令,比如#defineab这类指令,预编译需要做的是将程序中的a全部替换为b,但作为字符串常量的a并没有被替换。还有#undef,会取消某个宏的定义,这样以后出现这个字符串就不会被替换了。条件编译指令,如#ifdef、#ifndef、#else、#elif、#endif等。这些伪指令的引入允许程序员通过定义不同的宏来确定编译器处理哪些代码。预编译器会根据相关文件过滤掉那些不需要的代码。头文件包含指令,如#include"FileName"或#include等。该指令将头文件中的所有定义添加到它生成的输出文件中,供编译器处理。特殊符号,预编译器可以识别一些特殊符号。例如,源程序中出现的LINE标志将被解释为当前行号(十进制数),而FILE将被解释为当前编译的C源程序的名称。预编译器会将源程序中出现的这些字符串替换为合适的值。头文件的作用主要是为了让一些定义对多个不同的C源程序可用,这就涉及到头文件的位置,即搜索路径问题。头文件查找规则如下:所有头文件查找都会从-I开始查找环境变量C_INCLUDE_PATH、CPLUS_INCLUDE_PATH、OBJC_INCLUDE_PATH指定的路径然后查找默认目录(/usr/include、/usr/local/include,/usr/lib/gcc-lib/i386-linux/2.95.2/include……)2.编译通过词法分析和语法分析,确认所有指令符合语法规则后,translatethemintoequivalentintermediatecodes表示或汇编代码。3.汇编汇编程序(as)将汇编语言代码翻译成目标机器指令(.o)。对象文件中存放的是相当于源程序的对象的机器语言代码。目标文件由节组成。通常一个目标文件中至少有两个段:代码段:该段主要包含程序指令。该部分通常是可读和可执行的,但通常是不可写的。数据段:主要存放程序中要用到的各种全局变量或静态数据。一般数据段是可读、可写和可执行的。4.链接将相关目标文件相互连接起来,生成可加载、可执行的目标文件。链接器的核心工作是符号表的解析和重定位。4.1何时链接编译时,就是将源代码编译成机器码的时候(静态链接器负责);加载的时候,就是程序加载到内存的时候(加载器负责);在运行时,它由应用程序实现(动态链接器负责)。4.2链接的作用使得分离编译成为可能;动态绑定(binding):分离定义、实现、使用4.3静态库搜索路径gcc首先从-L搜索;然后找到环境变量LIBRARY_PATH指定的搜索路径;然后找到默认目录/lib/usr/lib/usr/local/lib这个是在编译gcc的时候写在程序里的。4.4动态库搜索路径编译目标代码时指定的动态库搜索路径-L;环境变量LD_LIBRARY_PATH指定的动态库搜索路径;配置文件/etc/ld.so.conf中指定的动态库搜索路径;默认动态库搜索路径/lib/usr/lib//usr/local/lib4.5静态链接(编译时)链接器将函数代码从其位置(在目标文件或静态链接库中)复制到最终的可执行程序。这样,程序执行时,这些代码就会被加载到进程的虚拟地址空间中。静态链接库实际上是目标文件的集合,每个目标文件都包含库中一个或一组相关函数的代码。为了创建一个可执行文件,链接器必须完成主要任务:符号解析:关联目标文件中符号的定义和引用;重定位:将符号定义与内存地址相关联,然后修改对该符号的所有引用。重定位可以让我们结合具体的CPU指令来理解这个过程。假设我们有一个名为var的全局变量,它在目标文件A中,我们需要在目标文件B中访问这个全局变量。比如我们在目标文件B中有这样一条指令:movl$0x2a,var这条指令就是给var变量赋值0x2a,相当于C语言中的语句var=42。然后我们编译目标文件B,得到这条指令的机器码。由于编译器在编译目标文件B时并不知道变量var的目标地址,所以编译器在无法确定地址时就会使用这条mov指令。文件的目标地址设置为0,链接器在链接目标文件A和B时会修复它。假设A和B链接后,变量var的地址确定为0x1000,那么链接器会将这条指令的目标地址部分修改为0x10000。这个地址修正过程也称为重定位(Relocation),每一个需要修正的地方称为一个重定位条目(RelocationEntry)。每个目标文件除了自身的数据和二进制代码外,还提供了三张表:未解析符号表、导出符号表和地址重定向表。unresolvedsymboltable提供了编译单元中引用但定义不在本编译单元中的所有符号及其出现的地址;导出符号表提供了本编译单元已经定义并愿意提供给其他单元的符号。它的地址;地址重定向表记录了本编译单元中所有对自身地址的引用;编译器将extern声明的变量放入未解析的符号表中,而不是导出的符号表中;----外部链接编译编译器不会将static声明的全局变量放入unresolvedsymboltable,也不会放入exportsymboltable,所以不能使用其他单元;----内部链接及其功能的普通变化放入导出符号表;4.6动态链接(加载、运行时)在这种模式下,函数的定义在动态链接库或共享对象的目标文件中。在编译的链接阶段,动态链接库只提供符号表和少量其他信息,以确保所有符号引用都已定义,编译顺利通过。动态链接器(ld-linux.so)链接器在运行时根据记录的共享库符号定义动态加载共享库,然后完成重定位。当这个可执行文件被执行时,动态链接库的全部内容将在运行时被映射到相应进程的虚拟地址空间。动态链接器会根据可执行程序中记录的信息找到相应的函数代码。各种文件ELFExecutableELFexecuable文件内容文件头描述文件属性、段表、重定位表代码段.code.text源代码编译后的机器指令、程序指令数据段.data初始化全局变量、局部静态变量.bss未初始化全局变量、局部静态变量.symtab存储程序符号表中定义和引用的函数和全局变量的信息目标文件可重定位(Relocatable)文件:由编译器和汇编程序生成,可与其他可重定位目标文件合并创建可执行文件或共享文件目标文件;共享(Shared)目标文件:一类特殊的可重定位目标文件,可以在链接(静态共享库)或加载或运行(动态共享库)时动态加载到内存并执行;可执行(Executable)文件:由链接器生成,可以被加载器直接加载到内存中作为进程执行的文件。静态库(ArchiveFIle)是多个.o文件的集合。Linux中默认的后缀是.a,静态库的.o是不链接的,只是.o的集合。共享目标文件包含代码和数据,可以在两种情况下使用。链接器可以使用该文件与其他可重定位文件和共享目标文件进行链接,以生成新的目标文件。动态链接器可以组合几个这样的文件。共享目录文件与可执行文件相结合,以作为过程映像的一部分运行GNU工具。gnu下提供了很多工具来帮助处理目标文件:AR:创建静态库,插入、删除、列出和提取成员;STRINGS:列出目标文件中所有可打印的字符串;STRIP:从目标文件中删除符号表信息;NM:列出目标文件符号表中定义的符号;SIZE:列出目标文件中段的名称和大小;READELF:显示目标文件的完整结构,包括ELF头中编码的所有信息。OBJDUMP:显示目标文件的所有信息,最有用的功能是反汇编.text段中的二进制指令。LDD:列出可执行文件在运行时所需的共享库。
