DllMain中不当操作导致死锁问题的分析--线程退出时产生了死锁
我们回顾下之前举得例子(转载请指明出于breaksoftware的csdn博客)
我们看到是在ExitThread中调用了LdrShutDownThread。我用IDA看了下LdrShutDownThread函数,并和网传的win2K源码做了比较。没发现明显的不一样之处,于是我这儿用更便于阅读的win2K的版本代码VOIDLdrShutdownThread ( VOID )/*++Routine Description: This function is called by a thread that is terminating cleanly. It's purpose is to call all of the processes DLLs to notify them that the thread is detaching.Arguments: NoneReturn Value: None.--*/{ PPEB Peb; PLDR_DATA_TABLE_ENTRY LdrDataTableEntry; PDLL_INIT_ROUTINE InitRoutine; PLIST_ENTRY Next; Peb = NtCurrentPeb(); RtlEnterCriticalSection(&LoaderLock); try { // // Go in reverse order initialization order and build // the unload list // Next = Peb->Ldr->InInitializationOrderModuleList.Blink; while ( Next != &Peb->Ldr->InInitializationOrderModuleList) { LdrDataTableEntry = (PLDR_DATA_TABLE_ENTRY) (CONTAINING_RECORD(Next,LDR_DATA_TABLE_ENTRY,InInitializationOrderLinks)); Next = Next->Blink; // // Walk through the entire list looking for // entries. For each entry, that has an init // routine, call it. // if (Peb->ImageBaseAddress != LdrDataTableEntry->DllBase) { if ( !(LdrDataTableEntry->Flags & LDRP_DONT_CALL_FOR_THREADS)) { InitRoutine = (PDLL_INIT_ROUTINE)LdrDataTableEntry->EntryPoint; if (InitRoutine && (LdrDataTableEntry->Flags & LDRP_PROCESS_ATTACH_CALLED) ) { if (LdrDataTableEntry->Flags & LDRP_IMAGE_DLL) { if ( LdrDataTableEntry->TlsIndex ) { LdrpCallTlsInitializers(LdrDataTableEntry->DllBase,DLL_THREAD_DETACH); }#if defined (WX86) if (!Wx86ProcessInit || LdrpRunWx86DllEntryPoint(InitRoutine, NULL, LdrDataTableEntry->DllBase, DLL_THREAD_DETACH, NULL ) == STATUS_IMAGE_MACHINE_TYPE_MISMATCH)#endif { LdrpCallInitRoutine(InitRoutine, LdrDataTableEntry->DllBase, DLL_THREAD_DETACH, NULL); } } } } } } // // If the image has tls than call its initializers // if ( LdrpImageHasTls ) { LdrpCallTlsInitializers(NtCurrentPeb()->ImageBaseAddress,DLL_THREAD_DETACH); } LdrpFreeTls(); } finally { RtlLeaveCriticalSection(&LoaderLock); }}我们看第23行,发现该函数一开始便进入了临界区,也就是说不管该线程是否需要对某DLL调用DllMain都要进入临界区,也就是说DisableThreadLibraryCalls对线程退出时是否进入临界区是没有影响的。因为主线程正在调用DllMain,所以它先进入了临界区,并一直占用了它。而工作线程退出前也要进入这个临界区做点事,所以它一直进不去,并被系统挂起。而此时占用临界区的主线程要一直等到工作线程退出才肯往下继续执行以退出临界区。这便产生了死锁。
最后附上实验中的例子和《Best Practices for Creating DLLs》