编译链接的过程
预编译
预编译:(处理所有预编译的指令如 #inclue #if #endif #define #ifdef 等)
(1)宏替换
(2)头文件的引入
(3)注释的替换(用空格替换)
(4)添加行号和文件名标识
编译
(1)词法分析
将代码分割成一系列的符号,将符号分类(关键字 标识符 字面量 字符串等 )同时扫描器将符号添加到符号表中
(2)语法分析
语法分析器对扫描器产生的符号进行分析产生语法树(上下文无关语法)语法分析确定表达式的含义。语法错误在这个阶段会被检查出来(函数参数不对,括号不匹配,缺少操作数等)
(3)语义分析
完成对表达式层面的语法分析(只能分析静态语义,表达式,指针相乘或指针与浮点数相乘检查不出来)静态语义(类型是否匹配,类型的转换)经过语义分析,语法树都加上了类型
(4)优化代码并产生相应的汇编代码文件
源码级优化器将整个语法树转换成中间代码,中间代码使得编译器可以分为前端和后端,前端负责产生机器无关的中间代码,后端将中间代码转换成目标机器代码,编译器后端包括代码生成器和目标代码优化器,代码生成器将中间代码转换为目标代码,目标代码优化器对目标代码进行优化,比如选择适当的寻址方式,使用移位来代替乘法运算,删除多余指令。
汇编
源程序被编译后主要分为两种段 ,程序指令和程序数据,代码段属于程序指令,数据段和.bss 属于程序数据
汇编生成目标文件windows 下生成*.obj文件 linux 下生成*.o 文件,经过汇编后的机器指令放在.text段,初始化的全局变量、静态变量放在.data段,未初始化的全局变量放在.bss段,目标文件中还包括符号表、调试信息、字符串等。
在下面的代码中我们分析一下程序数据与程序指令所在的段
#include <stdio.h>
static int g1 = 10;
static int g2 = 0;
static int g3;
int g4 = 20;
int g5 = 0;
int g6;
int main()
{
static int g7 = 30;
static int g8 = 0;
static int g9;
int a = 10;
int b = 0;
int c;
}
在linux 下编译上面的代码
gcc -c 123.c
objdump -h 123.o 查看各个段的大小
想要查看上面的变量分别在那个段可以用命令
objdump -t 123.o
从上面的图中我们可以看到
g1 g4 g7 在.data段
g2 g3 g5 g8 g9 在.bss 段
g6 在.comment段
为什么会有这样的结果呢?这就要了解一下各个段中存放的是什么了。
.text 段 存放程序指令
.data 段 存放初始化了的全局变量和静态变量
.bss 段 存放未初始化的静态变量,和初始化为0的全局变量和静态变量
至于未初始化的全局变量,属于弱符号,在编译阶段无法确定该变量的内存的大小,所以放到.comment 段。
编译后生成的是elf 文件,下面看一下elf 文件的中各个段的位置
在elf文件中.bss 段是不占文件的内存大小的,这一点从第一张图中也可以看出。.bss段的起始位置为 0x0000005c,二下一个段的起始地址 .comment段的起始地址也为 0x0000005c。既然 .bss段是个虚拟的段,实际不占用elf文件的大小,那他是怎么存储那些未初始化的数据的?
.bss段中的数据都是未初始化,或者初始化为0的,这样的数据efl中并不占用内存,而是记录了该变量产生的符号以及所需要占用内存的大小
readelf -s 123.o 查看一下符号表我们就会明白。
只是在elf文件中.bss段不占内存,在装载时.bss段占用地址空间
链接
链接的主要内容是将各个模块之间的相互引用处理好,使各个模块之间可以衔接好,主要包括 地址和空间的分配 符号解析和重定位
地址与空间的分配
1、符号表的合并
相似段合并,所有.text段合并到一起 .data段合并到一起,.bss段合并到一起。.bss段在elf文件中不占用内存,但是在装载的时候占用内存。未初始化的全局变量存放在.comment段,在编译阶段无法确定弱符号所占内存的大小,所以在链接阶段确定对于相同名字的变量的选择。
2、建立映射关系:
获得各个段的长度属性和位置,并将输入文件中的左右符号定义和符号引用收集起来统一放到一个全局符号表中,链接器获得所有输入文件的段长度并将他们合并,计算出输入文件中各个段合并后的长度和位置,并建立映射关系。
重定位
链接器只对符号表中的global 符号处理,local 符号不做处理,所有obj符号表中对符号引用的地方都要找到其定义的地方。在下图中
main.o 的符号表中有四个global 符号 x,y ,main,Max。这四个global 需要重定位 UND表示未在本文件中定义的。
在elf文件中.text 的起始位置是0x08048034处,链接后可以看到.text 段的起始地址并不是从0x08048034处开始的,而是从0x08048094处开始
在.text段的前面还有96个字节的programe headers
Program headers的偏移地址是52字节处0x34正好是保留区往上的0x08048034开始。两个load 页面分别用来存放指令和数据
在链接之前查看main.o 的重定位表,需要重定位的有x,y,Max 其中x,y 的类型是R_386_32, Max的类型是 R_386_PC32,Max是函数,x,y 是变量,下图为链接之前的汇编代码,可以看到,需要重定位的变量用0x 00000000代替,函数地址用0xfffffffc代替
在链接之后可以看到用0x080490cc和0x080490c8替换了0x00000000这个无效的地址 oxffffffd0替换了0xfffffffc ,函数这个地址是偏移量,在0xffffffd0 是-48 的补码 即是-0x30。0x080480c4 - 0x30 = 0x08048094即是Max函数的入口地址