DLL的绑定进程和解绑定进程引起的死锁

在DLL的编写中,可以不导出函数,而只有一个入口就是DllMain,在这里它和操作系统有四个接口:

1 绑定到进程

2 从进程解绑定

3 绑定到线程

4 从线程解绑定

我做一个写日志的公共库的时候,在DLL中测试,在绑定到进程中初始化,在从进程解绑定的过程中遇到了麻烦。

就是FreeLibrary 卡死。

卡死时候的dump如下:

 
  0  Id: 4d4.cb4 Suspend: 1 Teb: 7efdd000 Unfrozen

 # ChildEBP RetAddr  Args to Child              

00 001cefc0 759d0816 000002b8 00000000 00000000 ntdll!ZwWaitForSingleObject+0x15

01 001cf02c 75c11194 000002b8 ffffffff 00000000 KERNELBASE!WaitForSingleObjectEx+0x98

02 001cf044 75c11148 000002b8 ffffffff 00000000 kernel32!WaitForSingleObjectExImplementation+0x75

03 001cf058 723a5d97 000002b8 ffffffff 001cf070 kernel32!WaitForSingleObject+0x12

04 001cf068 723a5bd8 001cf090 723a7b11 723a5bd0 rdlp!util::mylog::StopLog+0x27 [e:\work\svn\esm\1.0\code\rdlp\utillib\util\mylog.cpp @ 250]

05 001cf070 723a7b11 723a5bd0 00000000 00000000 rdlp!util::mylog::Uninitialize_LogFile+0x8 [e:\work\svn\esm\1.0\code\rdlp\utillib\util\mylog.cpp @ 193]

06 001cf090 723a7c30 01da6e0c 01da6de0 01da6de0 rdlp!_CRT_INIT+0x1be [f:\dd\vctools\crt_bld\self_x86\crt\src\crtdll.c @ 449]

07 001cf0d4 723a7cad 72390000 001cf100 77c69930 rdlp!__DllMainCRTStartup+0xb7 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtdll.c @ 560]

08 001cf0e0 77c69930 72390000 00000000 00000000 rdlp!_DllMainCRTStartup+0x1e [f:\dd\vctools\crt_bld\self_x86\crt\src\crtdll.c @ 510]

09 001cf100 77c90000 723a7c8f 72390000 00000000 ntdll!LdrpCallInitRoutine+0x14

0a 001cf188 77c71221 72390000 001cf1ac 77c55ef5 ntdll!LdrpUnloadDll+0x375

0b 001cf1cc 759d1da7 72390000 755acca9 001cf204 ntdll!LdrUnloadDll+0x4a

*** ERROR: Symbol file could not be found.  Defaulted to export symbols for rscom.dll -

0c 001cf1dc 741405d2 72390000 00000008 0028f018 KERNELBASE!FreeLibrary+0x15

WARNING: Stack unwind information not available. Following frames may be wrong.

0d 001cf204 74143df5 01d9015c 00000000 002a2535 rscom!DllCanUnloadNow+0xea72

*** ERROR: Module load completed but symbols could not be loaded for esmmain.exe

0e 001cf7c0 002a2683 002a0000 00000000 002a668c rscom!DllCanUnloadNow+0x12295

0f 001cf868 75c133ca 7efde000 001cf8b4 77c69ed2 esmmain+0x2683

10 001cf874 77c69ed2 7efde000 77c5578d 00000000 kernel32!BaseThreadInitThunk+0xe

11 001cf8b4 77c69ea5 002a2b65 7efde000 00000000 ntdll!__RtlUserThreadStart+0x70

12 001cf8cc 00000000 002a2b65 7efde000 00000000 ntdll!_RtlUserThreadStart+0x1b
 
在栈里面可以看到在rdlp!util::mylog::StopLog的时候在等待,它在等待一个线程结束,LOG是用另外一个线程写的,在DLL退出的时候,要将这个线程停止,但是这个线程却没有停止导致DLL不能退出内存。
 
那么那个线程在干什么,怎么不听指挥了呢。
 
   4  Id: 4d4.f2c Suspend: 1 Teb: 7ef8d000 Unfrozen
 # ChildEBP RetAddr  Args to Child              
00 0468f8f4 77c68dd4 00000158 00000000 00000000 ntdll!ZwWaitForSingleObject+0x15
01 0468f958 77c68cb8 00000000 00000000 01e33828 ntdll!RtlpWaitOnCriticalSection+0x13e
02 0468f980 77c8d349 77d320c0 73b15521 00000000 ntdll!RtlEnterCriticalSection+0x150
03 0468fa18 77c8d5c2 00000000 00000000 0468fa34 ntdll!LdrShutdownThread+0x50
04 0468fa28 75563412 00000000 0468fa6c 75563439 ntdll!RtlExitUserThread+0x2a
05 0468fa34 75563438 00000000 39712431 00000000 msvcr90!_endthreadex+0x23
06 0468fa6c 755634c7 00000000 0468fa84 75c133ca msvcr90!_endthreadex+0x49
07 0468fa78 75c133ca 01e33828 0468fac4 77c69ed2 msvcr90!_endthreadex+0xd8
08 0468fa84 77c69ed2 01e33828 73b155fd 00000000 kernel32!BaseThreadInitThunk+0xe
09 0468fac4 77c69ea5 7556345e 01e33828 00000000 ntdll!__RtlUserThreadStart+0x70
0a 0468fadc 00000000 7556345e 01e33828 00000000 ntdll!_RtlUserThreadStart+0x1b
这个线程就在这里,正在努力的退出,它已经进入了ExitThread(ExitThread = ntdll!RtlExitUserThread)
在等待一个临界区。
这个临界区是谁?
0:006> !cs 77d320c0
-----------------------------------------
Critical section   = 0x77d320c0 (ntdll!LdrpLoaderLock+0x0)
DebugInfo          = 0x77d34360
LOCKED
LockCount          = 0x3
WaiterWoken        = No
OwningThread       = 0x00000cb4
RecursionCount     = 0x1
LockSemaphore      = 0x158
SpinCount          = 0x00000000

属于线程 0x00000cb4 ,这个正好是DllMain Detach 的那个线程,而那个线程在等待0x00000cb4 这个线程退出。所以两个互相等待,死锁。
我们分析一下这个锁是什么锁。
用IDAPRO 打开ntdll
翻到:
void __stdcall RtlExitUserThread(int a1)
{
  int v1; // [sp+4h] [bp-4h]@1
  v1 = 0;
  if ( NtQueryInformationThread(-2, 12, &v1, 4, 0) < 0 || !v1 )
  {
    LdrShutdownThread();
    TpCheckTerminateWorker(0);
    ZwTerminateThread(0, a1);
  }
  RtlExitUserProcess(a1);
  __debugbreak();
  JUMPOUT(*(int *)RtlpFreeTebLanguageList);
}
在 LdrShutdownThread 中
int __cdecl LdrShutdownThread()
{
  int result; //  eax@12
  unsigned __int32 v1; //  edi@1
  int v2; //  esi@1
  int v3; //  ebx@4
  int v4; //  esi@6
  int v5; //  eax@6
  int v6; //  ebx@17
  int v7; //  eax@22
  int v8; // [sp+10h] [bp-7Ch]@24
  int v9; // [sp+14h] [bp-78h]@24
  char v10; // [sp+18h] [bp-74h]@24
  int v11; // [sp+34h] [bp-58h]@11
  int v12; // [sp+38h] [bp-54h]@11
  char v13; // [sp+3Ch] [bp-50h]@11
  int v14; // [sp+58h] [bp-34h]@6
  int v15; // [sp+5Ch] [bp-30h]@1
  int v16; // [sp+60h] [bp-2Ch]@6
  int v17; // [sp+64h] [bp-28h]@11
  int v18; // [sp+68h] [bp-24h]@1
  unsigned __int32 v19; // [sp+6Ch] [bp-20h]@1
  char v20; // [sp+73h] [bp-19h]@4
  CPPEH_RECORD ms_exc; // [sp+74h] [bp-18h]@4

  v1 = __readfsdword(24);
  v19 = v1;
  v18 = *(_DWORD *)(__readfsdword(24) + 48);
  v2 = *(_DWORD *)(v1 + 4020);
  v15 = v2;
  if ( v2 )
    RtlProcessFlsData(v2);
  if ( RtlIsCurrentThreadAttachExempt() )
  {
    if ( v2 )
    {
      *(_DWORD *)(v1 + 4020) = 0;
      RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), 0, v2);
    }
    v6 = *(_DWORD *)(v1 + 3988);
    if ( v6 )
    {
      *(_DWORD *)(v1 + 3988) = 0;
      RtlEnterCriticalSection(&FastPebLock);
      RtlLeaveCriticalSection(&FastPebLock);
      RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), 0, v6);
    }
    if ( *(_BYTE *)(v1 + 4042) & 4 )
    {
      v7 = *(_DWORD *)(v1 + 16);
      *(_DWORD *)(v1 + 16) = 0;
      RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), 0, v7);
    }
    result = RtlFreeThreadActivationContextStack();
  }
  else
  {
    v20 = 1;
     RtlEnterCriticalSection(&LdrpLoaderLock);
    ms_exc.disabled = 0;
    v3 = dword_7DF70220;
    while ( (int *)v3 != &dword_7DF7021C )
    {
      v4 = v3 - 16;
      v14 = v4;
      v3 = *(_DWORD *)(v3 + 4);
      v16 = v3;
      v5 = *(_DWORD *)(v4 + 52);
      if ( *(_DWORD *)(v18 + 8) != *(_DWORD *)(v4 + 24)
        && !(v5 & 0x40000)
        && *(_DWORD *)(v4 + 28)
        && v5 & 0x80000
        && v5 & 4 )
      {
        v17 = *(_DWORD *)(v4 + 28);
        v11 = 36;
        v12 = 1;
        memset(&v13, 0, 0x1Cu);
        RtlActivateActivationContextUnsafeFast(&v11, *(_DWORD *)(v4 + 72));
        ms_exc.disabled = 1;
        if ( *(_WORD *)(v4 + 58) )
          LdrpCallTlsInitializers(3, v4);
        LdrpCallInitRoutine(v17, *(_DWORD *)(v4 + 24), 3, 0);
        ms_exc.disabled = 0;
        return RtlDeactivateActivationContextUnsafeFast(&v11);
      }
    }
    if ( LdrpImageHasTls() )
    {
      v8 = 36;
      v9 = 1;
      memset(&v10, 0, 0x1Cu);
      RtlActivateActivationContextUnsafeFast(&v8, *(_DWORD *)(LdrpImageEntry + 72));
      ms_exc.disabled = 2;
      LdrpCallTlsInitializers(3, LdrpImageEntry);
      ms_exc.disabled = 0;
      result = RtlDeactivateActivationContextUnsafeFast(&v8);
    }
    else
    {
      v20 = 0;
      RtlLeaveCriticalSection(&LdrpLoaderLock);
      result = LdrpFreeTls();
      ms_exc.disabled = -2;
      if ( v20 )
        result = RtlLeaveCriticalSection(&LdrpLoaderLock);
    }
  }
  return result;
}
ExitThread 进入了这里,在等待 Loader Lock 锁释放。
RtlEnterCriticalSection(&LdrpLoaderLock);
 
再看看Dll Detach时候的代码,从上面栈里我们看到FreeLibrary -->LdrUnloadDll ,我们用IDAPRO翻到 LdrUnloadDll
int __stdcall LdrUnloadDll(int a1)
{
  char v1; //  bl@3
  int result; //  eax@3
  int v3; // [sp+14h] [bp-20h]@1
  char v4; // [sp+1Bh] [bp-19h]@3
  CPPEH_RECORD ms_exc; // [sp+1Ch] [bp-18h]@3

  v3 = 0;
  if ( !LdrpInLdrInit )
     RtlEnterCriticalSection(&LdrpLoaderLock);
  v1 = LdrpUnloadInProgress;
  v4 = LdrpUnloadInProgress;
  LdrpUnloadInProgress = 1;
  ms_exc.disabled = 0;
  result = LdrpUnloadDll(a1, &v3);
  if ( !v1 )
    result = LdrpClearForwardersList(&v3);
  ms_exc.disabled = -2;
  LdrpUnloadInProgress = v1;
  if ( !LdrpInLdrInit )
    result = RtlLeaveCriticalSection(&LdrpLoaderLock);
  return result;
}
红色部分就是进入临界区那行,然后执行 DllMain 然后再退出临界区。
 
根据以上代码(逆向的)分析,当Dll被卸载的时候和线程要自杀的时候,需要释放资源,为了多线程安全,所以要使用临界区来防止大家都来访问,因此导致了在Dll Detach的时候和线程退出的时候互锁。如何防止这种情况发生?
1 在初始化的时候不要启动线程,导出函数,成对操作。
2 直接TerminateThread ,但是有泄露(不提倡)。
 
后记:
以前就职的某公司就有这种代码,在 WinMain退出的时候,不是优雅地结束,而是TerminateProcess结束进程,那时候很奇怪,后来不调用TerminateProcess让程序退出,结果崩溃,在某个DLL,不知道是不是这种情况,还有其他的什么难言之隐。现在看来可能也和CRT内部或者进程线程初始化过程有关系。
 
 
 
 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值