DisableThreadLibraryCalls與DLLMain死鎖


1、首先寫個簡單的DLL,用來驗證

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		{
			printf("DLL_PROCESS_ATTACH\n");
		}
		break;

	case DLL_PROCESS_DETACH:
		{
			printf("DLL_PROCESS_DETACH\n");
		}
		break;

	case DLL_THREAD_ATTACH:
		{
			printf("DLL_THREAD_ATTACH\n");
		}
		break;
	case DLL_THREAD_DETACH:
		{
			printf("DLL_THREAD_DETACH\n");
		}
		break;
	}
	return TRUE;
}

 2、再寫一個測試用的EXE:

unsigned int __stdcall ThreadFun(PVOID pM);

//int WinMain(HINSTANCE hInstance,
//			HINSTANCE hPrevInstance,
//			LPSTR lpCmdLine,
//			int nCmdShow)
void main()
{
	HMODULE hMod = LoadLibrary("TestDLL.dll");

	HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
	if(!hThread)
	{
		return;
	}

	WaitForSingleObject(hThread, INFINITE);

	FreeLibrary(hMod);
	getchar();
	exit(0);
//	return 0;
}

unsigned int __stdcall ThreadFun(PVOID pM)
{
	printf("線程ID號為%4d的子線程說:Hello World\n", GetCurrentThreadId());
	return 0;
}

3、你把這個EXE跑一遍就會發現,在線程被創建的時候,也會執行一次已經被加載的DLL的DLLMain,加載原因是DLL_THREAD_ATTACH,離開時也同理,最終顯示結果如下:

 

 

4、如果你在原先的DLL代碼中加入DisableThreadLibraryCalls調用,那么就不會在線程啟動時去執行這個DLL的DLLMain了

 

 

 

 當然這里關於線程是否執行DLLMain的DLL_THREAD_ATTACH和DLL_THREAD_DETACH的,還有一些比較細節的邏輯,可以參考:

https://blog.csdn.net/breaksoftware/article/details/8142339

5、在DLL中會產生死鎖的情況及原因

5.1、DLLMain中直接或者間接調用LoadLibrary:

起初我以為,假如我們在A.dll中的DllMain收到DLL_PROCESS_ATTACH時,加載了B.dll;而B.dll中的DllMain在收到DLL_PROCESS_ATTACH時又去加載A.dll,則產生了循環依賴,然而經試驗檢驗,並不會,因為如果A.dll已經加載進EXE的內存了,B.dll就不會再去加載A.dll了,實驗如下:

 

 

 結果如下:

 

 並沒有發生死鎖。第一這里沒有發生調用循環;第二這里也沒有發生死鎖。

首先,為什么沒有發生我們預計的循環調用呢?我們來一步步分析兩個DLL相互加載的過程。

 看調用堆棧發現,TestDLL2又調用了LdrLoadDll,看這架勢是要遞歸加載的:

 

 然而當我跟進去調試的時候,有某一個地方觸發了返回,並沒有調用LdrpRunInitializeRoutines,這個地方就是LdrpFindOrMapDll。但凡你要加載一個DLL,LdrpFindOrMapDll就會去查找是否已經加載過這個DLL了。

 

 如果沒有加載過某個DLL那么LdrpFindOrMapDll的最后一個參數會被寫入1,而如果加載過了這個參數就會被寫入0:

 DLL的查找方式就是計算Hash並保存在一張全局的Hash表中:

其次,這里為什么沒有發生死鎖?原因很簡單,我調試這里時:

 

 發現TestDLL在再次調用LoadLibrary前后,LdrpLoaderLock的變化如下(命令是dt _RTL_CRITICAL_SECTION 770520c0):

 

 

 

 也就是說這是同一個線程對LdrpLoaderLock上了鎖,所以並沒有死鎖。那么再來驗證下如果是另一個線程里,調用LoadLibrary是否會發生死鎖呢?

我們把實驗代碼稍微改動下,讓TestDLL在DLLMain中起一個線程:

 

 exe中為了保證TestDLL不被Free掉,我就一直Sleep了:

 

 如願,運行起來就在某處卡死掉了。我們查看所有的臨界區:

LockCount=1表示有一個線程正在等待這個臨界區。然后我們查看這個臨界區的詳細信息:

 

 0號線程正在等待1號線程擁有的臨界區:

 

或者我們直接用!locks命令查看:

我們驗證下0號線程:

 

再驗證下1號線程:

 

 

這里總結下,DLLMain中並不是因為LoadLibrary中的循環調用造成的死鎖,而是由於其它原因。什么原因?一個國外網站上給出了解釋:

Your DllMain function runs inside the loader lock,one of the few times the OS lets you run code while one of its internal locks is held. This means that you must be extra careful not to violate a lock hierarchy in your DllMain; otherwise, you are asking for a deadlock.

The loader lock is taken by any function that needs to access the list of DLLs loaded into the process.loader lock This includes functions like GetModuleHandle and GetModuleFileName. If your DllMain enters a critical section or waits on a synchronization object, and that critical section or synchronization object is owned by some code that is in turn waiting for the loader lock, you just created a deadlock.

 

所以我們的結論是:

無法通過使用DisableThreadLibraryCalls解決死鎖問題。因為,DisableThreadLibraryCalls只是用來防止EXE中起線程時重新執行DLLMain,但是你在主線程第一次Load TestDLL.dll時就已經出現死鎖了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM