首页 > 逆向调试 > DllMain多线程死锁

DllMain多线程死锁

在调试中遇到的一种死锁情况——DllMain死锁。DllMain是可选择的DLL入口指针,当进程和线程启动和终止时被系统调用,分别进行创建资源和释放资源等操作;而且也可以在DLL被装载进进程空间时(即DllMain响应DLL_PROCESS_ATTACH通知时)创建线程,在DLL从进程空间卸载时(即DllMain响应DLL_PROCESS_DETACH通知时)结束线程。但是在DllMain对创建或结束进程需要特别注意DllMain的序列化调用规则,使用不当将会造成死锁。下面是参考《Windows高级调试》的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Reference: 《Windows高级调试》
// 一个引发死锁的问题DLL代码
#include <windows.h>
#include <stdio.h>
 
DWORD WINAPI InitDllProc( LPVOID lpParam ) 
{ 
    LoadLibrary(L"ole32.dll");                
    return 1;
}
 
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    BOOL bRet=FALSE;
    switch(fdwReason)
    {
        case DLL_PROCESS_ATTACH:
        {
            DWORD dwId=0;
            HANDLE hThread=NULL;
 
            hThread = CreateThread(NULL, 0, InitDllProc, NULL, 0, &dwId);        
            if(hThread)
            {
                WaitForSingleObject(hThread, INFINITE);
                CloseHandle(hThread);
                bRet=TRUE;
            }
        }
        break;
    }
 
    return bRet;
}

DllMain会序列化处理DLL_PROCESS_ATTACH、DLL_THREAD_ATTACH、DLL_THREAD_DETACH和DLL_PROCESS_DETACHDLL这四个通知。

上面的代码中,当DLL被映射到进程的地址空间时,DLL响应DLL_PROCESS_ATTACH通知,此时,DLL又创建了一个工作者线程,并等待工作者线程的结束。当创建工作者线程时,系统必须用DLL_THREAD_ATTACH来再次调用DllMain,但是老线程还没有完成对DllMain的处理,因此新线程会被挂起。问题出在对WaitForSingleObject的调用,这个函数会把当前正在执行的线程挂起,直到新线程运行结束。但是,由于新线程为了等待当前线程退出DllMain函数而被挂起,因此他从来没有机会执行,于是产生死锁。

有一个API可以禁止产生DLL_THREAD_ATTACH和DLL_THREAD_DETACH通知,那就是DisableThreadLibraryCalls(可以在创建线程前使用)。
Disables the DLL_THREAD_ATTACH and DLL_THREAD_DETACH notifications for the specified dynamic-link library (DLL). This can reduce the size of the working set for some applications.
但是这并不能解决死锁的产生。因为在系统创建进程时,会同时创建一个锁,每个进程都有自己的锁。当进程中的线程调用映射到进程地址空间的DLL的DllMain函数时,会用这个锁来同步各个线程。所以一个可行的解决死锁的方案是重新设计代码,不在DllMain中使用WatiForSingleObject。

参考资料:《Windows高级调试》&《Windows核心编程》


觉得文章还不错?点击此处对作者进行打赏!


本文地址: 程序人生 >> DllMain多线程死锁
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


更多



分类: 逆向调试 标签: , , ,
  1. Drizzt
    2012年2月18日02:15 | #1

    我是你的fans了,哈哈。
    请问一下第三段的这句话怎么理解:“当创建工作者线程时,系统必须用DLL_THREAD_ATTACH来再次调用DllMain,但是老线程还没有完成对DllMain的处理,因此新线程会被挂起。”
    为什么这里说“老线程还没有完成DllMain的处理”?线程怎样才算完全结束了dllmain的处理?
    case DLL_PROCESS_ATTACH 这一句说明dll装入当前进程的地址空间,原因是程序开始,或者调用了loadlibrary;
    hThread = CreateThread(NULL, 0, InitDllProc, NULL, 0, &dwId); 这句是创建一个新的进程,返回一个handle;
    WaitForSingleObject(hThread, INFINITE); wait()函数和linux系统的一样,都是等待hThread进入signal state;
    这个程序里面的每一小部分大概能懂,但是放在一起就不知道怎么回事了….
    求指导,谢谢。

    [回复]

    代码疯子 回复:

    @Drizzt, 第25行WaitForSingleObject(hThread, INFINITE)表示等待hThread这个线程直到hThread结束,如果hThread没有结束就会一直阻塞在这里。也就是说子线程一旦创建成功,主线程就阻塞在第25行了。

    子线程之所以不会结束:子线程创建时会产生DLL_THREAD_DETACH,而DLL对DLL_THREAD_DETACH的响应必须在DLL_PROCESS_ATTACH处理完之后。就是说子线程在等待主线程完成DLL_PROCESS_ATTACH的代码。

    这样互相等待就死锁了。我看你可能是没明白WaitForSingleObject的意思。
    有个DisableThreadLibraryCalls可以禁止产生DLL_THREAD_ATTACH、DLL_THREAD_DETACH消息,网上又说也不能完全解决这里的死锁问题,我用的时候确实还是存在问题

    [回复]

    代码疯子 回复:

    @Drizzt,

    子线程创建时会产生DLL_THREAD_DETACH,而DLL对DLL_THREAD_DETACH的响应必须在

    这里写错了,是DLL_THREAD_ATTACH

    [回复]

    Drizzt 回复:

    @代码疯子, 嗯,终于明白了,谢谢疯子哥。本质就是系统在调用DllMain的时候必须得到一个锁。这样老的线程hold住了一个锁,但是代码中要求等待新的线程结束。然而新的线程也必须获得这个锁才能结束。所以死锁了。
    我看看linux在引用动态链接的时候有没有类似的问题:) 如有消息,告诉你一下。

    [回复]