在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;
}
{
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;
}
{
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内部或者进程线程初始化过程有关系。