原創 C++應用程序在Windows下的編譯、連接(四)動態連接

4動態連接

4.1概述

在靜態連接階段,連接器爲PE文件生成了導入表,導出表,符號表,並調整了Call指令後面的操做數,在程序調用的時候,可以直接地或者間接地定位到IAT中的某個位置,在PE文件中,該位置包含符號的名稱,當PE文件加載到內存之後,該位置應該修正爲符號的地址。這些已有的信息和已經完成的工做是後續動態連接的基礎。程序員

動態連接的任務是:在程序的加載或者運行階段,執行各個模塊的基址重定位工做,並將IAT中的符號名稱修正爲動態連接庫中被調用的符號的地址。windows

動態連接分爲隱式動態連接和顯式動態連接,不管是隱式動態連接仍是顯式動態連接,都會涉及到對WindowsAPI函數:LoadLibrary(),GetProcAddress(),FreeLibrary()的調用。它們之間的區別是:在執行隱式動態連接的時候,由Windows加載器負責完成對這些Windows API的調用;而在顯式動態連接的時候,這些Windows API函數必須由程序員編寫代碼主動地調用。數組

隱式動態連接在程序加載到內存的時候完成,而顯式動態連接則將這一過程推遲到程序運行的過程當中。數據結構

動態連接的流程以下圖所示:函數

由上圖能夠看出,動態連接的兩個主要任務:動態連接庫加載完畢之後的基址重定位,以及對導入表中函數名稱的修正,即:將函數名稱轉換成函數的地址。佈局

4.2程序加載及基址重定位

PE文件具備段結構,包含的主要的段有:代碼段,數據段,導入表,導出表,符號表,基址重定位表等。當PE文件存儲在文件中或者被加載到內存中的時候,這些段都須要遵循某個對齊規則。操作系統

PE中規定了三類對齊:數據在內存中的對齊,數據在文件中的對齊,資源數據在資源文件中的對齊。調試

數據在內存中的對齊。在內存中,PE文件之內存頁的大小做爲對齊粒度。在Windows操做系統中,內存以分頁的方式進行管理。在32位Windows下,內存頁的大小是4KB;在64位Windows下,內存頁的大小是8KB。當PE文件被加載到內存之後,各段的起始地址必須是內存頁大小的整數倍。blog

數據在文件中的對齊。在文件中,PE文件以一個扇區的大小做爲對齊粒度。一個扇區的大小爲512字節,十六進制表示爲200h。在文件存儲中,各段的地址偏移必須是200h的整數倍。內存

資源數據在資源文件中的對齊。在資源文件中,資源字節碼以4字節做爲對齊粒度。

因爲在內存中PE文件以4KB做爲對齊粒度,而在文件中以512字節做爲對齊粒度,所以當PE文件被加載到內存之後,PE文件的尺寸要大於該文件在硬盤上的尺寸。在執行加載的時候,操做系統會讀取PE文件的頭信息,去除不須要加載到內存的部分,如:調試信息等,而後操做系統將整個PE文件映射到內存空間中。在將PE文件加載到內存之後,PE文件的結構和佈局不會被改變。所以,PE文件在硬盤上的數據結構與在內存中的數據結構是相同的。具體的對比狀況以下圖所示:

當PE文件被windows加載器加載到內存之後,在內存中的版本被稱爲模塊,每個模塊的起始內存地址被稱爲HMODULE。經過這個HMODULE能夠得到該模塊的數據內容。

當可執行程序被加載到內存之後,Windows會查詢PE文件中的相關信息,得到該可執行程序所依賴的動態連接庫,而後Windows將這些動態連接庫也加載到內存中。若是某個要被加載地動態連接庫已經位於內存中,那麼操做系統就增長這個動態連接庫的引用計數。當可執行程序和它所依賴的動態連接庫都被加載到內存之後,Windows加載器開始執行基址重定位工做。Windows加載器加載可執行程序以及動態連接庫的流程以下圖所示:

當可執行文件以及它所依賴的動態連接庫文件被加載到內存之後,若是該文件被加載的內存位置不是基於默認內存地址的位置(可執行程序是0x00400000h,動態連接庫是0x10000000),那麼windows加載器就必須執行基址重定位工做。該工做是在基址重定位表的支持下完成的。對於每個須要進行重定位的內存地址,加載器都會給它加上一個差值做爲修正。該差值爲模塊當前加載到內存的地址 – 模塊默認加載位置的地址。具體的操做流程以下圖所示:

當完成基址重定位工做之後,導出地址表(EAT)中的地址也被修正完畢。在PE文件中,這些地址是基於默認加載位置的地址,若是當前加載位置發生了變化,那麼這些地址是要被修正的。參見2.4.7節。

4.3符號解析

符號解析是動態連接中最重要的一環,在該階段,加載器讀取PE文件中的導入地址表(IAT)的信息,取得每個函數的名稱,而後用該函數的名稱去相關動態連接庫中查詢函數的地址,用得到的地址替換該函數名稱。這一步工做是將函數名稱替換爲函數地址的過程。具體過程以下圖所示:

因爲一個可執行程序可能會依賴多個動態連接庫,那麼在導入表數組中就會包含多個數組元素,因此在符號解析的時候是一個循環處理。加載器對導入表數組執行循環,取得每個數組元素,而後根據得到的Dll名稱查找到相關的動態連接庫的位置。得到了相關動態連接庫的信息之後,加載器從導入地址表中取得一個符號的名稱,以該符號名稱爲條件去對應的動態連接庫中查詢。通過對符號名稱表,名稱序號對應表,以及符號地址表的查詢,最後得到符號的地址,將此地址寫回到導入地址表原來符號名稱的位置。

    嗯,那個恭喜你,看完了。爲了寫這個東西,也把我累的夠嗆,哈哈。

相關文章
相關標籤/搜索