编译链接运行原理

编译链接运行原理

我们知道,在操作系统中,只能识别机器码(0,1),为了让一个.c文件编译成为一个可运行的进程,我们需要进行五步操作:预编译,编译,汇编,链接,运行。那它们在这些阶段里做了什么事情,需要我们去研究它们。

编译器

编译器就是将高级语言翻译成机器语言的一个工具。就像我们用c/c++语言写的一个程序可以使用编译器将其翻译成机器可以执行的命令及数据

预编译阶段

**预编译阶段我们第一步是删除宏,即删除#define,并将其替换成文本文件。**但是我们一般不会去使用宏,因为它的风险大。看下面代码:
我们在宏中定义了一个乘法运算,让ab,编译打印出来的结果没有我们预想的100,而是35。这是因为在宏替换成文本后,它的表达式是这样的:5+55+5,即rt的值是35.但是当我们给每一个5+5都加上括号时,因为优先级原因,最终的表达式是(5+5)*(5+5)=100。
宏处理是一个简单的文本替换,没有类型检查和安全检查

在这里插入图片描述
在这里插入图片描述
预编译的第二部是处理#include开头的头文件,它是一个递归展开的过程在头文件中可能包含了其他的头文件
预编译的第三步是处理预编译指令,比如#if,#endif
预编译的第四步是删除注释
预编译的第五步是添加行号和文件标识,在程序出错时,快速的定位到出错的位置
预编译的第六步是保留#pragma,留给编译器来处理
预编译完成后由.c文件生成.i文件,此时.i文件还是高级语言

编译阶段

编译阶段主要做的事有:
词法分析(每个单词的组成是否规则)
我们将源代码程序输入到扫描器(scanner),它负责将源代码的字符序列分割成一系列的记号
语法分析(分析的是一行表达式)
对扫描器产生的记号进行语法分析,产生语法树。整个分析过程与上下文无关的分析手段
语义分析(多行分析)
编译器所能分析的语义是静态语义,即在编译期间可以确定的语义。与之对应的是动态语义,即只有在运行期间才能确定的语义
代码优化(编译器优化)
生成汇编指令(.s文件,介于高级语言和机器语言中间的低级语言)

汇编

汇编是将汇编代码转变成指令翻译成二进制(.o文件),目标文件,也叫可重入文件。
目标文件就是源代码编译后但未进行链接的中间文件,其中动态链接库和静态链接库的文件也都是按照库执行文件格式存储
.o文件是一个ELF格式的文件,我们可以在linux中使用objdump -h xxx.o来查看一个文件的核心布局
例如a=30;它的指令是mov dword ptr[a] 1E(将30移动到a的内存字段中,一个字是两个字节)
| ELF header |
±--------------+
| .text section | 指令段,执行指令的代码
±--------------+
| .data section | 数据段,已初始化且初始化不为0的全局变量或局部静态变量
±--------------+
| .bss section | 在文件中只有一个.data端。.bss段是不存在的,因为它们的数据都是0,所以没有开辟空间的必要。未初始化或初始化为0的全局变量和局部静态变量
±--------------+
| . comment section | 注释信息段
±--------------+
| .rodata section |只读数据段,存放的一般是只读变量(const 修饰的变量)和字符串常量
±--------------+
那我们这样做的目的是为了什么呢?它有三点好处:
1,数据区域对于进程来说是可读写的,指令区域是可读的,分开可以防止程序的指令被有意无意的修改
2,
现代cpu的缓存一般都被设计成数据缓存和指令缓存分离,这将对cpu的缓存命中率提高有好处
3,当系统中运行着多个该程序的副本时,它们的指令都是一样的,所以内存中只需要保存一份即可,这样可以极大的节省空间

可执行文件 , 可以直接执行的文件,/bin/bash文件windows下的.exe
可重定位文件,包含了代码和数据,可以被用来链接成可执行文件或者共享目标文件,包括静态库,Linux的.o,windows下的.obj
共享目标文件,跟其他的可重定位文件和共享目标文件链接,产生新的目标文件或者时做一个动态库,如:Linux的.so windows的DLL
我们可以在linux下使用file命令查看相应的文件格式,如file foobar.o

我们对main.文件只进行编译不连接,生成.o文件main.o使用gcc -c main.c
在这里插入图片描述
我们用objdump -h main.o命令来查看main.o 的结构,我们会发现处理.bss段以外每个段的下面都有一个CONTENTS,它代表这个段是真实存在的,那么就可以说明.bss段是不存在的,你可以把它理解为因本书上的目录。还有最后一个段我们称为“堆栈提示段”,它下面虽然有CONTENTS,但是我们发现它的大小为0,我们姑且认为它也不是真是存在的。
在这里插入图片描述
我们可以用size main.o来查看每个段中数据的大小
在这里插入图片描述
我们可以通过objdump -s -d main.o。其中-s可以将原有段中的内容以十六进制的方式打印出来,-d可以将所有包含指令的段反汇编

在这里插入图片描述

那其实除了以上几种段以外还有很多的段,比如:
.rdatal:和rdata其实是一样的
.debug:调试信息
.dynamic:动态连接信息
.hash:符号哈希表
.line:调试时的行号表,即源代码行号与编译后指令的对应表
.symtab:符号表
.shstrtab:段名表

链接

1.合并段和符号表

符号表:在连接中,目标文件之间互相拼合实际上是目标文件之间对地址的引用,即对函数和变量之间的引用。在连接中我们统称函数和变量为符号,函数名和变量名就是符号名。每一个文件都会有一个相应的符号表,每一个符号都有一个符号值。对于变量和函数来说,符号值就是它们的地址。我们可以用nm main.o 命令来查看都有哪些符号:
在这里插入图片描述
我们可以通过readeil -s main.o来查看符号表所对应的信息
在这里插入图片描述
**空间与地址分配:**扫描所有的输入目标文件,并且获得它们段的长度、属性、位置,并将目标文件中的符号表中所有的符号定义和符号引用收集起来,放到一个符号表。连接器可以获得所有输入目标文件的长度,并将它们合并,计算出输出文件中段合并后的长度与位置,建立映射关系。

**符号解析与重定位:**使用搜集好的信息来读取输入文件中的数据、重定位信息,并进行符号解析与重定位、调整代码中的地址。
强弱符号(链接阶段处理):在c中,已初始化的符号叫做强符号,没有初始化的符号称为弱符号。如果出现两个同名强符号,系统会报错;如果有一强一弱,最终会选择强符号;如果是两个弱符号,系统先编译哪个符号,就会选择哪个,但是也有可能直接报错。
预编译,编译,汇编,都是编译单元,即一个源文件编译完再去编译另外一个源文件。当我们去编译时遇到一个弱符号,我们不知道别的源文件中是否还存在强符号,所以我们会将这个弱符号放在.comment段来储存。当我们进行连接时,此时所有段已经合并,编译器进行比较选择。

重定位表:在ELF文件中,有一个叫重定位表的结构来专门保存这些与重定位相关的信息。我们可以用objdump -r main.o来查看重定位
表的内容。也就是说,在main.o中所有要引用到外部符号的地址,都会生成一个重定位入口,而重定位入口的偏移表示该入口在要被重定位的段中的位置。
我们假设有个全局变量var,它在目标文件A中,如果我们要在目标文件B里面访问这个变量
得到 movl $0x2a, var,这条指令是将var这个变量赋值给0x2a,相当于var=32。编译目标文件B,得到指令机器码:
c7 05 00 00 00 00 2a 00 00 00
由于我们在编译目标文件B时,不知道目标文件A中var的位置,所以我们将mov指令的目标地址置为0(虚假地址),假设A和B链接后,变量var的地址为0x100,那么连接器就会将这个指令的目标地址修改为0x100。这个地址的修正过程也叫做重定位,每一个要被修正的地方叫做重定位入口

符号解析:我们将多个目标文件链接起来的目的是因为我们要找的符号被定义在了其他的目标文件中。如果没有链接到一起系统就会报错,最常见的是我们在链接时少了某个库或者目标文件的路径不正确,我们可以通过readelf -s main.o来查看符号表,未定义的符号我们会将其放在"UND",即"undefined"(未定义区)

指令修正方式:一般的指令有跳转指令(jmp)子程序调用指令(call)数据传送指令(mov)
对于32位X86平台来说只有绝对近址和相对近址,这两种指令的修正方式每个被修正的位置长度都为32位,即4个字节。
x86有两种重定位类型:R_386_32绝对寻址修正方法S+A;R_386_PC32相对寻址修正方法S+A-P。
A=保存在被修正位置的值
P=被修正的位置(偏移量或者虚拟地址)
S=符号的实际地址

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值