incremental linking(增量链接)的作用

转:incremental linking(增量链接)的作用

今天编译一个C++程序时,报了一个奇怪的错误(之前是好好的):

1>LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt

Google上搜了一下解决方案:

把Project Properties -> Configuration Properties  -> Linker (General) -> Enable Incremental Linking中的Yes (/INCREMENTAL)改成No (/INCREMENTAL:NO)

果然管用!

但又有个新的疑问出现了:Incremental Linking是什么?它有什么作用?

大概总结一下:

Incremental Linking翻译成中文就是“增量链接”,是一个链接的参数选项,作用就是为了提高链接速度的。什么意思呢?不选用增量链接时,每次修改或新增代码后进行链接时会重新洗牌,把原来的.exe删了,重新链接成一个新的.exe,这样对于大型项目来说链接会比较慢。而选用增量链接时,在对代码做小的改动时会把新成的函数或数据穿插到已有的.exe中,而不重新生成.exe,只有做了大量修改时才可能会重新编排,这样就可以提高链接的速度。

一般VS的默认设置会把Debug版的Incremental Linking设置成Yes (/INCREMENTAL),而把Release版的设置成No (/INCREMENTAL:NO)。

关于Incremental Linking更详细的介绍和分析请参考另一位博主的原文:

http://www.cnblogs.com/Dahaka/archive/2011/08/01/2124256.html

为方便阅读,直接将这文章拷贝了一份:

好的,文接上回,本文我就来讲讲微软link.exe连接器的Incremental Liking这个特性。当然这个其实不是微软linker独有的特性,很多链接器都有这个特性,这个特性实际上是为了提高链接速度的。

想象一下这个场景,我写了两个函数foo()和bar(),其中foo()在0x400100处而bar()紧接着保存在0x400200处。现在我将foo()改写了一下,添加了一些perfect的功能,然后编译了新的代码。不过现在的麻烦是foo()不可避免的变大了,他现在需要200h字节来保存了。那么链接器该怎么办?

一般的思考是——重新洗牌,将现有的编译好的exe删除了,然后重新布局所有的函数,也即是说bar()函数向后挪动0x100h字节的位置,给foo()腾出空间来。然后之后所有的函数都需要重新定位……对于大型软件来说这个处理时间开销是痛苦的,但作为程序员我们却不能避免需要不断的调试改代码,不断地重复这个耗时的工作。

不过我们现在并不需要给客户最终的发行代码,我们只是想要尽快地将程序的bug改掉然后去休息而已!于是,Incremental Linking出现了!它的原理如下:

现在连接器不会将所有函数紧挨着放在一块儿了,他们会在函数之间加上padding,这个时候函数要想添几句指令就有余地了。只要我们的改动不大,没有超过padding的范围连接器就不需要重新洗牌,这大大提高了链接的速度。

先别高兴,加入我们的改动很大,以至于超过padding能够搞定的范围怎么办?如上图,我们还会在整个section末尾设置一个较大的padding(当然具体在哪里要看实现,比如我这图是从GCC那里搞得,说的就是ld.exe的行为方式),这时候就可以将这个函数搬到这里来了。但有个毁灭性的问题——所有调用我这个函数的函数都必须重定位他们的call指令啊!

为了解决这个问题,我们引入了一个ILT表(Incremental Linking Table),这个表是放在.text区域中的(我在IDA中观察得知)。它的原理是什么呢?我们来看:

;之前我们都是直接调用函数
   call foo

;现在我们来点小把戏
   call foo_stub

foo_stub:
   jmp foo

我们现在不直接调用函数,而是call到一个包含jmp指令的地方,然后由这个指令将我们的程度带往foo()函数的实现去。现在如果我们将foo()的实现改动过大后,linker直接将foo()移动了,然后只需要修改这个jmp指令就行了。可以看到,这种实现方式开销是O(1)。然后当很多个函数都用这种方式时,就形成了一个有jmp指令构成的表——这就是ILT表啦。

有兴趣的童鞋可以做下实验,在VS2010编译一次代码,然后用IDA或者W32Dasm之类的软件可以看到两个函数之间间隔了不少距离,而这些间隔就是我们所谓padding。padding被填充以0xCCh的数据。熟悉win32汇编的朋友这时候该笑而不语了,是的,这个值就是指令INT 3。在WIndows下,执行这个指令会引发一个异常,然后程序会被终止或是回到调试器去,这当然是出于安全性考虑的。这之后如果你在前一个函数加几句话,编译后可以看到两个函数位置不变,但函数间的padding变小了。

和.textbss的关系

嘛,之前有篇我讨论了PE常见的section,里面提到了这个节,下面我就详细介绍一下它的作用。

首先Incremental Linking作用不仅仅是在于减少我们重新连接程序所需要的时间,他还是我们调试时能够动态改动代码的前提。不知你还记得不,在那个炎热的夏天,你正汗流浃背地在没有空调的部屋里调试C代码(咳,说远了……)你直接修改了代码,然后VS直接在调试的时候将你的改动反映到程序里去了。这就是VS在Debug模式时动态编译代码的功能。

实际上这个功能是基于Incremental Linking机制的,而且是使用的Incremental Linking的第二套方案——直接找个大的地儿把修改的函数挪过去。

但是和.textbss有啥关系?

首先我们看到,.textbss有关键字bss,这就说明实际上这个节没有占据实际的硬盘空间。然后text关键字告诉我们这里段是包含代码的,另外用工具查知这个段有可执行属性更是印证了这个观点。没有代码,那要这个节有啥用呢?

你想到了么?是的,在VS动态编译的时候,他直接将被修改的函数放到了.textbss节里,然后修改了对应的ILT表项,是他指向这个位置。

说是简单,但实际上这个过程还个细节需要注意——你把我的函数挪地儿了,要是我正在执行这个函数怎么办?实际上,在改了ILT之后立刻会做的,就是检查当前程序所有线程的TIB,如果他们的EIP指向老的函数(它们正在执行老版本函数),我们就修改EIP使其指向新版本函数的对应位置。当然,这实际上暗示了,这个工作非要在调试程序的帮助下不可了。注意动态编译的功能只在Debug版本程序下有效,Release版本是不行的,因为Release版本默认禁用Incremental Linking。

下面是小亡的实验时间,以验证我的观点:

我就用手头上的程序来测试。有函数CheckValidPE(),它的RVA是0x12490h(你可理解为内存地址)。我的程序地址空间部分如下:

Section        RVA      Size

.textbss      1000h     10000h

.text        11000h     7000h

可以看到这两个函数实际上是位于.text节内的。我在CheckValidPE()上下个断,可以看到:

bool PEAnalyser::checkValidPE()
{
00D92490  push        ebp
00D92491  mov         ebp,esp
00D92493  sub         esp,0FCh
00D92499  push        ebx
00D9249A  push        esi
00D9249B  push        edi
00D9249C  push        ecx
......

从这里我发觉了VS貌似从来都是随机装载PE映像的ImageBase位置的,搞得我之前一次实验满心欢喜用常用的0x400000h为基址换算了所有的RVA~~T_T。。。

我之前说了CheckValidPE()的RVA是0x12490h,这里的绝对地址是0x00D92490h,我们用0x00D92490h-0x12490h = 0xD80000h,得到两个的差值。哈哈,看来这次的映像基址被射到了0xD80000h。

我回到VS源代码视图上,对代码稍作粉饰,嗯,然后它发生了!!!

bool PEAnalyser::checkValidPE()
{
00D81000  push        ebp
00D81001  mov         ebp,esp
00D81003  sub         esp,0FCh
00D81009  push        ebx
00D8100A  push        esi
00D8100B  push        edi
00D8100C  push        ecx
00D8100D  lea         edi,[ebp-0FCh]
00D81013  mov         ecx,3Fh
00D81018  mov         eax,0CCCCCCCCh
00D8101D  rep stos    dword ptr es:[edi]
.....

注意看函数变到这个地址来了!!而且VS的调试程序指针也确实说明EIP被更改了。

我们来算一下,看看这个地址在哪里?用这里新的函数起始地址减去我们之前计算的的基址得到RVA = 0xD81000h - 0xD80000h = 0x1000h

回去查一下我的内存地址空间,RVA为0x1000h正是位于.textbss节的起始位置。看来我的猜测是正确的。

小结一下,关于Incremental Linking,由于他的机制所致,势必带来程序体积的臃肿以及执行的低效,但是由于我们只是在Debug程序是使用,所以问题不大。另外VS默认是在Debug是开启Incremental Linking而Release模式关闭这个特性的。这说明在Release时,我们不能够动态的改动代码了。

另外注意Incremental Linking是和**/LTCG** 选项不兼容的,你不能同时开启Incremental Linking和Link Time Code Generation,从这个角度讲,使用Incremental Linking进一步会造成程序执行效率下降。所以,我们应该在发布程序时,注意避免带上这个特性。

原文地址:https://www.cnblogs.com/2018shawn/p/9651786.html

时间: 2024-10-11 22:57:02

incremental linking(增量链接)的作用的相关文章

【转】关于增量链接(incremental linking)

增量链接(Incremental Linking)这个词语在使用Visual C++时经常会遇到(其实不只是VS系列,其它链接器也有这个特性), 就比如经常遇到的:上一个增量链接没有生成它, 正在执行完全链接.  但是它是什么意思呢? 很多人只是经常看到, 但是不明白这是什么. 首先说明一下, 这个特性是链接器为了<提高链接速度>而增加的功能. 说是这样, 可能不理解, 下面就说说其原理: 有一个源代码文件, 你写了许多许多行, 你正在调试它, 你突然发现某个地方有一个小BUG, 你打算改正,

VS中incremental linking的作用

今天编译一个C++程序时,报了一个奇怪的错误(之前是好好的): 1>LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt Google上搜了一下解决方案: 把Project Properties -> Configuration Properties  -> Linker (General) -> Enable Incremental Linking中的Yes (

增量链接

连接器不会将所有函数紧挨着放在一块儿了,他们会在函数之间加上padding,这个时候函数要想添几句指令就有余地了.只要我们的改动不大,没有超过padding的范围连接器就不需要重新洗牌,这大大提高了链接的速度 假如我们的改动很大,以至于超过padding能够搞定的范围怎么办?如上图,我们还会在整个section末尾设置一个较大的padding(当然具体在哪里要看实现,比如我这图是从GCC那里搞得,说的就是ld.exe的行为方式),这时候就可以将这个函数搬到这里来了 但有个毁灭性的问题——所有调用

解释器编译器区别,链接器作用。

编译器是把源程序的每一条语句都编译成机器语言,并保存成二进制文件,这样运行时计算机可以直接以机器语言来运行此程序,速度很快; 而解释器则是只在执行程序时,才一条一条的解释成机器语言给计算机来执行,所以运行速度是不如编译后的程序运行的快的.这是因为计算机不能直接认识并执行我们写的语句,它只能认识机器语言(是二进制的形式) 链接器作用: 一个大的程序往往会分为多个源程序文件来编写,因而需要对多个源程序分别进行编译或汇编,来生成多个不同的目标文件(.o文件),这些目标文件包含指令.数据.和其他说明信息

SEO之友情链接的作用及筛选技巧

seoer都知道交换友情链接有利于网站权重提升,但是我相信大多数都不知道交换友情链接为什么可以提高权重,今天,我们就来学习下友情链接是在怎么提升网站权重的,以及网站选这交换友情链接该怎么选择,有哪些需要注意的事项.首先,我们先来了解下友情链接是如何能够提升权重的.通常,我们交换友情链接是为了对方网站的权重能传递到我们自己的网站,提升网站关键词排名,从而提升我们网站的权重,众所周知,一个高质量的友情链接能提高自己网站在各大搜索引擎中的权重,能优化你的关键词排名,增加自己在行业中的曝光量.那么问题来

为什么友情链接能提升百度排名、有帮助的作用

为什么会有搜索引擎的出现 ??在早期的互联网生态里面,企业网站的诞生要比搜索引擎出现的更早,在各行各业的网站逐渐增多之后,用户无法记住较多网站域名地址,于是网址导航就诞生了,再逐渐的才有了搜索引擎,搜索引擎诞生的理由是它能方便快捷的让用户搜索到他们想要去的网站,但是提供服务前,搜索引擎需要大量收录网站,于是就展开了一系列的模块化工作,今天宵云科技就为大家随意揣测一下搜索引擎的工作原理. ??搜索引擎工作原理猜想:收录→归档→展示→ ??一:收录??搜索引擎为了提供良好的检索服务,需要收录越来越多

OCP知识点讲解 之 检查点队列与增量检查点

原创 http://blog.chinaunix.net/uid-26762723-id-3271558.html 检查点的主要目的是以对数据库的日常操作影响最小的方式刷新脏块.脏块不断的产生,如何将脏块刷新到磁盘中去呢?在8i之前,Oracle定期的锁住所有的修改操作,刷新Buffer cache中的所有脏块,这种刷新脏块的方式被称为完全检查点,这极大的影响了效率,从9i之后只有当关闭数据库时才会发生完全检查点. 从8i开始,Oracle增加了增量检查点的概念,增量检查点的主要宗旨就是定期的刷

VS2008执行MFC程序,提示microsoft incremental linker已停止工作解决方法

事实上这边是由于设置有问题.详细的解决方式例如以下: 第一步:点击项目->"你的文件"属性->配置属性->链接器->启用增量链接   将  是(/INCREMENTAL)改为 否(/INCREMENTAL:NO) 第二步:点击项目->"你的文件"属性->配置属性->C/C++  将调试信息格式改为程序数据库(/Zi) 改完之后.又一次调试一下程序,是不是问题已经攻克了? 下面附上截图:

gcc/g++等编译器 编译原理: 预处理,编译,汇编,链接各步骤详解

摘自http://blog.csdn.net/elfprincexu/article/details/45043971 gcc/g++等编译器 编译原理: 预处理,编译,汇编,链接各步骤详解 C和C++编译器是集成的,编译一般分为四个步骤: 预处理(preprocessing)  ----------------- cpp/ gcc -E  编译(compilation) ------------------ cc1 / gcc -S 汇编(assembly)  ----------------