前几篇文章一直没有在源码级证明:DllMain在收到DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH时会进入临界区。这个论证非常重要,因为它是使其他线程不能进入临界区从而导致死锁的关键。我构造了在DLL被映射到进程地址空间的场景,请看死锁时加载DLL的线程的堆栈
如果仔细看过《DllMain中不当操作导致死锁问题的分析--导致DllMain中死锁的关键隐藏因子2》,应该得知第14步就是进入临界区的点。
我们可以看到LdrLoadDll内部调用了LdrLockLoaderLock。LdrLockLoaderLock内部进入临界区,我们用IDA查看LdrLoadDll函数
- int __stdcall LdrLoadDll(int a1, int a2, int a3, int a4)
- {
- ……
- LdrLockLoaderLock(1, 0, &v10);
- ……
- v6 = LdrpLoadDll(v9, a1, a2, v17, a4, 1);
- ……
- if ( v8 >= 0 )
- {
- ms_exc.disabled = -1;
- sub_7C936587(ebp0, v7);
- v6 = 0;
- goto LABEL_6;
- }
- }
- int __usercall sub_7C936587<eax>(int a1<ebp>, int a2<esi>)
- {
- LdrpTopLevelDllBeingLoaded = a2;
- return LdrUnlockLoaderLock(1, *(_DWORD *)(a1 - 572));
- }
我们看到在LdrpLoadDll是在临界区中执行的。其实在LdrpLoadDll中也会进入该临界区,但是我们不必关注了。因为只要一次没出临界区就可以满足死锁的条件了。
我们再看下卸载DLL时发生的进入临界区场景,请看堆栈
我们将关注FreeLibrary和LdrpCallInitRoutine之间的代码逻辑。我们用IDA查看LdrUnLoadDll
我们看到LdrUnloadDll几乎所有操作都是在临界区执行的。 以上两段从源码级证明了加载和卸载DLL导致的DllMain的调用(以及不调用)都是在临界区中完成的。