compile & link

高级语言写就的程序如何跑起来,编译器上的build命令其实干了四件事:

预处理 (文本文件->文件文件)

编译 (文本文件->文本文件)

汇编 (文本文件->二进制文件)

链接

文本文件:数字、字母等都用指定编码如ASCII码/UNICODE码等方式编码并存储的二进制文件。

二进制文件:编码格式自由的二进制文件。

今天不讲链接,只讲前三个功能,不求精细,但求思路连贯。

每个.cpp文件是一个编译单元,.h文件不是,编译器对每个编译单元分别处理。

预处理

展开宏定义:#define #undef

处理预编译指令:#if #ifdef #ifndef #elif #else #endif

递归处理头文件:#include

删除注释

添加行号和文件名标识别,用于debug

编译

前端(平台无关)

词法分析:形象点说就是扫描器,将字符识别为有意义的记号:关键字、标识符、字面量(数字、字符串)、操作符等,顺便将标示符放到符号表,数字、字符串放到文字表等,链接阶段要用。

语法分析:对每个表达式构造语法树-操作符在父节点上,左右操作数在左右孩子上。

语义分析:语法对不代表这句话有意义,不过这阶段只能做静态语义分析,给每个语法树节点标上类型,静态类型检查、类型转换等都是在这个阶段完成。

看完数学之美会知道,自然语言处理已经放弃了语义分析这些传统方法,转而采用统计学方法,一大原因就是自然语言是上下文相关的,而人类发明的高级语言是上下文无关的。

源代码优化:将语法树用 三地址码( x = yOPz)等格式表达,容易做优化,优化的代码包括常量相加之类。

后端(平台相关)

汇编语言生成:将中间代码转化为汇编指令。

汇编指令优化:比如用移位代替乘法等。

汇编

这项工作比较简单,将汇编指令转化为机器指令,参考汇编指令与机器指令对照表一对一翻译。

最终获得的文件称为可重定位或共享目标文件


可执行文件格式

可执行文件格式主要有两种:

PE( Portable Executable ) Windows下。

ELF( Executable Linkable Format ) Linux下。

两者都是COFF(Common File Format)格式的变种。

由于目标文件一般与可执行文件采用相同的格式存储,所以广义上将目标文件与可执行文件看做同一种类型的文件。

Linux下,统称为ELF文件,或者统称为目标文件

ELF文件分类

ELF文件类型说明生成实例

可重定位文件(Relocatable File)可用于链接为可执行文件或共享目标文件编译器和汇编器Linux .o, Windows .obj, 静态库

共享目标文件(Shared Object File)特殊的可重定位文件,可链接为新目标文件;动态链接器将之与可执行文件结合编译器和汇编器Linux .so, Windows .dll

可执行文件(Executable File)可以直接执行链接器Linux 无拓展名, Windows .exe

ELF文件结构

下表只包含主要段,每个段基本上都有独特的数据结构定义。

ELF文件部分段

ELF Header文件头,说明存储方式、版本、运行平台,程序头入口地址和长度,段表的位置、数量和长度

.text代码段,机器指令流

.data数据段,初始化的全局变量、局部静态变量

.rodata只读数据段,放置只读变量和字符串常量

.BSS存放未初始化的全局变量和局部静态变量,但由于强弱符号特性,未初始化全局变量有时并不放这里

.comment编译器版本信息

.strtab字符串表,变量的名字等各种字符串

.shstrtab段名表,每个段的名字

Section Table段表,头文件之后最重要的部分,存储各段的段名-在段名表的偏移,段的长度、偏移,读写权限等

.symtab符号表,最重要的是全局符号:函数和全局变量信息,符号名(字符串表偏移)、类型、值(可重定位文件中指在代码段或数据段偏移,可执行文件中指虚拟地址)

.rel.text重定位表,记录了.text中需要修改的位置(引用了其他编译单元的全局符号),链接时需要更改

.rel.data重定位表,记录了.data中需要修改的位置(定义或者引用的全局符号位置),链接时需要更改


链接分两种:

静态链接:加载前进行,各个编译单元生成可重定位目标文件(.a或者.obj)后,将它们组合成可执行文件。

动态链接:加载时进行,对于共享目标文件(.so或者.dll),静态链接阶段不处理这些文件,只是在生成的可执行文件中添加.interp段、.dynamic和.dynsym等段,其中记录关于动态链接器地址、依赖的共享目标文件与重定位信息和符号表等信息。

注:加载的意思就是创建进程,开始运行之前。

本文只谈静态链接,静态链接分为两个阶段:1. 虚拟空间分配 2. 符号解析及重定位

虚拟空间分配

由前一篇知,每个可重定位目标文件都包含了很多段,如.text/.data等,那么如何把多个可重定位文件合并为一个可执行文件呢?

如果按序叠加,也即所有可重定位文件原封不动摞在一起,这会导致段太多,且相同名称段分散。实际处理的办法是相似段合并

那么虚拟空间分配的工作就清楚了:

扫描所有可重定位文件,获取每个段的长度、属性和位置。

合并同名段,并给合并后的段分配一个虚拟地址(虚拟地址占据的位置和字节大小在ELF文件的数据结构中早就留好了),虚拟地址分配有规则(因为这里的虚拟地址对应的就是加载后进程的虚拟空间)。

收集各文件符号表所定义或者引用的所有全局符号到同一个符号表。

符号地址确定,由前一篇目标文件格式知道,每个可重定位文件都有一个符号表,里面存储全局符号的名字、属性和值(在xx段的偏移)。既然已经知道每一段的虚拟地址,那么可以计算得到每个符号的虚拟地址。

符号解析与重定位

既然所有全局符号的虚拟地址都知道了,那么接下来就需要进行符号重定位操作。

重定位:

原因:CPU执行代码段指令,指令中指明去哪个虚拟地址读取信息,可重定位文件代码段如果引用外部符号,那么外部符号的虚拟地址(符号定义的位置)就需要在知道每个符号的虚拟地址后进行更新(未知时填的是0)。

依据:由目标文件的格式知道,其包含重定位表,重定位表描述.text/.data段任何需要更改的指令的位置信息。

过程:

符号解析:每个重定位位置,都是对一个全局符号的引用,必须知道这个符号的虚拟地址,如果在全局符号表找不到这个符号,则报符号未定义错误

指令修正:按照重定位表给出的记录的修正方式进行修正,有多种方式。


动态链接的原因

由于代码量的膨胀,需要分为不同模块进行开发,最后将互相依赖的模块组合,这是链接的目的。前文讲了静态链接,在程序加载之前生成一个可执行文件。但是静态链接也有明显的弊端:

浪费内存和磁盘空间:任何一个可执行文件都需要拷贝使用到的目标文件。

不利于程序的发布和更新:如果更新了程序,那么必须重新获得可执行文件。

没有插件功能:不能够开放接口,将实现交给使用方自定义。

兼容性较差:程序在不同平台运行时,不能动态选择依赖的链接库。

动态链接:在程序加载时才进行链接(当然也可以在运行时进行手动动态链接),提高了灵活性,但也降低了启动速度。

动态链接的步骤:

加载可执行文件和动态链接库到内存;

将执行流程转到动态链接器(一段共享目标文件),其进行符号解析与重定位工作,不像静态文件一样直接转到main函数开始执行;

动态链接器工作完成后,转到main函数开始执行。

动态链接的核心问题

动态链接库的核心是如何生成地址无关的代码和数据,有两个问题需要解决:

共享目标文件是共享的,不应该进行重定位(加载地址相关)。

可执行文件的代码是不能进行重定位的。

问题1的解决办法:

模块内部的函数调用和变量引用采用相对地址;模块间的函数调用和变量引用采用间接方式访问,也即添加一个.got段(全局偏移表),其存储所引用符号的真正地址。那么共享文件的代码段和不可改数据段就可以共享了,.got段和可变数据才每个进程私有。

问题2的解决办法:

可执行文件不能重定位,那么其依赖的动态库符号必须认为是在可执行文件中定义。这就带来了一个新问题——共享目标文件也定义了此符号,怎么办?

解决办法就是认为所有共享目标文件定义的符号都是在可执行文件中定义。如果可执行文件中确实已经定义,将.got段符号真正地址设为可执行文件中的地址;如果可执行文件中未定义,那么将.got段符号地址设为共享目标文件中的地址。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容