天天看點

關于Visual C++增量連結以及.textbss

關于Visual C++增量連結以及.textbss

 好的,文接上回,本文我就來講講微軟link.exe連接配接器的Incremental Liking這個特性。當然這個其實不是微軟linker獨有的特性,很多連結器都有這個特性,這個特性實際上是為了提高連結速度的。

  想象一下這個場景,我寫了兩個函數foo()和bar(),其中foo()在0x400100處而bar()緊接着儲存在0x400200處。現在我将foo()改寫了一下,添加了一些perfect的功能,然後編譯了新的代碼。不過現在的麻煩是foo()不可避免的變大了,他現在需要200h位元組來儲存了。那麼連結器該怎麼辦?

  一般的思考是——重新洗牌,将現有的編譯好的exe删除了,然後重新布局所有的函數,也即是說bar()函數向後挪動0x100h位元組的位置,給foo()騰出空間來。然後之後所有的函數都需要重新定位……對于大型軟體來說這個處理時間開銷是痛苦的,但作為程式員我們卻不能避免需要不斷的調試改代碼,不斷地重複這個耗時的工作。

  不過我們現在并不需要給客戶最終的發行代碼,我們隻是想要盡快地将程式的bug改掉然後去休息而已!于是,Incremental Linking出現了!它的原理如下:

關于Visual C++增量連結以及.textbss

  現在連接配接器不會将所有函數緊挨着放在一塊兒了,他們會在函數之間加上padding,這個時候函數要想添幾句指令就有餘地了。隻要我們的改動不大,沒有超過padding的範圍連接配接器就不需要重新洗牌,這大大提高了連結的速度。

  先别高興,加入我們的改動很大,以至于超過padding能夠搞定的範圍怎麼辦?如上圖,我們還會在整個section末尾設定一個較大的padding(當然具體在哪裡要看實作,比如我這圖是從GCC那裡搞得,說的就是ld.exe的行為方式),這時候就可以将這個函數搬到這裡來了。但有個毀滅性的問題——所有調用我這個函數的函數都必須重定位他們的call指令啊!

  為了解決這個問題,我們引入了一個ILT表(Incremental Linking Table),這個表是放在.text區域中的(我在IDA中觀察得知)。它的原理是什麼呢?我們來看:

關于Visual C++增量連結以及.textbss
;之前我們都是直接調用函數
   call foo

;現在我們來點小把戲
   call foo_stub

foo_stub:
   jmp foo      
關于Visual C++增量連結以及.textbss

  我們現在不直接調用函數,而是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()上下個斷,可以看到:

關于Visual C++增量連結以及.textbss
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  
......      
關于Visual C++增量連結以及.textbss

  從這裡我發覺了VS貌似從來都是随機裝載PE映像的ImageBase位置的,搞得我之前一次實驗滿心歡喜用常用的0x400000h為基址換算了所有的RVA~~T_T。。。

  我之前說了CheckValidPE()的RVA是0x12490h,這裡的絕對位址是0x00D92490h,我們用0x00D92490h-0x12490h = 0xD80000h,得到兩個的內插補點。哈哈,看來這次的映像基址被射到了0xD80000h。

  我回到VS源代碼視圖上,對代碼稍作粉飾,嗯,然後它發生了!!!

關于Visual C++增量連結以及.textbss
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]  
.....      
關于Visual C++增量連結以及.textbss

  注意看函數變到這個位址來了!!而且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進一步會造成程式執行效率下降。是以,我們應該在釋出程式時,注意避免帶上這個特性。

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

繼續閱讀