1. 程式人生 > >Wine中PE格式檔案的載入(四):DLL的裝入和連線

Wine中PE格式檔案的載入(四):DLL的裝入和連線

在載入完PE可執行檔案後,回到kernel32的入口函式__wine_kernel_init中,接下來呼叫了函式LdrInitializeThunk。dll的裝入和連線過程主要是該函式實現的。

函式部分程式碼如下圖所示:

先用main_exe_file判斷主模組是否已經被建立了,這是在wine_process_init函式中被賦值的一個控制代碼型別。

get_moderf的作用正如註釋所說,是為可執行檔案分配一個模組的引用結構。

在Wine裝入和連線dll的時候,每個用到的dll以及exe,都對應於一個“模組的引用結構”,WINE_MODREF型別,並且裝載過程中也將單個dll或exe視作一個模組。結構如下圖所示:

其中LDR_MODULE型別來源於Windows。由於每個dll中都描述了其直接依賴的一組dll,所以需要用一個指標陣列deps來記錄。而nDeps顧名思義就是直接依賴的dll個數。

所以在裝入和連線過程中,應該是有這樣一顆樹

然後就是對PEB資訊進行填充。

該函式中直接負責dll裝載和連線的,只有fixup_imports。函式程式碼如下圖所示:

fixup_imports是用來將一個模組所依賴的所有模組都裝入連線進來。當然對於標誌位是LDR_DONT_RESOLVE_REFS,即不需要解析其引用的模組,或者該模組的引用數為0(這種情況當然只有ntdll.dll才可能),那麼就可以直接返回了。而對於大多數依賴其他dll的模組而言,接下來就是要裝入和連線它們了。

從LdrInitializeThunk看到,fixup_imports會從第一個模組,即exe開始,按照我們上面的圖的依賴關係把所有需要的dll都裝入連線起來。但這裡必然會有重複的情況,比如gdi32.dll依賴於ntdll.dll,而kernel32.dll也依賴於ntdll.dll。不過,這不是fixup_imports考慮的問題,它只管接下來呼叫import_dll,後者將按照匯入表imports[]中記錄的模組,將它們一一裝入,併為裝入的模組中的所有函式分配好地址空間。

import_dll中與連線相關的程式碼涉及到PE格式的細節,就不展開討論。總體上說,它會呼叫load_dll將目標模組裝入,如果目標模組還依賴於其他dll,那麼load_dll還將進一步裝入被依賴的dll,直到所有被依賴的dll都被裝入到程序空間為止。之後,import_dll再完成從函式名到地址的解析。迴圈往復過後,一個可執行檔案依賴的dll都將被裝入,用到的函式都將被譯成地址,用於程式執行過程中的呼叫。

前面有講到,EXE檔案的載入也呼叫到了load_dll,但沒有詳細展開。該函式程式碼如下圖所示:

首先,load_dll需要確定是否能找到需要裝載的dll,並且該dll是否已經被裝入,即呼叫find_dll_file。後者會根據程序引數塊(ppb)中指定的各個路徑,去查詢是否有該dll的檔案。如果找到,那麼pwm就不為空,說明該dll已經被裝入,不必再重複裝載,返回即可。

get_load_order這個函式是專門為wineconfig準備的。我們知道,用wineconfig是可以為每個應用程式配置各種dll的使用搭配,有些dll可以使用built-in的,有些則可以用native的。這些資訊最終都寫到登錄檔中,而get_load_order就會從登錄檔中讀取該dll的選用資訊。

這裡一個有意思的插曲,is_fake_dll函式判斷找到的dll檔案是否是假的dll。在~/.wine目錄生成後,其下的drive_c/windows/system32下面,就產生了很多PE格式的dll。這些dll通常只有幾k位元組,內容也是一些簡單的指令,很顯然是無法使用的。至於Wine為什麼要產生這些dll,還沒進行過深入的分析,但是如果find_dll_file找到的是這樣的dll,那麼下面就會當作沒有找到該dll處理。

下面的程式碼就是根據loadorder的值選擇裝入什麼格式的dll,native的或者built-in的。從函式名就可以看出,load_native_dll是用於裝入native dll的,而load_builtin_dll是用於裝入built-in dll的。在開頭提到過,Wine通過winebuilder將所有built-indll都做成了類似PE格式的dll,那麼為什麼這裡裝入時還有不同呢?

其實,built-in dll仍然是ELF格式,只是winebuilder刻意為它們各自寫入了一個類似於PE格式中的header,這樣,在裝入過程中,仍然可以獲得dll的描述資訊,寫到LDR_MODULE結構中。但是最終連線時,還是要根據ELF的頭部來解決最終的裝入連線問題。

因此可以看到,load_native_dll是呼叫Windows的 section系統呼叫將dll裝入(前面有講到),而load_builtin_dll則要通過wine_dll_load裝載。

至此dll的裝入連線過程就可以結束了。函式最後做了一個判斷,需要說明一下。其實這時候,再回顧一下前面的過程會發現,如果程式碼執行到這個判斷中,即nts == STATUS_SUCCESS的話,那麼肯定是這個dll被裝入到程序空間的那次。如果一個dll被多次引用而進入到load_dll的話,從第二次開始就不會進入到這個判斷了。所以對同一個dll,只會執行一次if語句塊中的程式碼。

參考:longene相容核心論壇