LoadLibrary下錯誤返回126錯誤碼排查過程

在開發一些Windows下的應用程序過程中,經常會手動加載一些DLL,使用的就是LoadLibrary這個函數,而這個函數一旦失敗,返回的錯誤碼基本都是126,126錯誤碼的意思是找不到指定的模塊,這不,我就遇到了這個問題,而且挺詭異的,就是Debug模式下一點問題沒有,Release模式下就必出這個錯誤碼。

先介紹下問題的環境,一個應用程序需要加載一個IoT模塊的DLL,這個DLL又依賴了一些DLL。有經驗的老司機或者對DLL加載這塊很熟悉的人應該很快就知道問題所在了。不急,慢慢介紹。

-- core.exe
  |--module
    |--iot.dll
    |--mqtt.dll
1
2
3
4
目錄是視圖如上所示,core.exe會LoadLibrary目錄module下iot.dll,而iot.dll又依賴mqtt.dll。

當時我都沒有意識到IoT這個DLL還依賴了別的DLL這一個關鍵點,

所以我排查問題的第一步,既然找不到指定模塊,那麼就先看看對應位置下到底有沒有iot.dll,很遺憾,有!

第二步,既然Debug和Release下表現地不一樣,那麼想會不會是是iot.dll的一些編譯鏈接選項在這兩種模式下不一樣導致DLL有問題?於是就手動修改成Release模式下使用Debug來編譯,還是不行,依然返回錯誤碼126。

第三步,既然以自己現有的知識搞不定了,那麼就只能求助Google了,搜索了一波"LoadLibrary error 126 Release",沒看到別人在Release下出現過這種問題,反倒是查出來,出現126錯誤碼一般是DLL所依賴的DLL沒有找到,就會出現這種問題。這時候才意識到iot.dll還依賴了mqtt.dll,於是又去確認了下mqtt.dll,發現對應位置有這個DLL。這下完全懵了。

第四步,在stackoverflow上有一個大神https://stackoverflow.com/questions/14361992/dll-load-library-error-code-126 給出了一個分析的方法,大概意思我翻譯下:

Windows dll 錯誤碼126有很多原因,最有用的調試方案如下:

使用Dependency Walker[http://www.dependencywalker.com/]查看你的dll依賴;
使用Process Monitor[https://docs.microsoft.com/zh-cn/sysinternals/downloads/procmon]去追蹤dll加載時的所有文件操作。
第五步,先用第一個工具Dependency Walker分別看了下Debug和Release下的iot.dll,發現沒用到的DLL在Release下被優化掉了,依然沒看出來問題。但所有依賴的DLL在對應位置都存在,這依然沒有解決問題。

第六步,沒辦法,只能使用Process Monitor了,這個我之前沒用過,簡單看了熟悉了下它,發現它和Linux下的strace命令很像,都是追蹤程序運行過程中的系統調用和行爲,終於我看到了以下的東西:

可以看到,iot.dll已經LoadImage了,但是它的依賴DLLmqtt.dll一直沒找到,而且可以看出Windows在LoadLibrary時的查找路徑,如下:

程序目錄,也就是exe所在目錄
系統目錄
Windows目錄
進程當前目錄
環境變量PATH目錄
而反觀我的DLL所在位置,不在這上面的任意一個裏面,因此,報出126錯誤碼是很合理的。

解決的方法就很簡單了,根據加載的第一順序拷貝下依賴的DLLmqtt.dll到程序目錄下就行了。

-- core.exe
  |--module
    |--iot.dll
    |--mqtt.dll
  |--mqtt.dll
1
2
3
4
5
當然,你可以通過SetCurrentDirectory改變當前進程目錄來達到順利加載DLL的目的,不過別忘了把通過GetCurrentDirectory把當前目錄恢復回去;有一個函數SetDllDirectory是專門用來指定DLL的搜索路徑的,這個更好,如果使用了這個函數,那麼DLL的加載順序會有點不同,如下:

進程當前目錄
SetDllDirectory指定的目錄
系統目錄
Windows目錄
進程當前目錄
環境變量PATH目錄
設置了這個之後,就會發現不會在程序所在目錄下搜索DLL了,反而會把進程當前目錄作爲第一順序。因此,如果你的應用程序下面有同名的DLL,這個DLL不是你想要的,那就一定要用SetDllDirectory這個函數了。

其實微軟已經給我們提供了更好的方式,LoadLibrary有一個擴展函數LoadLibraryEx [https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw],裏面有個參數LOAD_WITH_ALTERED_SEARCH_PATH,可以讓DLL的搜索路徑從DLL所在目錄開始,這個同樣可以使用Process Monitor去觀察其行爲。那麼這個擴展函數和不擴展的有啥區別呢?下面是我摘抄的一段:

The LoadLibraryEx function is very similar to the LoadLibrary function. The differences consist of a set of optional behaviors that LoadLibraryEx provides:

LoadLibraryEx can load a DLL module without calling the DllMain function of the DLL.
LoadLibraryEx can load a module in a way that is optimized for the case where the module will never be executed, loading the module as if it were a data file.
LoadLibraryEx can find modules and their associated modules by using either of two search strategies or it can search a process-specific set of directories.
設置了SetDllDirectory,LoadLibrary和LoadLibraryEx使用的搜索算法是一樣的,這個我通過Process Monitor已經看過了。

總結一下,這其實是一個很常識的問題,加載有依賴的DLL時要注意依賴DLL的位置,可是自己之前對這部分知識有盲區,導致思路打不開。如果我早知道DLL的查找路徑,這個問題就不會排查這麼久了,不過,在排查問題的過程中,學到了很多東西,包括新工具的使用,對一些東西理解也深刻了很多,同時也發現了知識盲區,知道自己不知道了。
————————————————
版權聲明:本文爲CSDN博主「FlushHip」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/FlushHip/article/details/96167157

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章