C/C++程序编译过程分析

14 篇文章 0 订阅

1、编译过程介绍

1)gcc编译器编译过程如下:

上图中的所有步骤并不是gcc编译器全部完成,gcc只是完成编译,当然你也可以使用C编译器(ccl)来编译。对于预处理阶段,是gcc调用C预处理器(CPP)来完成的,得到hello.i文件。对于将汇编文件翻译成可重定位目标文件(二进制文件)是gcc调用汇编器(as)完成的,得到hello.o文件。这就是单个源文件翻译过程。生成可执行文件是gcc调用链接器(ld)完成的,链接器将多个可重定位目标文件链接成可执行目标文件(没有限制一定是a.out,只是从贝尔实验室诞生的第一个Unix系统使用的是a.out格式,至今为止,可执行文件仍然被称为a.out文件)。

2)就像上面提到的,多个源文件是先将单个源文件分别翻译成可重定位目标文件(.o文件),然后链接成可执行目标文件。这里将使用一个示例来展示编译过程,包含两个源文件main.c和sum.c。其编译成可执行目标文件过程如下图:

                                 

main.c代码如下:

 1 #include <stdio.h>                                                                                          
 2 #include <stdlib.h>
 3 
 4  
 5 int sum(int *a, int n);
 6  
 7 int array[2] = {1, 2};
 8  
 9 int main(int argc, char* argv[])
 10 {
 11     int val = sum(array, 2);
 12  
 13     return val;
 14 }

 

sum.c代码如下:

1                                                                                             
2  
3 int sum(int *a, int n)
4 {
5     int i, s = 0;
6  
7     for(i = 0; i < n; i++)
8     {
9         s += a[i];
10    }
11  
12    return s;
13 }

将mian.c编译成main.o过程如下:

预处理:cpp mian.c mian.i    或者 gcc -E main.c

编译:ccl main.i -o main.s     或者gcc -S main.c

翻译成可执行目标文件: as main.s -o main.o   或者 gcc main.c -o mian.o

生成可执行文件:ld  mian.o sum.o -o prog     或者 gcc main.c sum.c -o prog

2、目标文件介绍

1)目标文件有三种形式:

   (1)可重定位目标文件:包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。

   (2)可执行目标文件:包含二进制代码和数据,其形式可以直接被赋值到内存并执行。

   (3)共享目标文件:一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载进内存并链接。

2)目标文件地文件格式:windows使用地是可移植可执行(Portable Executable,PE)格式,现代x86-64 Linux和Unix系统使用可执行可链接(Executable and Linkable Format,ELF)格式。

3)可重定位目标文件典型格式如下图:

                   

       ELF头(ELF header)以个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目(条目就是项的意思)的大小和数量。 可以使用readelf命令来查看目标文件内容,执行readelf -a main.o来查看mian.o地内容,其ELF头部格式如下(由于系统版本地原因,可能和描述地有差异):

    节头部表:不同节的位置和大小是由节头部表描述的。可以使用readelf命令来查看目标文件内容,执行readelf -a main.o来查看mian.o地内容,其节头部表如下(由于系统版本地原因,可能和描述地有差异):

    .text:已编译程序的机器代码。

    .rodata: 只读数据,比如printf语句中的格式和开关语句的跳转表。

    .data: 已初始化的全局和静态C变量。局部C变量在运行时被保存在栈中,既不出现在.data节中,也不出现在.bss节中。
    .bss: 未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在 目标文件中这个节不占据实际的空间,它仅仅是一个占位符。目标文件格式区分已初始化 和未初始化变量是为了空间效率:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为0。

    .symtab: 一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。一些程序员错误地认为必须通过-g选项来编译 个程序,才能得到符号表信息。实际上,每一个可重定位目标文件在.symtab中都有一张符号表(除非程序员特意用STRIP命令去掉它)。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。

    .rel.text:一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非用户下使地指示链接器包含这些信息。
    .rel. data:被模块引用或定义的所有全局变最的重定位信息。一般而言,任何已初始化的全局变量,如果它的初始值是一个全局变量地址或者外部定义函数的地址,都需要被修改。

    .debug: 一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量以及原始的C源文件。只有以-g选项(即调试)调用gcc时才会得到这张表。

    .line:原始C源程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译器驱动程序时,才会得到这张表。

    .strtab: 一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以null结尾的字符串的序列。

3、符号和符号表

1)每个重定位目标文件m都有一个符号表,它包含这个可重定位目标文件m的定义和引用的符号的信息。在链接器的上下文中,有三种不同的符号:

    (1) 由模块m定义并能被其他模块引用的全局符号。全局链接器符号对应于非静态的C 函数和全局变量。

    (2)由其他模块定义并被模块m引用的全局符号。这些符号称为外部符号,对应于在其他模块中定义的非静态C函数和全局变量。

    (3)只被模块m定义和引用的局部符号。它们对应于带static属性的C函数和全局变量。这些符号在模块m中任何位置都可见,但是不能被其他模块引用。

2).symtab节中包含ELF符号表,在这张符号表中包含一个数组,如下:

    name是字符串表中的字节偏移, 指向符号的以null结尾的字符串名字。value是符号的地址。 对于可重定位的模块来说,value是距定义目标的节的起始位置的偏移(比如在.data节其实位置的偏移)。 对于 可执行目标文件来说, 该值是一个绝对运行时地址。size是目标的大小(以字节为单位)。 type通常耍么是数据, 要么是函数。 符号表还可以包含各个节的条目, 以及对应原始源文件的路径名的条目。 所以这些目标的类型也有所不同。binding字段表示符号是本地的还是全局的。

    每个符号都被分配到目标文件的某个节,由section字段表示,该字段也是一个到节头部表的索引。有三个特殊的伪节(pseudosection), 它们在节头部表中是没有条目的: ABS代表不该被重定位的符号;UNDEF代表未定义的符号,也就是在本目标模块中引用,但是却在其他地方定义的符号;COMMON表示还未被分配位置的未初始化的数据目标。对于COMMON符号,value字段给出对齐要求,而size给出最小的大小。注意,只有可重定位目标文件中才有这些伪节,可执行目标文件中是没有的。

     COMMON和.bss的区别很细微。现代的GCC版本根据以下规则来将可重定位目标 文件中的符号分配到COMMON和.bss中:

   可以使用readelf命令来查看目标文件内容,执行readelf -a main.o来查看mian.o的内容,其mian.o的符号表如下(由于系统版本地原因,可能和描述地有差异):

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值