1、首先写个简单的DLL,用来验证

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
printf("DLL_PROCESS_ATTACH\n");
}
break; case DLL_PROCESS_DETACH:
{
printf("DLL_PROCESS_DETACH\n");
}
break; case DLL_THREAD_ATTACH:
{
printf("DLL_THREAD_ATTACH\n");
}
break;
case DLL_THREAD_DETACH:
{
printf("DLL_THREAD_DETACH\n");
}
break;
}
return TRUE;
}

2、再写一个测试用的EXE:

unsigned int __stdcall ThreadFun(PVOID pM);

//int WinMain(HINSTANCE hInstance,
// HINSTANCE hPrevInstance,
// LPSTR lpCmdLine,
// int nCmdShow)
void main()
{
HMODULE hMod = LoadLibrary("TestDLL.dll"); HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
if(!hThread)
{
return;
} WaitForSingleObject(hThread, INFINITE); FreeLibrary(hMod);
getchar();
exit(0);
// return 0;
} unsigned int __stdcall ThreadFun(PVOID pM)
{
printf("线程ID号为%4d的子线程说:Hello World\n", GetCurrentThreadId());
return 0;
}

3、你把这个EXE跑一遍就会发现,在线程被创建的时候,也会执行一次已经被加载的DLL的DLLMain,加载原因是DLL_THREAD_ATTACH,离开时也同理,最终显示结果如下:

4、如果你在原先的DLL代码中加入DisableThreadLibraryCalls调用,那么就不会在线程启动时去执行这个DLL的DLLMain了

当然这里关于线程是否执行DLLMain的DLL_THREAD_ATTACH和DLL_THREAD_DETACH的,还有一些比较细节的逻辑,可以参考:

https://blog.csdn.net/breaksoftware/article/details/8142339

5、在DLL中会产生死锁的情况及原因

5.1、DLLMain中直接或者间接调用LoadLibrary:

起初我以为,假如我们在A.dll中的DllMain收到DLL_PROCESS_ATTACH时,加载了B.dll;而B.dll中的DllMain在收到DLL_PROCESS_ATTACH时又去加载A.dll,则产生了循环依赖,然而经试验检验,并不会,因为如果A.dll已经加载进EXE的内存了,B.dll就不会再去加载A.dll了,实验如下:

结果如下:

并没有发生死锁。第一这里没有发生调用循环;第二这里也没有发生死锁。

首先,为什么没有发生我们预计的循环调用呢?我们来一步步分析两个DLL相互加载的过程。

看调用堆栈发现,TestDLL2又调用了LdrLoadDll,看这架势是要递归加载的:

然而当我跟进去调试的时候,有某一个地方触发了返回,并没有调用LdrpRunInitializeRoutines,这个地方就是LdrpFindOrMapDll。但凡你要加载一个DLL,LdrpFindOrMapDll就会去查找是否已经加载过这个DLL了。

如果没有加载过某个DLL那么LdrpFindOrMapDll的最后一个参数会被写入1,而如果加载过了这个参数就会被写入0:

DLL的查找方式就是计算Hash并保存在一张全局的Hash表中:

其次,这里为什么没有发生死锁?原因很简单,我调试这里时:

发现TestDLL在再次调用LoadLibrary前后,LdrpLoaderLock的变化如下(命令是dt _RTL_CRITICAL_SECTION 770520c0):

也就是说这是同一个线程对LdrpLoaderLock上了锁,所以并没有死锁。那么再来验证下如果是另一个线程里,调用LoadLibrary是否会发生死锁呢?

我们把实验代码稍微改动下,让TestDLL在DLLMain中起一个线程:

exe中为了保证TestDLL不被Free掉,我就一直Sleep了:

如愿,运行起来就在某处卡死掉了。我们查看所有的临界区:

LockCount=1表示有一个线程正在等待这个临界区。然后我们查看这个临界区的详细信息:

0号线程正在等待1号线程拥有的临界区:

或者我们直接用!locks命令查看:

我们验证下0号线程:

再验证下1号线程:

这里总结下,DLLMain中并不是因为LoadLibrary中的循环调用造成的死锁,而是由于其它原因。什么原因?一个国外网站上给出了解释:

Your DllMain function runs inside the loader lock,one of the few times the OS lets you run code while one of its internal locks is held. This means that you must be extra careful not to violate a lock hierarchy in your DllMain; otherwise, you are asking for a deadlock.

The loader lock is taken by any function that needs to access the list of DLLs loaded into the process.loader lock This includes functions like GetModuleHandle and GetModuleFileName. If your DllMain enters a critical section or waits on a synchronization object, and that critical section or synchronization object is owned by some code that is in turn waiting for the loader lock, you just created a deadlock.

所以我们的结论是:

无法通过使用DisableThreadLibraryCalls解决死锁问题。因为,DisableThreadLibraryCalls只是用来防止EXE中起线程时重新执行DLLMain,但是你在主线程第一次Load TestDLL.dll时就已经出现死锁了。

DisableThreadLibraryCalls与DLLMain死锁的更多相关文章

  1. [转]DllMain中不当操作导致死锁问题的分析——DllMain中要谨慎写代码(完结篇)

    在CSDN中发现这篇文章,讲解的比较详细,所以在这里备份一个.原文链接:http://blog.csdn.net/breaksoftware/article/details/8167641 DllMa ...

  2. DllMain加载其他DLL造成的死锁问题及其解决办法

    使用VS 2008新建一个MFC ActiveX工程,因为在工程里要用到GDI+.我习惯把初始化GDI+库的代码放在应用程序类的InitInstance函数,对应的销毁代码放在ExitInstance ...

  3. std::thread 在DLLMain 中会发生死锁 std::thread cause deadlock in DLLMain

    注意不要再DLLMain中使用 std::thread 否则会发生死锁. 但是可以使用 _beginthreadex (此函数可以使用lambda) 或者直接使用windows的底层函数: Creat ...

  4. [原]调试实战——使用windbg调试DLL卸载时的死锁

    原调试debugwindbg死锁deadlock 前言 最近我们的程序在退出时会卡住,调查发现是在卸载dll时死锁了.大概流程是这样的:我们的dll在加载的时候会创建一个工作线程,在卸载的时候,会设置 ...

  5. 正尝试在 OS 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码,这样...

    出错提示: 正尝试在 OS 加载程序锁内执行托管代码.不要尝试在 DllMain 或映像初始化函数内运行托管代码,这样做会导致应用程序挂起. 原因分析: .NET2.0中增加了42种非常强大的调试助手 ...

  6. 正试图在 os 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码

    来自:http://www.cnblogs.com/lcxu2/archive/2011/01/16/2004016.html 正试图在 os 加载程序锁内执行托管代码.不要尝试在 DllMain 或 ...

  7. 正尝试在 OS 载入程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内执行托管代码,这样做会导致应用程序挂起。

    出错提示: 正尝试在 OS 载入程序锁内执行托管代码. 不要尝试在 DllMain 或映像初始化函数内执行托管代码,这样做会导致应用程序挂起. 原因分析: .NET2.0中添加了42种非常强大的调试助 ...

  8. 在AE二次开发中出“正试图在 OS 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码,这样做会导致应用程序挂起。”异常解决方案

    今天的一个项目总用到了AE的开发组件,也就是ESRI公司提供的一系列的开发包(组件)都是以dll(动态链接库的形式)然后今天在调试的时候却出现了“正试图在 OS 加载程序锁内执行托管代码.不要尝试在 ...

  9. [原]调试实战——使用windbg调试TerminateThread导致的死锁

    原调试debugwindbg死锁deadlock 前言 项目里的一个升级程序偶尔会死锁,查看dump后发现是死在了ShellExecuteExW里.经验少,不知道为什么,于是在高端调试论坛里发帖求助, ...

随机推荐

  1. IPv4 地址分类-for what

    怎么分的:IPV4 地址分类 A B C D E 分来做什么:IP地址为什要分类?就是a类,b类,c类...? - wuxinliulei的回答 - 知乎

  2. 解决spring-boot 各版本包冲突兼容的方法

    思路        在微服务盛行的当下,spring boot 流行程度已经家喻户晓.但同时,随着spring boot 快速迭代,出现了很多版本,比如当前已经推出了2.2.x-SNAPSHOT/ , ...

  3. tornado框架学习

    tornado是一个非阻塞的web服务器框架,每秒可以处理上千个客户端连接(都是在一个线程中,不需要为每个客户端创建线程,资源消耗少),适合用来开发web长连接应用,如long polling(轮询) ...

  4. linux下的开源NFC协议栈

    1. 协议栈名称 neardal 2. 源码 https://github.com/connectivity/neardal.git 3. 由谁维护? intel 4. 基于neardal的nfc协议 ...

  5. RCAN——Image Super-Resolution Using Very Deep Residual Channel Attention Networks

    1. 摘要 在图像超分辨领域,卷积神经网络的深度非常重要,但过深的网络却难以训练.低分辨率的输入以及特征包含丰富的低频信息,但却在通道间被平等对待,因此阻碍了网络的表示能力. 为了解决上述问题,作者提 ...

  6. css滚动条美化

    /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/ ::-webkit-scrollbar { width: 5px; height: 5px; background-color: #F5F5 ...

  7. React里单页面div自适应浏览器高度占满屏幕

    可以用绝对定位方式,让div占满屏幕,css样式如下: height: 100%; width: 100%; position: absolute; top: 0px; bottom: 0px;

  8. mybatis映射文件祥解(StudentMapper.xml)

    1)以下是StudentMapper.xml文件,提倡放在与实体同目录下,文件名任意 <?xml version="1.0" encoding="UTF-8&quo ...

  9. TASK的开始与暂停

    namespace WpfApplication1 { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> publi ...

  10. 利用Oracle定时任务重置序列

    业务需求是:二元化编号规则:RYH+年月+001(开始),按月计算,每月1号重置为001 数据库中已有序列和函数如下: 解决方法:采用Oracle定时任务,每月1号重置该序列从1开始增长,SQL如下: ...