OpenCV400

本文深入探讨Visual Studio中Incremental Linking的机制,解释其如何通过在函数间添加padding来提高链接速度,特别是在调试阶段对代码进行小改动时。此外,文章还介绍了Incremental Linking如何与ILT表配合使用,以最小化函数重定位的影响。
VC versionVisual Studio Version
VC8Visual Studio 2005
VC10Visual Studio 2010
VC11Visual Studio 2012
VC12Visual Studio 2013
VC14Visual Studio 2015
VC15Visual Studio 2017

vc14(vs15) build only install 在debug mode下,没有opencv_world400d.dll
查看build solution的error,有个linker error:

LNK1210 exceeded internal ILK size limit; link with /incremental:no

Google 上搜了一下解决方案: Project Properties -> Configuration Properties ->
Linker (General) -> Enable Incremental Linking: 把 Yes
(/INCREMENTAL)改成 No (/INCREMENTAL:NO)

然后再build only install,opencv_world400d.dll就出来了!问题解决!

/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() { 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
进一步会造成程序执行效率下降。所以,我们应该在发布程序时,注意避免带上这个特性。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值