C++應用程式在Windows下的編譯、連結(四)動態連結

yangxi_001發表於2016-11-17

4動態連結

4.1概述

在靜態連結階段,連結器為PE檔案生成了匯入表,匯出表,符號表,並調整了Call指令後面的運算元,在程式呼叫的時候,能夠直接地或者間接地定位到IAT中的某個位置,在PE檔案中,該位置包含符號的名稱,當PE檔案載入到記憶體以後,該位置應該修正為符號的地址。這些已有的資訊和已經完成的工作是後續動態連結的基礎。

動態連結的任務是:在程式的載入或者執行階段,執行各個模組的基址重定位工作,並將IAT中的符號名稱修正為動態連結庫中被呼叫的符號的地址。

動態連結分為隱式動態連結和顯式動態連結,無論是隱式動態連結還是顯式動態連結,都會涉及到對WindowsAPI函式:LoadLibrary(),GetProcAddress(),FreeLibrary()的呼叫。它們之間的區別是:在執行隱式動態連結的時候,由Windows載入器負責完成對這些Windows API的呼叫;而在顯式動態連結的時候,這些Windows API函式必須由程式設計師編寫程式碼主動地呼叫。

隱式動態連結在程式載入到記憶體的時候完成,而顯式動態連結則將這一過程推遲到程式執行的過程中。

動態連結的流程如下圖所示:

由上圖可以看出,動態連結的兩個主要任務:動態連結庫載入完畢以後的基址重定位,以及對匯入表中函式名稱的修正,即:將函式名稱轉換成函式的地址。

4.2程式載入及基址重定位

PE檔案具有段結構,包含的主要的段有:程式碼段,資料段,匯入表,匯出表,符號表,基址重定位表等。當PE檔案儲存在檔案中或者被載入到記憶體中的時候,這些段都需要遵循某個對齊規則。

PE中規定了三類對齊:資料在記憶體中的對齊,資料在檔案中的對齊,資源資料在資原始檔中的對齊。

資料在記憶體中的對齊。在記憶體中,PE檔案以記憶體頁的大小作為對齊粒度。在Windows作業系統中,記憶體以分頁的方式進行管理。在32位Windows下,記憶體頁的大小是4KB;在64位Windows下,記憶體頁的大小是8KB。當PE檔案被載入到記憶體以後,各段的起始地址必須是記憶體頁大小的整數倍。

資料在檔案中的對齊。在檔案中,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名稱查詢到相關的動態連結庫的位置。獲得了相關動態連結庫的資訊以後,載入器從匯入地址表中取得一個符號的名稱,以該符號名稱為條件去對應的動態連結庫中查詢。經過對符號名稱表,名稱序號對應表,以及符號地址表的查詢,最後獲得符號的地址,將此地址寫回到匯入地址表原來符號名稱的位置。

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

相關文章