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

来源:互联网 发布:c语言中汉诺塔问题 编辑:程序博客网 时间:2024/06/06 08:33

(转载于breaksoftware的csdn博客)

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


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


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

[cpp] view plaincopy
  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

[cpp] view plaincopy
  1. 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. }  
        我们看到LdrUnloadDll几乎所有操作都是在临界区执行的。        以上两段从源码级证明了加载和卸载DLL导致的DllMain的调用(以及不调用)都是在临界区中完成的。
0 0
原创粉丝点击