C++應用程式在Windows下的編譯、連結(四)動態連結
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名稱查詢到相關的動態連結庫的位置。獲得了相關動態連結庫的資訊以後,載入器從匯入地址表中取得一個符號的名稱,以該符號名稱為條件去對應的動態連結庫中查詢。經過對符號名稱表,名稱序號對應表,以及符號地址表的查詢,最後獲得符號的地址,將此地址寫回到匯入地址表原來符號名稱的位置。
嗯,那個恭喜你,看完了。為了寫這個東西,也把我累的夠嗆,哈哈。
相關文章
- C++應用程式在Windows下的編譯、連結:第三部分 靜態連結(一)C++Windows編譯
- C++應用程式在Windows下的編譯、連結:第三部分 靜態連結(二)C++Windows編譯
- C++應用程式在Windows下的編譯、連結:第一部分 概述C++Windows編譯
- C++應用程式在Windows下的編譯、連結:第二部分COFF/PE檔案結構C++Windows編譯
- Linux下的靜態連結與動態連結Linux
- Windows下的VC++動態連結庫程式設計WindowsC++程式設計
- C++編譯連結的那些小事 .C++編譯
- 在AndroidStudio下使用cmake編譯出靜態連結庫的方法Android編譯
- 程式的連結和裝入及Linux下動態連結的實現Linux
- cmake 連結動態連結庫
- 程式的編譯和連結原理分析編譯
- 關於程式的編譯和連結編譯
- Ubuntu中編譯連結Opencv應用的簡便方式Ubuntu編譯OpenCV
- 動態連結庫與靜態連結庫
- 靜態連結動態連結的連結順序問題和makefile示例
- 編譯連結過程編譯
- 編譯、連結學習筆記(一)簡述編譯連結過程編譯筆記
- 編譯 pyav 成 wheel 並使用 auditwheel 固化動態連結庫編譯
- C#呼叫C++動態連結庫C#C++
- 動態連結庫和靜態連結庫的區別
- 動態連結串列的建立(程式碼)
- linux下靜態連結庫和動態連結庫的區別有哪些Linux
- LINUX動態連結庫高階應用(轉)Linux
- ndk-build 編譯多個CPU架構的動態連結庫UI編譯架構
- Linux環境下:程式的連結, 裝載和庫[靜態連結]Linux
- 使用js動態新增連結隨機連結JS隨機
- golang可以呼叫C++的動態連結庫麼GolangC++
- 動態連結的相關結構
- 從編譯連結到cmake編譯
- 《程式設計師的自我修養》(一)——編譯與靜態連結程式設計師編譯
- 【連結 1】與靜態連結庫連結
- C/C++預處理、編譯、連結過程【Z】C++編譯
- 程式設計師的自我修養-編譯連結程式設計師編譯
- 【PB】powerbuilder呼叫VC編寫的動態連結庫UI
- (轉)編譯和連結的區別編譯
- 動態連結庫(DLL)
- 動態連結庫(轉)
- linux 下動態連結實現原理Linux