[转载] DllMain中不当操作导致死锁问题的分析--加载卸载DLL与DllMain死锁的关系

(转载于breaksoftware的csdn博客)

前几篇文章一直没有在源码级证明:DllMain在收到DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH时会进入临界区。这个论证非常重要,因为它是使其他线程不能进入临界区从而导致死锁的关键。我构造了在DLL被映射到进程地址空间的场景,请看死锁时加载DLL的线程的堆栈

        如果仔细看过《DllMain中不当操作导致死锁问题的分析--导致DllMain中死锁的关键隐藏因子2》,应该得知第14步就是进入临界区的点。

        我们可以看到LdrLoadDll内部调用了LdrLockLoaderLock。LdrLockLoaderLock内部进入临界区,我们用IDA查看LdrLoadDll函数

 
  1. int __stdcall LdrLoadDll(int a1, int a2, int a3, int a4)  
  2. {  
  3.     ……  
  4.     LdrLockLoaderLock(1, 0, &v10);  
  5.     ……  
  6.     v6 = LdrpLoadDll(v9, a1, a2, v17, a4, 1);  
  7.     ……  
  8.     if ( v8 >= 0 )  
  9.     {  
  10.         ms_exc.disabled = -1;  
  11.         sub_7C936587(ebp0, v7);  
  12.         v6 = 0;  
  13.         goto LABEL_6;  
  14.     }  
  15.   
  16. }  
  17.   
  18. int __usercall sub_7C936587<eax>(int a1<ebp>, int a2<esi>)  
  19. {  
  20.     LdrpTopLevelDllBeingLoaded = a2;  
  21.     return LdrUnlockLoaderLock(1, *(_DWORD *)(a1 - 572));  
  22. }  

        我们看到在LdrpLoadDll是在临界区中执行的。其实在LdrpLoadDll中也会进入该临界区,但是我们不必关注了。因为只要一次没出临界区就可以满足死锁的条件了。

        我们再看下卸载DLL时发生的进入临界区场景,请看堆栈

        我们将关注FreeLibrary和LdrpCallInitRoutine之间的代码逻辑。我们用IDA查看LdrUnLoadDll

int __stdcall LdrUnloadDll(int a1)  
2.{  
3.    ……  
4.        v73 = 0;  
5.    v70 = *(_DWORD *)(*MK_FP(__FS__, 24) + 48);  
6.    v71 = 0;  
7.    ms_exc.disabled = 0;  
8.    if ( !LdrpInLdrInit )  
9.        RtlEnterCriticalSection(&LdrpLoaderLock);  
10.    ++LdrpActiveUnloadCount;  
11.    if ( !LdrpShutdownInProgress )  
12.    {  
13.        if ( LdrpCheckForLoadedDllHandle(a1, (int)&v78) )  
14.        {  
15.            if ( *(_WORD *)(v78 + 56) != -1 )  
16.            {  
17.                ……  
18.                if ( (unsigned __int8)LdrpActiveUnloadCount <= 1u )  
19.                {  
20.                    ……  
21.                    v15 = (int *)LdrpUnloadHead;  
22.                    v77 = (int *)LdrpUnloadHead;  
23.                    while ( v15 != &LdrpUnloadHead )  
24.                    {  
25.                        ……  
26.                        LdrpCallInitRoutine((int (__stdcall *)(_DWORD, _DWORD, _DWORD))v20, *(_DWORD *)(v78 + 24), 0, 0);  
27.                        ……  
28.                        v15 = (int *)LdrpUnloadHead;  
29.                        v77 = (int *)LdrpUnloadHead;  
30.                        ms_exc.disabled = 0;  
31.                        v3 = 0;  
32.                    }  
33.                   ……  
34.                }  
35.            }  
36.        }  
37.        else  
38.        {  
39.            v71 = 0xC0000135u;  
40.        }  
41.    }  
42.    ms_exc.disabled = -1;  
43.    sub_7C937424();  
44.……  
45.    return v71;  
46.}  
47.  
48.int __cdecl sub_7C937424()  
49.{  
50.    int result; // eax@3  
51.  
52.    --LdrpActiveUnloadCount;  
53.    if ( !LdrpInLdrInit )  
54.        result = RtlLeaveCriticalSection(&LdrpLoaderLock);  
55.    return result;  
56.}  
View Code

 

        我们看到LdrUnloadDll几乎所有操作都是在临界区执行的。        以上两段从源码级证明了加载和卸载DLL导致的DllMain的调用(以及不调用)都是在临界区中完成的。

posted @ 2015-02-02 21:21  Acg!Check  阅读(768)  评论(0编辑  收藏  举报