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中起一个线程://后发现,这里论证可能有误,因为即便是单独起一个线程,也会导致死锁,所以就证明不了这是LoadLibrary造成的了
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时就已经出现死锁了。