DisableThreadLibraryCalls与DLLMain死锁
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死锁的更多相关文章
- [转]DllMain中不当操作导致死锁问题的分析——DllMain中要谨慎写代码(完结篇)
在CSDN中发现这篇文章,讲解的比较详细,所以在这里备份一个.原文链接:http://blog.csdn.net/breaksoftware/article/details/8167641 DllMa ...
- DllMain加载其他DLL造成的死锁问题及其解决办法
使用VS 2008新建一个MFC ActiveX工程,因为在工程里要用到GDI+.我习惯把初始化GDI+库的代码放在应用程序类的InitInstance函数,对应的销毁代码放在ExitInstance ...
- std::thread 在DLLMain 中会发生死锁 std::thread cause deadlock in DLLMain
注意不要再DLLMain中使用 std::thread 否则会发生死锁. 但是可以使用 _beginthreadex (此函数可以使用lambda) 或者直接使用windows的底层函数: Creat ...
- [原]调试实战——使用windbg调试DLL卸载时的死锁
原调试debugwindbg死锁deadlock 前言 最近我们的程序在退出时会卡住,调查发现是在卸载dll时死锁了.大概流程是这样的:我们的dll在加载的时候会创建一个工作线程,在卸载的时候,会设置 ...
- 正尝试在 OS 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码,这样...
出错提示: 正尝试在 OS 加载程序锁内执行托管代码.不要尝试在 DllMain 或映像初始化函数内运行托管代码,这样做会导致应用程序挂起. 原因分析: .NET2.0中增加了42种非常强大的调试助手 ...
- 正试图在 os 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码
来自:http://www.cnblogs.com/lcxu2/archive/2011/01/16/2004016.html 正试图在 os 加载程序锁内执行托管代码.不要尝试在 DllMain 或 ...
- 正尝试在 OS 载入程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内执行托管代码,这样做会导致应用程序挂起。
出错提示: 正尝试在 OS 载入程序锁内执行托管代码. 不要尝试在 DllMain 或映像初始化函数内执行托管代码,这样做会导致应用程序挂起. 原因分析: .NET2.0中添加了42种非常强大的调试助 ...
- 在AE二次开发中出“正试图在 OS 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码,这样做会导致应用程序挂起。”异常解决方案
今天的一个项目总用到了AE的开发组件,也就是ESRI公司提供的一系列的开发包(组件)都是以dll(动态链接库的形式)然后今天在调试的时候却出现了“正试图在 OS 加载程序锁内执行托管代码.不要尝试在 ...
- [原]调试实战——使用windbg调试TerminateThread导致的死锁
原调试debugwindbg死锁deadlock 前言 项目里的一个升级程序偶尔会死锁,查看dump后发现是死在了ShellExecuteExW里.经验少,不知道为什么,于是在高端调试论坛里发帖求助, ...
随机推荐
- Ubuntu使用PBIS认证
1:下载 https://github.com/BeyondTrust/pbis-open/releases wget https://github.com/BeyondTrust/pbis-open ...
- EBS 查看输出HTML报表问题总结
问题一: 请求输出格式为HTML(如下图,默认浏览器输出),希望 查看输出 的时候能够实现excel输出. 解决方法: 路径:系统管理员/安装/浏览器选项 注:维护如下记录 文件格式:HTML ...
- MySQL5.7 创建及查看数据库
1.创建数据库语句create database语句是在MySQL实例上创建一个指定名称的数据库.create schema语句的语义和create database是一样的. 2.语法解析 CREA ...
- parted对大容量磁盘进行分区
Linux系统中MBR与GPT的区别 主引导记录(Master Boot Record , MBR)是指一个存储设备的开头 512 字节.它包含操作系统的引导器和存储设备的分区表. 全局唯一标识分区表 ...
- .netcore多语言解决方案
这里本文使用Microsoft.AspNetCore.Localization来实现多语言的解决方案 默认是包含这个包的,所有不需要再额外安装 首先需要注入我们需要的服务 1.在startup.cs中 ...
- presto安装
下载 presto-server-0.217 包 进入presto根目录,新建脚本deploy.sh mkdir etc cd etc #配置 cat >config.properties &l ...
- MATLAB学习(六)绘图图形功能
>> x=0:.1:2*pi;plot(x,sin(x),x,cos(x)) >> plot(x,sin(x),'p ...
- HTML <a> 标签的 href 属性_定位资源
* 定位资源 ** 如果想要定位资源:定义一个位置 <a name="top">顶部</a> ** 回到这个位置 <a href="#top ...
- Delphi 10.2.3 精简版自动激活Embarcadero Delphi 10.2.3 v25.0.29899.2631 Lite v14.4
下载:https://maxwoods.ctfile.com/u/758954/28516301 Embarcadero.Delphi.10.2.RTM.v25.0.26309.314.Lite.v1 ...
- [Http] Difference between POST and GET?
What is the difference between POST and GET HTTP requests? GET and POST are two different types of H ...