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里.经验少,不知道为什么,于是在高端调试论坛里发帖求助, ...
随机推荐
- python中selenium操作下拉滚动条方法
场景:在当前显示的页面元素不可见,拖动下拉条后元素就出来了. 解决方法: 在python中有几种方法解决这种问题,简单介绍下,给需要的人: 方法一)使用js脚本直接操作,方法如下: #将页面滚动条拖到 ...
- nginx 反向代理实现负载均衡*配置实战
重要点: 1配置反向代理多虚拟主机节点服务器 2经过反向代理后的节点服务器记录用户IP 3与反向代理配置相关的更多参数说明 4根据URL目录地址转发 (1)根据URL中的目录地址实现代理转发(动静分离 ...
- ContentProvider 共享数据
onCreate 其它应用第一次访问时被调. insert 外部应用使用此方法添加数据. delete 外部应用使用此方法删除数据. update 外部应用使用此方法更新数据. query 外部应 ...
- java.lang.reflect.Method.getAnnotation()方法示例
转: java.lang.reflect.Method.getAnnotation()方法示例 作者: 初生不惑 Java技术QQ群:227270512 / Linux QQ群:479429477 ...
- LVS系列二、LVS集群-DR模式
一. LVS-DR和LVS-IP TUN集群概述 1. Direct Routing(直接路由) Director分配请求到不同的real server.real server处理请求后直接回应给用 ...
- 在HbuilderX的Uni-app中引入crypto-js md5
在 hbuilderX的菜单“视图”中选择“显示终端”,在终端中把当前目录定位到uni-app的根目录,这样才可以把crypto-js库安装在正确的位置,在终端中键入:npm install cryp ...
- redis集群 应该注意的问题
redis cluster注意的问题 : ‘cluster-require-full-coverage’参数的设置.该参数是redis配置文件中cluster模式的一个参数,从字面上基本就能看出它的作 ...
- centos启动排障
centos6 启动引导第一阶段破坏 破坏mbr前446字节 先备份 [root@centos6 ~]# dd if=/dev/sda of=/root/mbr bs=1 count=446 破坏 [ ...
- 《JAVA语言》课问题汇总
一.阅读相应教材,或者使用互联网搜索引擎,弄清楚反码.补码跟原码这几个概念,然后编写示例程序,对正数.负数进行各种位操作,观察输出结果,与手工计算的结果进行比对,看看Java中的数是采用上述哪种码表示 ...
- 小记--------spark的worker原理分析及源码分析