LoadLibrary深入案例詳解
LoadLibrary流程分析
在Windows開(kāi)發(fā)中,我們都有過(guò)一個(gè)規(guī)定:在DllMain中不應(yīng)該處理過(guò)于復(fù)雜的事情,防止死鎖的發(fā)生。
那么,到底為什么DllMain中容易導(dǎo)致死鎖呢?下面我們來(lái)分析一下LoadLibrary的整個(gè)流程和原理。
1. 使用
我們來(lái)看一下LoadLibrary怎么使用的,由于這個(gè)函數(shù)底層是調(diào)用LoadLibraryEx我們看LoadLibraryEx的使用情況。
1.1 聲明
HMODULE WINAPI LoadLibraryEx( _In_ LPCTSTR lpFileName, _Reserved_ HANDLE hFile, _In_ DWORD dwFlags );
這里主要第三個(gè)參數(shù)使用起來(lái)有需要注意的地方:
- DONT_RESOLVE_DLL_REFERENCES : 這個(gè)標(biāo)志用于告訴系統(tǒng)將DLL映射到調(diào)用進(jìn)程的地址空間中,但是不調(diào)用DllMain并且不加載依賴(lài)Dll(只映射自己本身)。
- LOAD_LIBRARY_AS_DATAFILE : 這個(gè)標(biāo)志與DONT_RESOLVE_DLL_REFERENCES標(biāo)志相類(lèi)似,因?yàn)橄到y(tǒng)只是將DLL映射到進(jìn)程的地址空間中,就像它是數(shù)據(jù)文件一樣。系統(tǒng)并不花費(fèi)額外的時(shí)間來(lái)準(zhǔn)備執(zhí)行文件中的任何代碼。
- LOAD_LIBRARY_SEARCH_USER_DIRS : 搜索路徑的使用使用AddDllDirectory和SetDllDirectory設(shè)置的路徑(保護(hù)Dll自己和依賴(lài)Dll)。
- LOAD_LIBRARY_SEARCH_SYSTEM32 : 從%windows%\system32加載Dll和其依賴(lài)項(xiàng)。
- LOAD_LIBRARY_SEARCH_APPLICATION_DIR : 應(yīng)用程序安裝路徑搜索Dll和其依賴(lài)項(xiàng)。
- LOAD_WITH_ALTERED_SEARCH_PATH : 按照如下目錄搜索:
- 進(jìn)程當(dāng)前目錄。
- Windows的系統(tǒng)目錄。
- 16 位Windows的系統(tǒng)目錄。
- Windows目錄。
- path環(huán)境變量目錄。
默認(rèn)情況下,LoadLibrary和LoadLibrary按照如下目錄搜索:
- 進(jìn)程當(dāng)前目錄。
- SetDllDirectory設(shè)置的文件夾路徑。
- Windows的系統(tǒng)目錄。
- 16 位Windows的系統(tǒng)目錄。
- Windows目錄。
- path環(huán)境變量目錄。
1.2 SetDllDirectoryW
這個(gè)函數(shù)的實(shí)現(xiàn)如下:
其中KernelBaseGetGlobalData返回的結(jié)果信息如下:
如下信息為:
0:003> dd KERNELBASE!KernelBaseGlobalData 75b155a0 00000000 00000000 00160014 7f9a1240 75b155b0 00280026 7f9a1260 00000000 00000000 75b155c0 00000000 00e60000 75b156c0 7fff0000 75b155d0 00c4b494 0000011a 00cc1914 0000012c 75b155e0 00cb9f34 00000253 00cc2658 00cc5e20 75b155f0 00000000 00e84380 0000000f ffffffff 75b15600 ffffffff 00000000 00000000 00000000 75b15610 020007d0 75950000 00000000 00000000 0:003> du 7f9a1240 7f9a1240 "C:\Windows" 0:003> du 7f9a1260 7f9a1260 "C:\Windows\system32"
2. LoadLibrary分析
下面來(lái)分析一下這個(gè)函數(shù)的執(zhí)行過(guò)程:
HMODULE __stdcall LoadLibraryW(LPCWSTR lpLibFileName) { return LoadLibraryExW(lpLibFileName, 0, 0); }
2.1 LoadLibraryExW
對(duì)于LoadLibraryW的主要流程如下:
HMODULE __stdcall LoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) { //... SearchPath = BaseGetProcessDllPath( &dwFlags, (chFlags & LOAD_WITH_ALTERED_SEARCH_PATH) != 0 ? DllName.Buffer : 0, 0, (int)&dwFlags); //... ntStatus = LdrLoadDll(SearchPath, (PULONG)&lpLibFileName, &DllName, &hFile); }
通過(guò)BaseGetProcessDllPath獲取到的路徑為:
00072acc "C:\Users\xxx\Desktop;C:\Windows\" 00072b0c "system32;C:\Windows\system;C:\Wi" 00072b4c "ndows;.;C:\Windows\system32;C:\W" 00072b8c "indows;C:\Windows\System32\Wbem;" 00072bcc "C:\Windows\System32\WindowsPower" 00072c0c "Shell\v1.0\"
接下來(lái)就是使用LdrLoadDll價(jià)值dll了。
2.2 LdrLoadDll
NTSTATUS __stdcall LdrLoadDll(PWSTR SearchPath, PULONG LoadFlags, PUNICODE_STRING DllName, PVOID *BaseAddress) { //... if ( SearchPath ) { result = RtlInitUnicodeStringEx(&DestinationString, SearchPath); if ( result < 0 ) return result; NewSearchPath = &DestinationString; } else { NewSearchPath = &LdrpDefaultPath; } //... v7 = LdrpLoadDll(DllName, (int)NewSearchPath, v6, 1, 0, (int)&DllName); //... return v7; }
這里有一個(gè)默認(rèn)的加載路徑為L(zhǎng)drpDefaultPath,路徑信息如下:
0:000> dS LdrpDefaultPath 00061560 "C:\Users\xxx\Desktop;C:\Windows\" 000615a0 "system32;C:\Windows\system;C:\Wi" 000615e0 "ndows;.;C:\Windows\system32;C:\W" 00061620 "indows;C:\Windows\System32\Wbem;" 00061660 "C:\Windows\System32\WindowsPower" 000616a0 "Shell\v1.0\"
2.3 LdrpLoadDll
int __stdcall LdrpLoadDll(PCUNICODE_STRING Source, int a2, int a3, char a4, int a5, int a6) { //... if ( !LdrpInLdrInit ) RtlEnterCriticalSection(&LdrpLoaderLock); //... LdrpFindOrMapDll(*(PCUNICODE_STRING *)((char *)&v31 + 1), v29, a3, v27[0], (int)&v33, (int)&v31); //... if ( v21 & 0x1000000 ) v22 = LdrpCorProcessImports((void *)v21, v33); else v22 = LdrpProcessStaticImports(v33, v29); //... LdrpRunInitializeRoutines(0); //... if ( !LdrpInLdrInit ) RtlLeaveCriticalSection(&LdrpLoaderLock); }
這個(gè)函數(shù)的整理流程如下:
獲取加載鎖RtlEnterCriticalSection(&LdrpLoaderLock);嘗試加載dll: LdrpFindOrMapDll。處理導(dǎo)入表信息。運(yùn)行回調(diào)函數(shù)LdrpRunInitializeRoutines。釋放鎖RtlLeaveCriticalSection(&LdrpLoaderLock);。
這里值得注意的地方就是LdrpRunInitializeRoutines是調(diào)用DllMain函數(shù),調(diào)用堆棧信息如下:
# ChildEBP RetAddr Args to Child 00 0029d718 67f42c22 67ef0000 00000001 00000000 DllTest!DllMain 01 0029d75c 67f42def 67ef0000 00000001 00000000 DllTest!dllmain_dispatch+0xb2 02 0029d770 777b89d8 67ef0000 00000001 00000000 DllTest!_DllMainCRTStartup+0x1f 03 0029d790 777c5c41 67f3dcc5 67ef0000 00000001 ntdll!LdrpCallInitRoutine+0x14 04 0029d884 777c052e 00000000 73bd12d3 777a7c9a ntdll!LdrpRunInitializeRoutines+0x26f 05 0029d9f0 777c232c 0029da50 0029da1c 00000000 ntdll!LdrpLoadDll+0x4d1 06 0029da24 75b688ee 00072acc 0029da64 0029da50 ntdll!LdrLoadDll+0x92 07 0029da5c 763d3c12 00000000 00000000 00000001 KERNELBASE!LoadLibraryExW+0x15a
加載dll的時(shí)候,會(huì)獲取鎖LdrpLoaderLock;然而加載的時(shí)候又會(huì)調(diào)用LdrpRunInitializeRoutines進(jìn)入DllMain。
2.4 LdrpFindOrMapDll
加載和映射的dll的函數(shù)是LdrpFindOrMapDll,這個(gè)函數(shù)在加載Dll之前,需要判斷Dll是否被加載過(guò), 已經(jīng)加載的dll,通過(guò)一個(gè)哈希表加載,哈希表如下:
LIST_ENTRY LdrpHashTable[LDR_HASH_TABLE_ENTRIES];
其實(shí)每個(gè)LdrpHashTable都是通過(guò)一個(gè)_LDR_DATA_TABLE_ENTRY的成員HashLinks鏈接起來(lái),結(jié)構(gòu)如下:
0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY +0x000 InLoadOrderLinks : _LIST_ENTRY +0x008 InMemoryOrderLinks : _LIST_ENTRY +0x010 InInitializationOrderLinks : _LIST_ENTRY +0x018 DllBase : Ptr32 Void +0x01c EntryPoint : Ptr32 Void +0x020 SizeOfImage : Uint4B +0x024 FullDllName : _UNICODE_STRING +0x02c BaseDllName : _UNICODE_STRING +0x034 Flags : Uint4B +0x038 LoadCount : Uint2B +0x03a TlsIndex : Uint2B +0x03c HashLinks : _LIST_ENTRY //LdrpHashTable連接的列表結(jié)構(gòu) +0x03c SectionPointer : Ptr32 Void +0x040 CheckSum : Uint4B +0x044 TimeDateStamp : Uint4B +0x044 LoadedImports : Ptr32 Void +0x048 EntryPointActivationContext : Ptr32 _ACTIVATION_CONTEXT +0x04c PatchInformation : Ptr32 Void +0x050 ForwarderLinks : _LIST_ENTRY +0x058 ServiceTagLinks : _LIST_ENTRY +0x060 StaticLinks : _LIST_ENTRY +0x068 ContextInformation : Ptr32 Void +0x06c OriginalBase : Uint4B +0x070 LoadTime : _LARGE_INTEGER
對(duì)于每個(gè)加載的DLL,都會(huì)有兩種形式:
- dll名稱(chēng)加載。
- dll全路徑加載。
在LdrpFindLoadedDllByName也會(huì)根據(jù)不同的加載來(lái)匹配不同的情況:
- 如果使用dll名稱(chēng)加載,那么比較BaseDllName;使用RtlEqualUnicodeString.
- 如果使用dll全路徑加載,那么比較FullDllName;使用RtlEqualUnicodeString.
例如:
0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY 006f6270 +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x6f6358 - 0x6f5eb8 ] +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x6f6360 - 0x6f5ec0 ] +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x6f6e38 - 0x6f6368 ] +0x018 DllBase : 0x75b30000 Void +0x01c EntryPoint : 0x75b43273 Void +0x020 SizeOfImage : 0x110000 +0x024 FullDllName : _UNICODE_STRING "C:\Windows\syswow64\kernel32.dll" //全路徑匹配這個(gè) +0x02c BaseDllName : _UNICODE_STRING "kernel32.dll" //Dll名稱(chēng)匹配這個(gè) +0x034 Flags : 0x84004 +0x038 LoadCount : 0xffff +0x03a TlsIndex : 0 +0x03c HashLinks : _LIST_ENTRY [ 0x77c148a0 - 0x77c148a0 ] +0x03c SectionPointer : 0x77c148a0 Void +0x040 CheckSum : 0x77c148a0 +0x044 TimeDateStamp : 0x589c961f +0x044 LoadedImports : 0x589c961f Void +0x048 EntryPointActivationContext : (null) +0x04c PatchInformation : (null) +0x050 ForwarderLinks : _LIST_ENTRY [ 0x718580 - 0x718580 ] +0x058 ServiceTagLinks : _LIST_ENTRY [ 0x6f62c8 - 0x6f62c8 ] +0x060 StaticLinks : _LIST_ENTRY [ 0x6f63f0 - 0x6f62f0 ] +0x068 ContextInformation : 0x77b4ef40 Void +0x06c OriginalBase : 0x7dd60000 +0x070 LoadTime : _LARGE_INTEGER 0x01d48576`ee26eab0
如果,這里匹配成功了,那么Dll就不會(huì)再加載了。
3. 總結(jié)
3.1 同名DLL
一個(gè)進(jìn)程是否可以加載相同名字的Dll呢?按照上面分析有一個(gè)加載規(guī)則
在LdrpFindLoadedDllByName也會(huì)根據(jù)不同的加載來(lái)匹配不同的情況:
- 如果使用dll名稱(chēng)加載,那么比較BaseDllName;使用RtlEqualUnicodeString.
- 如果使用dll全路徑加載,那么比較FullDllName;使用RtlEqualUnicodeString.
那么可以從這個(gè)規(guī)則來(lái)看,是看加載是的方式:
int main(int args, char* argv[]) { LoadLibraryW(L"C:\\DllTest.dll"); LoadLibraryW(L"DllTest.dll"); system("pause"); return 0; }
這種方式,第二個(gè)應(yīng)該LoadLibraryW(L"DllTest.dll")不會(huì)去匹配加載了,如下:
那么我們按照這種方式加載呢?
int main(int args, char* argv[]) { LoadLibraryW(L"DllTest.dll"); LoadLibraryW(L"C:\\DllTest.dll"); system("pause"); return 0; }
按照規(guī)則來(lái)說(shuō),應(yīng)該是可以加載的,如下:
這個(gè)中情況在wow64 情況下,有時(shí)候會(huì)踩到坑,例如,對(duì)于wowo64路徑,如果直接使用C:\Windows\system32\kernel32.dll路徑加載,那么LdrpFindLoadedDllByName一定會(huì)失敗,因?yàn)镕ullDllName為wow64使用的值:C:\Windows\syswow64\kernel32.dll.
3.2 死鎖
我們知道,在調(diào)用Dllmain的時(shí)候,會(huì)占用鎖RtlEnterCriticalSection(&LdrpLoaderLock),所有凡是在DllMain中調(diào)用可能獲取RtlEnterCriticalSection(&LdrpLoaderLock)的操作,就可能會(huì)導(dǎo)致死鎖,例如:
- 直接調(diào)用LoadLibrary(Ex)。
- 調(diào)用CoInitializeEx(在CoInitializeEx底層會(huì)調(diào)用LoadLibraryEx)。
- 調(diào)用CreateThread(因?yàn)镃reateThread會(huì)繼續(xù)調(diào)用DllMain,稍有不慎可能就會(huì)死鎖)。
- 調(diào)用GetModuleFileName、GetModuleHandle 等(底層獲取RtlEnterCriticalSection(&LdrpLoaderLock)鎖)。
到此這篇關(guān)于LoadLibrary深入案例詳解的文章就介紹到這了,更多相關(guān)LoadLibrary詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VisualStudio2019配置OpenCV4.5.0的方法示例
這篇文章主要介紹了VisualStudio2019配置OpenCV4.5.0的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03C++11中移動(dòng)構(gòu)造函數(shù)案例代碼
C++11 標(biāo)準(zhǔn)中為了滿(mǎn)足用戶(hù)使用左值初始化同類(lèi)對(duì)象時(shí)也通過(guò)移動(dòng)構(gòu)造函數(shù)完成的需求,新引入了 std::move() 函數(shù),它可以將左值強(qiáng)制轉(zhuǎn)換成對(duì)應(yīng)的右值,由此便可以使用移動(dòng)構(gòu)造函數(shù),對(duì)C++11移動(dòng)構(gòu)造函數(shù)相關(guān)知識(shí)感興趣的朋友一起看看吧2023-01-01C++利用鏈表實(shí)現(xiàn)圖書(shū)信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++利用鏈表實(shí)現(xiàn)圖書(shū)信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11Qt自定義Widget實(shí)現(xiàn)互斥效果詳解
在使用Qt時(shí),可能會(huì)遇到這種問(wèn)題:多個(gè)控件互斥,類(lèi)似于QRadiButton控件,但又不是單純的QRadioButton控件,互斥的可能是一個(gè)窗口,也可能是幾個(gè)按鈕,等等多種情況。本文將介紹利用Qt自定義Widget實(shí)現(xiàn)的互斥效果,需要的可以參考一下2022-01-01C++中main函數(shù)怎樣調(diào)用類(lèi)內(nèi)函數(shù)
這篇文章主要介紹了C++中main函數(shù)怎樣調(diào)用類(lèi)內(nèi)函數(shù)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08C++ STL_vector 迭代器失效問(wèn)題的解決方法
迭代器的主要作用就是讓算法能夠不用關(guān)心底層數(shù)據(jù)結(jié)構(gòu),其底層實(shí)際就是一個(gè)指針,或者是對(duì)指針進(jìn)行了封裝,迭代器失效,實(shí)際就是迭代器底層對(duì)應(yīng)指針?biāo)赶虻目臻g被銷(xiāo)毀了,對(duì)迭代器失效我們了解了,那么現(xiàn)在我們就分析,在vector中哪些操作會(huì)導(dǎo)致迭代器失效2023-08-08