预处理,编译,汇编,链接,全过程。

news2025/1/20 3:45:46

编译,链接,全过程。

  • 背景知识
  • 预处理:
    • 1.宏定义指令,如#define MAX 1;
    • 2.条件编译指令,如#ifdef、 #ifndef、#else、#elif、#endif等。
    • 3.头文件包含指令,如#include等。
    • 4.特殊符号,预编译程序可以识别一些特殊的符号。
  • 编译:
    • 1.词法分析:
    • 2.语法分析
    • 3语义分析
    • 4.中间语言生成
    • 5.目标代码生成与优化
  • 汇编:
  • 链接:
    • 两步链接:
    • 1.空间与地址分配
    • 2.符号解析与重定位

背景知识

ELF文件类型

ELF文件类型说明实例
可重定位文件这类文件包含了代码和数据,可以被用来链接成可执行文件或共享目标文件,静态链接库也可以归为这一类Linux的.o,Windows的.obj
可执行文件可以执行的程序Linux的/bin/bash Windows的.exe
共享目标文件这种文件包含了代码和数据,1.链接器可以使用这种文件与其它可重定位文件和共享目标文件链接,产生新的目标文件,2.动态连接器可以将这几个这种共享目标文件与可执行文件结合,作为进程映像的一部分来运行Linux的.so,Windows的DLL
核心转储文件当进程意外终止,系统可以将该进程的地址空间的内容急终止的一些其它信息转储到核心转储文件Linux 下的core dump

编译器和汇编器生成可重定位目标文件(包括共享目标文件),链接器生成可执行目标文件

目标文件:源代码编译后但未进行链接的中间文件,它与可执行文件的格式几乎是一样的。不仅如此动态链接库(Linux的.so和Windows的.dll)和静态链接库(Linux的.a和Windows的.lib)格式都是一样的。在Window按照PE-COFF,LInux下按照ELF存储。

现在PC流行的可执行文件格式主要是Windows下的PE和Linux下的ELF,它们都是COFF格式的变种

ELF可重定位文件

在这里插入图片描述

  • .text:已编译程序的机器代码
  • .rodata:只读数据
  • .data:已初始化的全局变量和静态变量
  • .bss未初始化的全局变量和静态变量
  • .symtab:一个符号表,它存放程序中定义和引用的函数和全局变量的信息。(和编译器中的符号表不同,它不存放局部变量的信息)
  • .rel.text: .text的重定位表
  • .rel.data:.data的重定位表
  • .debug:调试符号表,只有以-g选项调用编译器编译时,才会得到这种表
  • .line:源程序中行号和.text节中机器指令之间的映射,只有以-g调用编译器时,才会得到这张表。
  • .strtab:一个字符串表,内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。
    符号表:

符号表中每个条目的格式

typedef struct {
	int name;
	char type : 4,
		binding : 4;
	char reserved;
	short section;
	long value;
	long size;
}Elf64_Symbol;

name:字符串表中的字节偏移

size:目标大小

value是符号的地址(决对运行时地址)

type:要么是函数,要么是数据

binging字段表示符号是本地还是全局的。

每个符号都被分配到目标文件的某个节,由section字段表示,该字段也是一个到一个节头部表的索引,有三个特殊的伪节,它们在节头部表中是没有条目的,

  1. ABS代表不该被重定位的符号
  2. UNDEF代表未定义的符号(在其它模块定义,在本模块引用)
  3. COMMON表示还未被分配位置的未初始化的数据目标。

对于COMMON符号,value字段给出对齐要求,而size给出最小的大小。

只有可重定位目标文件中才有这些伪节,可执行目标文件中是没有的。

COMMON与bss区别

COMMOE 未初始化的全局变量

.bss未初始化的静态变量,以及初始化为0的全局或静态变量。

预处理:

处理以"#"开始的预编译指令

1.宏定义指令,如#define MAX 1;

对于这种伪指令,预编译所要做的是将程序中的所有M用1来替换,一定要注意作为字符常量MAX则不被替换(因为已经是常量,其值已经是确定的)。与之相对应的还有#undef,则是将取消对某个宏的定义,使之在后面出现时再不被替换。

2.条件编译指令,如#ifdef、 #ifndef、#else、#elif、#endif等。

这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。换言而之也就是预编译程序将根据有关的文件,将哪些不必要的代码过滤掉。

3.头文件包含指令,如#include等。

把引用的头文件在引用点展开

4.特殊符号,预编译程序可以识别一些特殊的符号。

例如,在源程序中出现的LINE标识符将被解释为当前行号(十进制),FILE则被解释为当前被编译的C源程序的文件名称,FUNCTION则被解释为当前被编译的C源程序中的函数名称。

编译:

我们通过对下面这行代码进行举例来帮助理解

array[index] =(index+4)*(2+6);

1.词法分析:

源程序先被输入到扫描器中,将源代码的字符序列分割成一系列的记号(lex的程序(一种扫描器)可以实现词法扫描,它会按照用户之前描述好的词法规则将输入的字符创分割成一个个记号

符号分为:关键字,标识符,字面量(数据,字符串),特殊符合

而这行代码进行扫描后就产生了16个记号。

记号类型
array标识符
[左方括号
index标识符
]右方括号
=赋值
(左圆括号
index标识符
+加号
4数字
)右圆括号
*乘号
(左圆括号
2数字
+加号
6数字
)右圆括号

2.语法分析

在这里插入图片描述

语法分析器将对由扫描器产生的记号进行语法分析(采用上下文无关语法),从而产生语法树(以表达式为节点的树),(yacc(一种语法分析器)可以根据用户给定的语法规则对输入的记号序列进行解析,从而构建一根语法树,对于不同的编程语义,编译器的开发者只需要改变语法规则,而无需为每个编译器编写一个语法分析器,所以它有被称为“编译器编译器 Compiler Compiler”)

我们可以看到,整个语句被看作成一个赋值表达式,赋值表达式的左边是一个数组表达式,它的右边是一个乘法表达式,数组表达式又由两个符号组成。而在语法分析的同时,很多运算的优先级和含义也被确定了下来,同时,如果出现了了表达式不合法,比如各种符号不匹配,表达式缺少操作符,编译器就会报告语法分析阶段的错误。

3语义分析

在这里插入图片描述

由语义分析器来完成,语法分析仅仅完成了对表达式语法层面的分析,并不了解这个语句是否真正有意义,(不能分析语法的对错,如一根指针和浮点数相乘是否合法),编译器能分析的语义是静态语义,而动态语义只能在运行期才能确定。(如0作为除数是一个运行期语义错误)

静态语义通常包括声名类型的匹配类型的转换,如将一个浮点型的表达式给一个整形表达式。

而比如将一个浮点型赋值给一个指针的时候,语义分析程序就会发现这个类型不匹配,编译器就会报错。 而比如将0做为除数是一个运行期语义错误。

经过语义分析后,整个语法树的表达式都被标识了类型,如果有些类型需要做隐式类型转换,语义分析程序就会在语法树中插入相应的转换节点。

4.中间语言生成

在这里插入图片描述

现代编译器通常会对源代码进行优化

比如说上面的(2+6)就会被优化掉,因为它的值在编译期就可以被确定,所以这个表达式被优化成8,这个在语法树上做优化比较困难,所以源代码往往将语法树转化为中间代码(语法树的顺序表示,十分接近目标代码,但是它不包含数据的尺寸,变量地址,寄存器名字)

中间代码有很多形式(三地址码,P-代码)

中间代码使得编译器可以被分为前端和后端,编译器前端负责产生机器无关的中间代码编译器后端负责将中间代码转换成目标机器代码,(这样对于一些可以跨平台的编译器,它们可以针对不同的平台使用同一个前端和针对不同机器平台的数个后端)。

5.目标代码生成与优化

源代码优化器产生中间代码标志着下面的过程都属于编译器后端,编译器后端主要包括代码生成器和目标代码优化器,

代码生成器将中间代码转化为目标机器代码,依赖于目标机器,因为不同的机器有着不同的字长,寄存器,整数类型和浮点数类型,

最后目标代码优化器对上述的目标代码进行优化,比如选择合适的寻址方式使用位移来代替乘法运算删除多余的指令

因为现代高级编程语言的复杂性,至今没有一个编译器能够完整的支持c++语言标准所规定的所有语言特性。(所以在编译的时候需要指定它的标准。)

因为现代CPU的复杂性,为了支持这些特性,编译器的机器指令优化过程也十分复杂,

因为有些编译器支持多种硬件平台(如gcc几乎支持所有的CPU平台),即允许编译器编译出多种目标CPU的代码,这也导致编译器的指令生成更为复杂。

汇编:

将汇编代码转变成机器可以执行的指令

生成可重定位的二进制文件;(.obj文件)
此文件中生成符号表,符号表中存放的就是程序所产生的符号(例如:函数名,变量名等),我们的编译阶段是不会去给符号分配地址的。

链接:

对于早期程序来说,如下面的指令

第一条指令0001 01000001xxxx表示跳转指令,这条指令表示跳转到第四条指令 )

第二条指令..................

第三条指令..................

第四条指令1000 0111

当程序修改时,比如对第二条指令进行删除或者在其之前添加指令。而当其一旦修改,第一条指令也要进行修改。(要调转的位置)

而在发展的过程中,汇编语言使用符号来标记位置,如我们把刚才的第四条指令命名为"foo"则第一条指令的汇编就为 jump foo,这个时候就算第一条指令和第四条之间有指令修改了,我们也不需要修改第一条指令了。当这个foo指令之前插入或减少了多少条指令导致foo的地址发送变化,汇编器在每次汇编程序时都会重新计算"foo"这个符号的地址,然后把所有引用都"foo"的指令修正到正确的地址。

这种用符号表明地址的概念在被汇编的使用而被迅速普及 从此函数的起始地址,变量的起始地址也用符号来表示。

而上面讲的重新计算各个符号的地址的过程就是重定位。

比如我们在main.c模块中调用了其它模块的foo函数,但是在编译器编译main.c的时候,编译器并不知道foo函数的地址,它暂时把这些调用foo的指令的目标地址搁置,等到最后链接的时候由连接器进行修正,填入正确的foo地址

在现代的程序中,代码往往有多个模块,而当不同模块之间的函数的调用,符号访问,即模块之间符号的引用,而把不同模板拼接到一起,则就是链接,链接的主要内容就算把各个模块之间引用的部分都处理好,也使得各个模块之间能够正确的链接。

原理:把一些指令对其它符号地址的引用加以修正。

主要过程:地址和空间分配,符号决议,重定位
符号决议有时也叫(符号绑定,名称绑定,名称决议,地址绑定,指令绑定),大体它们的意思都一样,但从细节来区分:决议更倾向于静态链接,绑定更倾向于动态链接,在静态链接中,我们将统一称为符号决议。

对于链接器来说:符号表中有三种不同的符号,

1.由模块m定义并能被其它模块引用的全局符号

2.由其它模块定义并被模块m引用的全局符号(外部符号)

3.只被模块m定义和引用的局部符号(这些符号在模块m中任务位置都可见,但是不能被其它模块引用)。

两步链接:

1.空间与地址分配

扫描所有的输入目标文件,获得它们各个段的长度,属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一放到一个全局符号表中同时链接器进行相似段合并,并建立映射关系。

相似段合并

把所有输入文件的.text合并到输出文件的.text(.data,.bss都一样)

.bss段在目标文件和可执行文件中并不占用空间,但是它在装载时占用地址空间,所有链接器在链接.bss时,也将它们合并,并且分配虚拟空间。

链接前后各个段的属性

在这里插入图片描述

VMA(virtual Memory Address)虚拟地址,LMA(Load Memory Address)加载地址,

我们可以看到在链接前,目标文件中所有段的VMA都是0,因为虚拟空间还没有分配。

下面为链接前后,目标文件各段的分配,程序虚拟地址情况。

在这里插入图片描述

为什么链接器对可执行文件中".text"段分配的时候是从0x08049108,而不是从0开始呐,因为在Linux下,ELF可执行文件默认从地址0x0804800开始分配。

链接器计算各个符号的虚拟地址:

因为各个符号在段内的相对位置是固定的(链接器需要给每个符号加一个偏移,使它们能够调整到正确的位置)

如:a.o的main函数相对于a.o的.text的 偏移是x,经过链接合并后,a.o的.text段位于虚拟地址的0x08048094,但是由因为a.o的main位于.text段的首部,所以x为0,main的地址为0x08048094。

2.符号解析与重定位

符号解析:将每个符号引用正好和一个符号定义关联起来。

当编译器遇到一个不是在当前模块定义的符号,会加上该符号是在其它模块定义,生成一个链接器符号条目,并把它交给链接器处理,链接器就会查找由所有输入目标文件的符号表组成的全局符号表,找到后进行重定位

重定位:链接器通过重定位表来确定那些指令需要被重定位。通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。

重定位表:专门来存放这些需要重定位符号的信息。

对于每个要重定位的ELF段都有一个重定位表。

如代码段的.text有要被重定位的地方,那么就有一个对于的的.rel.text的段保存了代码段的重定位条目。

ELF重定位条目

typedef struct {
	long offset;
	long type : 32,
		symbol : 32;
	long addend;
	     
}Elf64_Rela;


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/44094.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQL explain解析器

EXPLAIN 参数前言字段参数id 查询编号select_type 关联类型SIMPLEPRIMARYUNION & UNION RESULTDERIVEDSUBQUERYDEPENDENTUNCACHEABLEMATERIALIZEDtable 表名partitions 数据的分区信息type 关联类型system & consteq_refreffulltextref_or_nullindex_mergeunique_subqu…

必备表格软件-FineReport正则表达式简介

1. 概述 1.1 应用场景 有时候我们需要用到正则表达式进行信息的校验。 例如有一张使用了「文本控件」的查询报表,输入「销售员」姓名后可查询销售员的销售情况,此时希望设置销售员文本控件的填入信息校验内容为:若填入内容不是中文或中文的…

【无标题】接口测试用例设计(精华)

接口测试 请求头 请求头中的Content-Type有哪几种: 1.application/x-www-form-urlencoded 最常见的 POST 提交数据的方式,原生Form表单,如果不设置 enctype 属性,默认为application/x-www-form-urlencoded 方式提交数据。 2.appli…

Node.js 入门教程 19 package-lock.json 文件

Node.js 入门教程 Node.js官方入门教程 Node.js中文网 本文仅用于学习记录,不存在任何商业用途,如侵删 文章目录Node.js 入门教程19 package-lock.json 文件19.1 示例19 package-lock.json 文件 在版本 5 中,npm 引入了 package-lock.json 文…

BUUCTF Reverse/[2019红帽杯]xx

BUUCTF Reverse/[2019红帽杯]xx 先看下文件信息:没有加壳、64位程序 看别人wp时候发现个好东东,就是这个findcrypt插件,可以看加密算法的,具体安装可以看这个IDA7.5安装findcrypt3插件 可以看到这是tea加密 先一点点分析代码,输入…

48.标准输入输出流

标准输入流对象cin,重点掌握的函数: 1.cin.get() //一次只能读取一个字符 2.cin.get(一个参数) //读一个字符 3.cin.get(两个参数) //可以读字符串 这种情况下不会读取换行符,换行符始终留在缓冲区当中 4.cin.getline() 此函数在读取数据的…

跳出打工圈!程序员要如何走上创业逆袭路,获得财富自由

前言 采访了一位创业人物,创业即是人生,生命精彩待续 人生的每一种经历都是一门功课,我们无法跳跃过去,所以必须要逐个地去完成它。无论遇到什么样的困难,自己想通了、走出来了,才会有不一样的自己。 大…

rt-thread通过spi连接W25Q32后无法读取ID

注意,cs引脚必须由rtt控制,但是我这个cs引脚用的是PA15,它默认是jlink的引脚,所以首先要将jlink禁用,如下: rcu_periph_clock_enable(RCU_AF);rcu_periph_clock_enable(RCU_GPIOA);rcu_periph_clock_enable…

开课通知 | 《AISHELL-3语音合成实战》课程

语音合成技术 在多个智能语音技术的学习方向中,语音合成又称文本转换(Text To Speech, 简称TTS)即将文字信息转换成为人类可以听得懂、流利的语音技术。在人机语音交互系统中,语音合成作为最后机器将内容转化为语音的输出环节&…

线程可重复使用,程序开发是如何使用线程池的呢?

大家都知道多线程开发对于程序开发的重要性,今天大连九哥来给大家聊一聊线程池的使用过程。 一、为什么要使用线程池? 大家都知道java支持多线程开发,也就是支持多个任务并行运行,我们也知道线程的生命周期中包括创建、就绪、运…

maltose-Transferrin 麦芽糖-转铁蛋白

maltose-Transferrin 麦芽糖-转铁蛋白 中文名称:麦芽糖-转铁蛋白 英文名称:maltose-Transferrin 别称:转铁蛋白修饰麦芽糖,Tf-麦芽糖 可以提供PEG接枝修饰麦芽糖,麦芽糖-聚乙二醇-转铁蛋白,Transferrin-PEG-maltos…

17.Http__Linux

目录 1.为什么要学Linux 2.我们要学什么 3.Linux命令操作 1.常用快捷键: 2.文件的操作 4.管道pipe 5.重定向redirect 5.查看系统指标(任务管理器) 7.安装软件(maven、包) 8.部署博客系统 1.首先对tomcat进行安装和调配 HTTPS:出现…

山西青年杂志山西青年杂志社山西青年编辑部2022年第22期目录

本刊专稿《山西青年》投稿:cn7kantougao163.com 基于学生激励机制的考核方案研究 聂晶晶; 1-4 当前我国大学生网络心理障碍分析及引导机制研究 陈宁;王佳玮; 5-8 教育教学研究 百万扩招背景下高职院校“三教”改革探究 郭庆秋; 9-11 “百万扩招”背…

linux统计目录文件数量

1、当前文件夹及子文件夹的数量: ls -lR | grep "^d" | wc -l 2、当前文件及子目录文件夹的数量: ls -lR | grep "^-" | wc -l 3、当前目录某文件的数量: find . -name filename | wc -l 4、当前目录所有目录和文件罗…

SpringCloud服务治理介绍Nacos安装及实现负载均衡

目录 一、服务治理简介 二、nacos简介 三、nacos下载&安装 四、nacos实现负载均衡 一、服务治理简介 通过上一章的操作,我们已经可以实现微服务之间的调用。但是我们把服务提供者的网络地址 (ip,端口)等硬编码到了代码中…

基于PHP+MySQL大连真爱果汁厂管理系统的设计与实现

果汁是以水果为原料经过物理方法如压榨、离心、萃取等得到的汁液产品。长期的饮用果汁不仅可以让我们大饱口福而且能够增加免疫力,减少生病,延缓衰老,甚至一些果汁还有美容养颜的功效,果汁中富含多种矿物质和有机酸为此深受各类人群的喜欢,随着人们健康意识的增加,人们对果汁的…

双十二投影仪推荐 三分钟告诉你怎么挑选到称心如意的投影仪

作为家庭沉浸式观影的必备神器,投影仪越来越受大众的喜爱,今天就让我们一起来看看双十二投影仪推荐,双十二高性价比投影仪选购指南,双十二卧室投影怎么选?2022双十二热门投影仪推荐,这8款投影仪总有一款适合…

华为云会议,轻松实现远程智能办公

说到云会议,很多人首先想到的应该就是华为云会议!华为云会议基于华为近30年的音视频技术,结合华为IdeaHub等全系列智能协作终端,为客户提供全场景端云协同视频会议解决方案,满足跨地区、跨企业、跨终端的智能沟通协作需…

聚L-精氨酸/纳米金/石墨烯/聚苯胺复合膜/铝粉/稀土粒子修饰多巴胺的制备

小编这里给大家分享的科研内容是聚L-精氨酸/纳米金/石墨烯/聚苯胺复合膜/铝粉/稀土粒子修饰多巴胺的制备,和小编一起来看! 聚L-精氨酸/纳米金修饰多巴胺的制备: 利用多电位脉冲沉积法制备纳米金修饰电极 (AuNPs/GCE),再将L-精氨酸电聚合在AuNPs/GCE表面…

Kotlin高仿微信-项目实践58篇

Kotlin高仿微信项目实践主要包含5大模块: 1、Web服务器 2、Kotlin客户端 3、Xmpp即时通讯服务器 4、视频通话服务器 5、腾讯云服务器 另外也有Flutter版本高仿微信功能,Flutter版本跟Kotlin同时开发,调用的是同一个服务器接口。 每天只…