DLL释放引起的死锁问题

一,问题介绍

     最近在做代码重构,今早对前两天重构的某个组件进行自测时,突然发现停止程序运行时,出现了卡死,进程退不出,查看日志发现是FreeLibrary没有走完。打开任务管理器-》性能-》资源监视器,找到进程,右键分析分析等待链,结果如下:
在这里插入图片描述
     出现了死锁,16800线程是主线程(调用FreeLibrary),12128是DLL中的工作线程,两个线程互相等待,导致死锁,进程退不出。

二,问题分析

     打开Windbg,配好pdb文件和源码路径,attach到该进程上,输入!locks命令,查看当前进程锁的情况:
在这里插入图片描述     可以发现锁771f20c0处于死锁状态,它此时被30f8线程占有。再输入~*kb查看所有线程,看看哪个线程正在等待锁771f20c0:
在这里插入图片描述     线程42d4在等待锁771f20c0,而锁771f20c0被线程30f8占有,我们再看看30f8线程:
在这里插入图片描述
     它也在等待,不过从代码中看得出来它在等待线程42d4的退出,而42d4线程又在等待771f20c0锁的释放,可是锁771f20c0被30f8线程占有,由此产生的死锁,导致进程退不出来:
在这里插入图片描述
     可是我们在线程30f8中并没有看到771f20c0锁的相关信息,这个锁是在42d2线程调用LdrShutdownThread时进行等待的,我们可以在win2K中找到它的实现:

VOID
LdrShutdownThread (
    VOID
    )
/*++
Routine Description:
    This function is called by a thread that is terminating cleanly.
    It's purpose is to call all of the processes DLLs to notify them
    that the thread is detaching.
Arguments:
    None
Return Value:
    None.
--*/
{
    PPEB Peb;
    PLDR_DATA_TABLE_ENTRY LdrDataTableEntry;
    PDLL_INIT_ROUTINE InitRoutine;
    PLIST_ENTRY Next;
 
    Peb = NtCurrentPeb();
 
    RtlEnterCriticalSection(&LoaderLock);
 
    try {
        //
        // Go in reverse order initialization order and build
        // the unload list
        //
 
        Next = Peb->Ldr->InInitializationOrderModuleList.Blink;
        while ( Next != &Peb->Ldr->InInitializationOrderModuleList) {
            LdrDataTableEntry
                = (PLDR_DATA_TABLE_ENTRY)
                  (CONTAINING_RECORD(Next,LDR_DATA_TABLE_ENTRY,InInitializationOrderLinks));
 
            Next = Next->Blink;
 
            //
            // Walk through the entire list looking for
            // entries. For each entry, that has an init
            // routine, call it.
            //
 
            if (Peb->ImageBaseAddress != LdrDataTableEntry->DllBase) {
                if ( !(LdrDataTableEntry->Flags & LDRP_DONT_CALL_FOR_THREADS)) {
                    InitRoutine = (PDLL_INIT_ROUTINE)LdrDataTableEntry->EntryPoint;
                    if (InitRoutine && (LdrDataTableEntry->Flags & LDRP_PROCESS_ATTACH_CALLED) ) {
                        if (LdrDataTableEntry->Flags & LDRP_IMAGE_DLL) {
                            if ( LdrDataTableEntry->TlsIndex ) {
                                LdrpCallTlsInitializers(LdrDataTableEntry->DllBase,DLL_THREAD_DETACH);
                                }
 
#if defined (WX86)
                            if (!Wx86ProcessInit ||
                                LdrpRunWx86DllEntryPoint(InitRoutine,
                                                        NULL,
                                                        LdrDataTableEntry->DllBase,
                                                        DLL_THREAD_DETACH,
                                                        NULL
                                                        ) ==  STATUS_IMAGE_MACHINE_TYPE_MISMATCH)
#endif
                               {
                                LdrpCallInitRoutine(InitRoutine,
                                                    LdrDataTableEntry->DllBase,
                                                    DLL_THREAD_DETACH,
                                                    NULL);
                                }
                            }
                        }
                    }
                }
            }
 
        //
        // If the image has tls than call its initializers
        //
 
        if ( LdrpImageHasTls ) {
            LdrpCallTlsInitializers(NtCurrentPeb()->ImageBaseAddress,DLL_THREAD_DETACH);
            }
        LdrpFreeTls();
 
    } finally {
 
        RtlLeaveCriticalSection(&LoaderLock);
    }
}

     锁771f20c0也就是LoaderLock,LoaderLock是dll加载和释放时微软内部给的一把锁(就是一个临界区),它在LoadLibrary和FreeLibrary时都会用到,我们以FreeLibrary来看:
在这里插入图片描述
     在红色函数内部就会去获取LoaderLock这把锁,继续走会到DLL的各个析构函数里面。而在我们的析构函数里会通知线程退出,并用WaitForSingleObject等待各个创建的线程退出,最终也会调用_LdrShutdownThread,而这个函数内部也在等待LoaderLock这把锁,由此导致了死锁。

三,问题总结

     对于DLL中的线程释放,最好提供一个导出函数,函数内部专门处理各个线程的释放,由外部调用方主动调用该函数,切记不可使用全局类或者静态类的析构函数来进行线程的释放退出。

参考文献:
1,https://blog.csdn.net/breaksoftware/article/details/8163663
2,《windows核心编程》第二十章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Simple Simple

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值