码迷,mamicode.com
首页 > 编程语言 > 详细

[转载]DllMain中不当操作导致死锁问题的分析--线程退出时产生了死锁

时间:2015-02-02 22:45:59      阅读:279      评论:0      收藏:0      [点我收藏+]

标签:

(转载于breaksoftware的csdn博客)

我们回顾下之前举得例子

  1. case DLL_PROCESS_ATTACH: {  
  2.           printf("DLL DllWithoutDisableThreadLibraryCalls_A:\tProcess attach (tid = %d)\n", tid);  
  3.           HANDLE hThread = CreateThread(NULL, 0, ThreadCreateInDllMain, NULL, 0, NULL);  
  4.           WaitForSingleObject(hThread, INFINITE);  
  5.           CloseHandle(hThread);  
  6.       }break;  

        可以想象下这么写代码同学的思路:我要在DLL第一次被映射到进程地址空间时创建一个线程,该线程完成一些可能是初始化的操作后马上结束。然后wait到这个线程结束,我们在DllMain中继续做些操作。

       是否想过,如果我们这儿创建一个线程去做事,然后去等待该线程结束。这样就是同步操作了,如此操作不如将线程函数内容放在DllMain中直接执行,何必再去启动一个线程呢?现实中更多的操作可能是:在DLL第一次被映射入进程地址空间时创建一个线程,在卸载出进程空间时将这个线程关闭。

  1. HANDLE g_thread_handle = NULL;  
  2. HANDLE g_hEvent = NULL;  
  3.   
  4. static DWORD WINAPI ThreadCreateInDllMain( LPVOID p )  
  5. {  
  6.     WaitForSingleObject( g_hEvent, INFINITE );  
  7.     return 0;  
  8. }  
  9.   
  10. BOOL APIENTRY DllMain( HMODULE hModule,  
  11.                       DWORD  ul_reason_for_call,  
  12.                       LPVOID lpReserved  
  13.                       )  
  14. {  
  15.     switch (ul_reason_for_call)     
  16.     {  
  17.     case DLL_PROCESS_ATTACH:  
  18.         {  
  19.             g_hEvent = ::CreateEvent( NULL, FALSE, FALSE, NULL );  
  20.             g_thread_handle = ::CreateThread( NULL, 0, ThreadCreateInDllMain,NULL, 0, NULL ) ;  
  21.         }break;  
  22.     case DLL_PROCESS_DETACH:  
  23.         {  
  24.             ::SetEvent(g_hEvent);  
  25.             ::WaitForSingleObject(g_thread_handle, INFINITE );  
  26.   
  27.             ::CloseHandle(g_thread_handle);  
  28.             g_thread_handle = NULL ;                    
  29.             ::CloseHandle(g_hEvent);  
  30.             g_hEvent=NULL;  
  31.         } break;  
  32.     case DLL_THREAD_ATTACH:  
  33.     case DLL_THREAD_DETACH:  
  34.         break;  
  35.     }  
  36.     return TRUE;  
  37. }  

        很不幸,这个程序也会死锁。稍微敏感的同学应该可以猜到第25行是死锁的一个因素。是的!那另一个呢?必然是线程了。DllMain中SetEvent之后,工作线程从挂起状态复活,并执行完了return 0。那么另一个死锁因素是出现在线程退出的逻辑中。我们查看堆栈

技术分享         我们看到是在ExitThread中调用了LdrShutDownThread。我用IDA看了下LdrShutDownThread函数,并和网传的win2K源码做了比较。没发现明显的不一样之处,于是我这儿用更便于阅读的win2K的版本代码

技术分享
VOID  
2.LdrShutdownThread (  
3.    VOID  
4.    )  
5./*++ 
6.Routine Description: 
7.    This function is called by a thread that is terminating cleanly. 
8.    It‘s purpose is to call all of the processes DLLs to notify them 
9.    that the thread is detaching. 
10.Arguments: 
11.    None 
12.Return Value: 
13.    None. 
14.--*/  
15.{  
16.    PPEB Peb;  
17.    PLDR_DATA_TABLE_ENTRY LdrDataTableEntry;  
18.    PDLL_INIT_ROUTINE InitRoutine;  
19.    PLIST_ENTRY Next;  
20.  
21.    Peb = NtCurrentPeb();  
22.  
23.    RtlEnterCriticalSection(&LoaderLock);  
24.  
25.    try {  
26.        //  
27.        // Go in reverse order initialization order and build  
28.        // the unload list  
29.        //  
30.  
31.        Next = Peb->Ldr->InInitializationOrderModuleList.Blink;  
32.        while ( Next != &Peb->Ldr->InInitializationOrderModuleList) {  
33.            LdrDataTableEntry  
34.                = (PLDR_DATA_TABLE_ENTRY)  
35.                  (CONTAINING_RECORD(Next,LDR_DATA_TABLE_ENTRY,InInitializationOrderLinks));  
36.  
37.            Next = Next->Blink;  
38.  
39.            //  
40.            // Walk through the entire list looking for  
41.            // entries. For each entry, that has an init  
42.            // routine, call it.  
43.            //  
44.  
45.            if (Peb->ImageBaseAddress != LdrDataTableEntry->DllBase) {  
46.                if ( !(LdrDataTableEntry->Flags & LDRP_DONT_CALL_FOR_THREADS)) {  
47.                    InitRoutine = (PDLL_INIT_ROUTINE)LdrDataTableEntry->EntryPoint;  
48.                    if (InitRoutine && (LdrDataTableEntry->Flags & LDRP_PROCESS_ATTACH_CALLED) ) {  
49.                        if (LdrDataTableEntry->Flags & LDRP_IMAGE_DLL) {  
50.                            if ( LdrDataTableEntry->TlsIndex ) {  
51.                                LdrpCallTlsInitializers(LdrDataTableEntry->DllBase,DLL_THREAD_DETACH);  
52.                                }  
53.  
54.#if defined (WX86)  
55.                            if (!Wx86ProcessInit ||  
56.                                LdrpRunWx86DllEntryPoint(InitRoutine,  
57.                                                        NULL,  
58.                                                        LdrDataTableEntry->DllBase,  
59.                                                        DLL_THREAD_DETACH,  
60.                                                        NULL  
61.                                                        ) ==  STATUS_IMAGE_MACHINE_TYPE_MISMATCH)  
62.#endif  
63.                               {  
64.                                LdrpCallInitRoutine(InitRoutine,  
65.                                                    LdrDataTableEntry->DllBase,  
66.                                                    DLL_THREAD_DETACH,  
67.                                                    NULL);  
68.                                }  
69.                            }  
70.                        }  
71.                    }  
72.                }  
73.            }  
74.  
75.        //  
76.        // If the image has tls than call its initializers  
77.        //  
78.  
79.        if ( LdrpImageHasTls ) {  
80.            LdrpCallTlsInitializers(NtCurrentPeb()->ImageBaseAddress,DLL_THREAD_DETACH);  
81.            }  
82.        LdrpFreeTls();  
83.  
84.    } finally {  
85.  
86.        RtlLeaveCriticalSection(&LoaderLock);  
87.    }  
88.}  
View Code


 

        我们看第23行,发现该函数一开始便进入了临界区,也就是说不管该线程是否需要对某DLL调用DllMain都要进入临界区,也就是说DisableThreadLibraryCalls对线程退出时是否进入临界区是没有影响的。因为主线程正在调用DllMain,所以它先进入了临界区,并一直占用了它。而工作线程退出前也要进入这个临界区做点事,所以它一直进不去,并被系统挂起。而此时占用临界区的主线程要一直等到工作线程退出才肯往下继续执行以退出临界区。这便产生了死锁。

[转载]DllMain中不当操作导致死锁问题的分析--线程退出时产生了死锁

标签:

原文地址:http://www.cnblogs.com/Acg-Check/p/4268711.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!