DllMain的相關特性
首先列出《DllMain中不當操作導致死鎖問題的分析--進程對DllMain函數的調用規律的研究和分析》中論證的11個特性:
- Dll的加載不會導致之前創建的線程調用其DllMain函數。
- 線程創建後會調用已經加載了的DLL的DllMain,且調用原因是DLL_THREAD_ATTACH。(DisableThreadLibraryCalls會導致該過程不被調用)
- TerminateThread方式終止線程是不會讓該線程去調用該進程中加載的Dll的DllMain。
- 線程正常退出時,會調用進程中還沒卸載的DLL的DllMain,且調用原因是DLL_THREAD_DETACH。
- 進程正常退出時,會調用(不一定是主線程)該進程中還沒卸載的DLL的DllMain,且調用原因是DLL_PROCESS_DETACH。
- 加載DLL進入進程空間時(和哪個線程LoadLibrary無關),加載它的線程會調用DllMain,且調用原因是DLL_PROCESS_ATTACH。
- DLL從進程空間中卸載出去前,會被卸載其的線程調用其DllMain,且調用原因是DLL_PROCESS_DETACH。
- TerminateProcess 將導致線程和進程在退出時不對未卸載的DLL進行DllMain調用。
- ExitProcess將導致主線程意外退出,子線程對未卸載的DLL進行了DllMain調用,且調用原因是DLL_PROCESS_DETACH。
- ExitThread是最和平的退出方式,它會讓線程退出前對未卸載的DLL調用DllMain。
- 線程的創建和退出不會對調用了DisableThreadLibraryCalls的DLL調用DllMain。
不要在DllMain中做的事情
- A 直接或者間接調用LoadLibrary(Ex)
- B 使用CoInitializeEx
- C 使用CreateProcess
- D 使用User32或Gdi32中的函數
- E 使用託管代碼
- F 與其他線程同步執行
- G 同步對象
- H 使用CreateThread
- I 使用ExitThread
- J 寄希望於DisableThreadLibraryCalls解決死鎖問題
而創建線程在底層將調用LdrpInitializeThread(詳見《DllMain中不當操作導致死鎖問題的分析--DisableThreadLibraryCalls對DllMain中死鎖的影響》)。該函數一開始便進入了PEB中LoaderLock臨界區,在該臨界區中根據PEB中LDR的InMemoryOrderModuleList遍歷加載的DLL,然後判斷該DLL信息的Flags字段是否或上了0x40000。如果或上了,就不調用DllMain。如果沒或上,就調用DllMain。這說明DisableThreadLibraryCalls對創建線程時是否進入臨界區無關。
在退出線程時底層將調用LdrShutdownThread(詳見《DllMain中不當操作導致死鎖問題的分析--線程退出時產生了死鎖》)。該函數邏輯和LdrpInitializeThread相似,只是在調用DllMain時傳的是DLL_THREAD_DETACH。所以DisableThreadLibraryCalls對LdrShutdownThread是否進入臨界區也是沒有影響的。