参考《程序员的自我修养》
1.源代码为什么需要编译链接
编译类的语言(c 或者c++)写出的源代码机器是看不懂的,机器看懂的是可执行的代码。而源代码转化成可执行代码需要经过编译和链接。
2.虚拟地址空间布局
生成完一个可执行文件a.out之后,我们需要把指令和数据从磁盘加载到虚拟地址空间中去。我们先看一下可执行文件都加载到虚拟地址空间中哪些地方。
#include <stdio.h>
int gdata1 = 10;
int gdtata2 = 0;
int gdtata3;
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;
}
3.编译链接步骤
看完虚拟内存布局之后,我们看一下编译链接步骤
1)预编译 2)编译 3)汇编 4)链接
该图来源《程序员的自我修养》
我们看一下这个图来讲一下每个步骤都做了哪些事情
预编译
test.cpp文件和相关头文件预编译成.i文件
1)将所有#define删除 ,并展开宏定义
2)处理所有的预编译指令 比如#if
3)处理#include 预编译指令,将包含的头文件插入到预编译指令的位置
4)删除注释/* //
5)添加行号和文件标识
6)保留#pragma编译器指令
简单来说就是展开需要展开的东西,删除一些不要的东西。不检查代码
编译
1)词法分析
2)语法分析
3)语义分析
4)代码优化
5)汇总当前文件的所有符号
汇编
1).s文件中有很多汇编指令,根据特定平台(windows linux)把它们转化成特定的机器码。
2)构建.obj格式
链接
1)合并所有obj文件的段,并调整段偏移和段长度, 合并符号表
2)符号解析完成后,分配到虚拟地址
3)链接核心 符号的重定位
看完概述之后,我们具体看一下这几个问题
产生了obj组成格式是什么
链接都做了什么,可执行文件为什么能运行,从哪里运行
4.obj文件格式
这个是.obj文件格式
ELF Header 这个里面保存了整个文件的基本属性,版本,程序入口地址等,文件头大小,这样也能往下偏移多少
主要存储了一个关键段表。段表保存了段名,长度,文件的偏移。这里就包括了.bss段。要知道ELF文件中没有单独保存.bss段
信息是存储在段表中的。.bss少了个数据,其实是gdata3,它是一个弱符号,不知道会不会被其他目标文件强符号代替。所以它是放在.comment段里的。.symtab是符号表,里面存放了符号。符号也是链接不可缺少的东西。
5.链接
符号:
我们将函数统称为符号,符号表就是把所有符号进行分类,函数名和变量名就是符号名。符号表记录了目标文件所用到的符号,每个符号都有一个对应的符号值。对于变量和函数来说,就是它们的地址。
我们先看一段代码
//main.c
#include <stdio.h>
extern int gdata10;
extern int sum(int,int);
int gdata1 = 10;
int gdata2 = 0;
int gdata3;
static gdata4 = 11;
static gdata5 = 0;
static gdata6;
int main()
{
int a = 12;
int b = 0;
int c = gdata10;
static int d = 13;
static int e = 0;
static int f;
sum(a,b);
return 0;
}
//sum.c
int gdata10 = 14;
int sum(int a,int b)
{
return a+b;
}
产生的main.o文件中的,两个外部引用的符号。*UND* gdata 10 和 * UND*sum 因为是分离编译,当前文件找不到。
函数和数据在编译阶段都是不分配地址的。数据地址是0地址,函数地址是-4地址
链接步骤
相似段合并
这个就是把所有目标文件相同属性(可读,可写,可读可写之类的)的段合并在一起,因为obj按4个字节对齐,可执行文件是按页面4kb对齐。
在段表里重新调整下,每个段的起始地址和段的偏移量。合并符号表。
符号解析
链接器在扫描完所有的输入目标文件后,所有这些未定义的符号都应该能够在全局符号表中找到,否则链接器报符号未定义错误。
给符号分配地址
这里符号解析完成之后,链接器给所有分配新的地址。但是指令段里面地址还是不对的。于是要把指令段的地址改正确。这就是重定位。
重定位
这个就是重定位,把指令段的符号地址进行改动。数据符号地址是绝对地址,而函数符号涉及到了指令跳转所以存的是偏移量。这样的话就算完成编译链接。
程序运行
./a.out
1)创建虚拟地址空间到物理内存的映射创建页目录和页表
2)加载代码段和数据段
3)把可执行文件的入口地址写到CPU的pc寄存器里面