新聞速報

        

2018年11月19日 星期一

Windows 用來找出 DLL 的搜尋路徑

Windows 用來找出 DLL 的搜尋路徑

  1. 目前處理序可執行模組的所在目錄。
  2. 目前的目錄。
  3. Windows 系統目錄。 GetSystemDirectory 函式擷取這個目錄的路徑。
  4. Windows 目錄。 GetWindowsDirectory 函式擷取這個目錄的路徑。
  5. 列於 PATH 環境變數的目錄。


    Windows載入器將可執行模組映射到進程的地址空間中,載入器分析可執行模組的輸入表,並設法找出任何需要的DLL,並將它們映射到進程的地址空間中。

    由於輸入表中只包含DLL名而沒有它的路徑名,因此載入程式必須在磁碟上搜尋DLL檔案。首先會嘗試從當前程式所在的目錄載入DLL,如果沒找到,則在Windows系統目錄中查找,最後是在環境變數中列出的各個目錄下查找。

 

原文出處: https://www.cnblogs.com/tocy/p/windows_dll_searth_path.html

一、寫作初衷
在Windows下單個DLL可能存在多個不同的版本,若不特別指定DLL的絕對路徑或使用其他手段指定,在應用程序加載DLL時可能會查找到錯誤的版本,進而引出各種莫名其妙的問題。本文主要考慮以下兩個方面:
a. 參考MSDN,給出Windows下DLL查找順序
b. 簡單使用ProcessMonitor來驗證DLL查找順序

二、DLL查找順序
(本部分多數內容是參考MSDN上的Dynamic-Link Library Search Order一文,鏈接如下http://msdn.microsoft.com/en-us/library/ms682586(v=vs.85).aspx。多數為翻譯,有部分內容修改。本文僅關注桌面應用程序的查找順序,對於Windows Store apps請參考MSDN原文。)
1. DLL查找路徑基礎
應用程序可以通過以下方式控制一個DLL的加載路徑:使用全路徑加載、使用DLL重定向、使用manifest文件。如果上述三種方式均未指定,系統查找DLL的順序將按照本部分描述的順序進行。
對於以下兩種情況的DLL,系統將不會查找,而是直接加載:
a. 對於已經加載到內存中的同名DLL,系統使用已經加載的DLL,並且忽略待加載DLL的路徑。(注意對某個進程而言,系統已經加載的DLL一定是唯一的存在於某個目錄下。)
b. 如果該DLL存在於某個Windows版本的已知DLL列表(unkown DLL)中,系統使用已知DLL的拷貝(包括已知DLL的依賴項)。已知DLL列表可以從如下註冊表項看到:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs。
這裡有個比較坑的地方,對於有依賴項的DLL(即使使用全路徑指定DLL位置),系統查找其所依賴DLL的方法是按照實際的模塊名稱來的,因此如果加載的DLL不在系統查找順序目錄下,那麼動態加載該DLL(LoadLibrary)會返回一個"找不到模塊"的錯誤。

2. 系統標準DLL查找順序
系統使用的標準DLL查找順序依賴於是否設置了"安全DLL查找模式"(safe DLL search mode)。"安全DLL查找模式"會將用戶當前目錄置於查找順序的後邊。
"安全DLL查找模式"默認是啟用的,禁用的話,可以將註冊表項HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode設為0。調用SetDllDirectory函數可以禁用"安全DLL查找模式",並修改DLL查找順序。
Windows XP下,"安全DLL查找模式"默認是禁用的,需要啟用該項的話,在註冊表中新建一個SafeDllSearchMode子項,並賦值為1即可。"安全DLL查找模式"從Windows XP SP2開始,默認是啟用的。

啟用"安全DLL查找模式"時,查找順序如下:
a. 應用程序所在目錄;
b. 系統目錄。GetSystemDirectory返回的目錄,通常是系統盤\Windows\System32;
c. 16位系統目錄。該項只是為了向前兼容的處理,可以不考慮;
d. Windows目錄。GetWindowsDirectory返回的目錄,通常是系統盤\Windows
e. 當前目錄。GetCurrentDirectory返回的目錄;
f. 環境變量PATH中所有目錄。

如果"安全DLL查找模式"被禁用,查找順序如下:
a. 應用程序所在目錄;
b. 當前目錄。GetCurrentDirectory返回的目錄;
c. 系統目錄。GetSystemDirectory返回的目錄,通常是系統盤\Windows\System32;
d. 16位系統目錄。該項只是為了向前兼容的處理,可以不考慮;
e. Windows目錄。GetWindowsDirectory返回的目錄,通常是系統盤\Windows
f. 環境變量PATH中所有目錄。

3. 修改系統DLL查找順序
系統使用的標準DLL查找順序可以通過以下兩種方式調整:
3.1 使用LOAD_WITH_ALTERED_SEARCH_PATH標誌調用LoadLibraryEx函數;
這種方式調用LoadLibraryEx函數,需要設置lpFileName參數(絕對路徑)。與標準查找策略不同的是,使用LOAD_WITH_ALTERED_SEARCH_PATH標誌調用LoadLibraryEx函數的DLL查找順序將"查找應用程序所在目錄"修改為lpFileName指定的目錄。
3.2 調用SetDllDirectory函數。
注意:SetDllDirectory函數在Windows XP SP1開始支持的。
函數SetDllDirectory在調用參數lpPathName是一個路徑時,可支持修改DLL搜索路徑。修改之後的搜索順序如下:
a. 應用程序所在目錄;
b. 函數SetDllDirectory參數lpPathName給定的目錄
c. 系統目錄。GetSystemDirectory返回的目錄,通常是系統盤\Windows\System32;
d. 16位系統目錄。該項只是為了向前兼容的處理,可以不考慮;
e. Windows目錄。GetWindowsDirectory返回的目錄,通常是系統盤\Windows
f. 環境變量PATH中所有目錄。
如果lpPathName參數為空字符串,這樣就會把當前目錄從DLL搜索路徑中去掉。
如果用NULL參數調用SetDllDirectory函數,可以恢復按照系統註冊表的"安全DLL查找模式"來查找DLL。

當然win8或者windows server 2012提供更多的可定製方法,這個可以參考MSDN上介紹。比如:SetDefaultDllDirectories、 AddDllDirectoryRemoveDllDirectory

三、ProcessMonitor使用
ProcessMonitor可以從http://technet.microsoft.com/en-us/sysinternals/bb896645下載。
官網給出的介紹資料如下:
Process Monitor一款系統進程監視軟件,總體來說,Process Monitor相當於Filemon+Regmon,其中的Filemon專門用來監視系統 中的任何文件操作過程,而Regmon用來監視註冊表的讀寫操作過程。 有了Process Monitor,使用者就可以對系統中的任何文件和 註冊表操作同時進行監視和記錄,通過註冊表和文件讀寫的變化, 對於幫助診斷系統故障或是發現惡意軟件、病毒或木馬來說,非常 有用。
軟件下載之後,解壓就可以直接運行。Process Monitor默認會啟用針對真當前系統的"File System"、"Registry"、"Process"的所有操作的記錄,類似wireshark網卡抓包軟件,只是抓取的信息不同。如果僅關心某個進程的事件,可以在工具欄或者菜單中選擇Filter-Filiter,彈出下圖所示對話框:

舉個例子,我們只關心進程名為"qwe.exe"的相關操作,可以做如下處理:從第一個下拉列表框中選擇ProcessName,將進程名字填入輸入框,然後點擊"Add"按鈕,點擊 "OK"(如果已經開始監測,可以直接點Apply按鈕)。
其他關於Process Monitor的使用可以參考幫助文檔,介紹整體比較詳細,這裡不做贅述。

四、驗證Windows下DLL加載順序是否正確
那麼我們可以考慮在win7下驗證下DLL加載順序,想法很簡單,隨便寫一個系統中不存在的DLL,用LoadLibray動態加載下看看,用Process Monitor記錄當前進程的操作記錄。
代碼如下:
複製代碼
 1 #include <windows.h>
 2 #include <iostream>
 3 
 4 int main(int argc, char ** argv)
 5 {
 6     using std::cout;
 7     using std::endl;
 8     
 9     // 隨便設置一個不存在的dll名
10     HMODULE hMod = LoadLibrary("123.dll");
11 
12     if (NULL != hMod)
13         FreeLibrary(hMod);
14         
15     cout << "LoadLibrary Test" << endl;
16     
17     return 0;
18 }
複製代碼

使用MinGW編譯之後,在命令行下運行該程序。Process Monitor輸出如下信息(這裡僅截取關於123.dll加載的信息):

可以看到這裡搜索的路徑跟系統標準DLL搜索路徑時一致的。我的環境變量Path從D:\software\Subversion\Apache2\bin開始到D:\software\tortoiseGit\bin結束。

五、總結
本文主要介紹了Windows下DLL查找順序,理清這個查找順序基本可以找到LoadLibrary返回NULL,提示"找不到指定模塊"的原因。通常可以考慮一下幾點:
a. 待加載DLL文件是正確的、完整的嗎? 是否有損壞? 加載全路徑是否正確?
b. 待加載DLL的依賴項是否存在?(Dependency Walker可以查看依賴性)這些依賴項在是否都在系統可以查找到的目錄下?
另外,本文簡單介紹了使用Process Monitor分析程序運行對文件系統、註冊表、進程/線程的操作,工具不錯,有待開發。
還有一個問題,需要特別注意的是當前目錄,因為很多情況下當前目錄會因為SetCurrentDirectory或者OpenFile的操作而改變,某些情況下會對加載DLL造成意外的麻煩。

 

沒有留言:

張貼留言