编译链接

来源:互联网 发布:河南网络电视台 编辑:程序博客网 时间:2024/05/23 15:57

先来通过一张图看看这一过程:


那在预编译、编译、汇编以及链接过程中具体都做了哪些工作呢?


预编译:自处理过程,带#都是预处理(包括#if0),字处理(删除注释)。

1、将所有的“#define”删除,并且展开所有的宏定义;

2、处理所有条件预编译指令,例如“#if”、“#ifdef”、“#elif”、“#else”、“endif”;

3、处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置;

4、删除所有的注释“//”和“/**/”;

5、添加行号和文件名表示,比如#2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息以及编译时产生编译错误或警告能够显示行号;

6、保留所有的#pragma编译器指令,因为编译器必须要使用他们。

编译:词法分析、语法分析、语义分析、代码优化、汇总所有的符号。

汇编:将汇编语言翻译成机器码,构建.o文件(二进制可重定位的目标文件)的组成格式。

链接

1、合并所有.obj文件的段,调整段地址偏移和段长度;

2、合并所有符号,进行符号解析(所有.obj符号表中对符号引用的地方都要找到该符号定义的地方);

3、空间与地址分配;

4、符号重定位(所有.obj文件的global符号进行处理,local的符号不做任何处理)。


我们通过上一篇《程序加载》中的例子来具体看一下:

  int gdata1 = 10;  int gdata2 = 0;  int gdata3;    static int gdata4 = 11;  static int gdata5 = 0;  static int gdata6;    int main()  {      int a = 12;      int b = 0;      int c;       static int d = 13;      static int e = 0;      static int f;        return 0;  }         

放在.text段的有main、a、b、c;放在.data段的有gdata1、gdata4、d;放在.bss段的有gdata2、gdata3、gdata5、gdata6、e、f。我们来看看到底是不是这样?




从上图可以看出虚拟内存地址(VMA)、加载的内存地址(LMA)都是0也就说明编译的过程是不分配内存地址的,链接过程中符号解析完成后才分配。.text的File off也就是偏移并不是从0开始,也就说前边还有一部分。这里也有一个比较奇怪的现象,.bss大小为20字节,但是起始偏移和.comment段起始偏移是一样的,也就说明.bss并没有占用文件的空间。这也就回答了上一篇提出的问题,.bss节省的是文件的空间而不是虚拟地址空间。


从下图可以看到所有段的信息。



从下图可以看到文件头部分。


从图中可以看出文件头52字节,换算成十六进制是0x34 ,我们假设文件头为0x00,那么ELF Header偏移到0x34处。正好接下来是.text段。


通过下面的命令打印各段的内容如下图所示:



如果有一个字符常量呢?比如char*p = “hello world!”


可以看到多了一个.rodata段,也就是只读数据段,字符串常量放在这个段中。


.bss存都不存我们怎么知道有哪些数据呢?通过读文件头可以知道section table 在哪里。文件的段表section table,保存了文件有哪些段,偏移量以及大小。初始化为0的没有初始化的都是0,没必要存储,但是要知道它是存在的,把它的信息存储下来。


通过上面的分析,我们可以得到.o文件的结构:


上面说了放在.data段的有gdata1、gdata4、d,一共12字节;放在.bss段的有gdata2、gdata3、gdata5、gdata6、e、f,应该是24字节,换算成十六进制应该是18,从上面的图中看到的是14,少了哪一个数据?又放在哪呢?

这就涉及到C语言中的强类型和弱类型,gdata3为弱类型。在编译时不能确定其他文件中是否有比它更强的符号或内存占用更大的符号,但是加static变量只是本文件可见,链接时所有.obj文件的global符号进行处理,local的符号不做任何处理。


从上图可以看出gdata3放在了*COM*块。



在main.o中gdata10和sum只是符号引用,前边是*UND*没有定义的,所以链接时所有.obj符号表中对符号引用的地方都要找到该符号定义的地方。


指令编译时,没有分配地址,gdata10只能是0x00000000,前边说了这个地址是不能访问的,函数的地址为0xfffffffc,这个地址是内核空间,也是不能用的。函数调用函数时涉及到指令的跳转,当前指令执行完后,怎么知道下一行指令的地址呢?pc寄存器记录了下一条要执行的指令的地址。当我们执行31行指令时,pc寄存器存放的是36行地址,函数地址就是pc寄存器+偏移量,即36-4=32。


.obj对齐方式为4字节对齐,而可执行文件为页面对齐。所以链接时合并所有.obj的段。那么如何合并呢?


所有相同属性的段(可读可写,可读可执行,只读)进行合并,组织在一个页面上。要重新调整偏移量的段长度,合并符号表。


接下来就该符号重定位,这一步又做了什么呢?我们把两个文件链接起来,通过查看符号表看到每个符号都有地址,之前没有地址的地方就要改变,这也就是符号的重定位。