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

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

DllMain的相关特性

首先列出《DllMain中不当操作导致死锁问题的分析--进程对DllMain函数的调用规律的研究和分析》中论证的11个特性:

  1. Dll的加载不会导致之前创建的线程调用其DllMain函数。

  2. 线程创建后会调用已经加载了的DLL的DllMain,且调用原因是DLL_THREAD_ATTACH。(DisableThreadLibraryCalls会导致该过程不被调用)
  3. TerminateThread方式终止线程是不会让该线程去调用该进程中加载的Dll的DllMain。
  4. 线程正常退出时,会调用进程中还没卸载的DLL的DllMain,且调用原因是DLL_THREAD_DETACH。
  5. 进程正常退出时,会调用(不一定是主线程)该进程中还没卸载的DLL的DllMain,且调用原因是DLL_PROCESS_DETACH。
  6. 加载DLL进入进程空间时(和哪个线程LoadLibrary无关),加载它的线程会调用DllMain,且调用原因是DLL_PROCESS_ATTACH。
  7. DLL从进程空间中卸载出去前,会被卸载其的线程调用其DllMain,且调用原因是DLL_PROCESS_DETACH。
  8. TerminateProcess 将导致线程和进程在退出时不对未卸载的DLL进行DllMain调用。
  9. ExitProcess将导致主线程意外退出,子线程对未卸载的DLL进行了DllMain调用,且调用原因是DLL_PROCESS_DETACH。
  10. ExitThread是最和平的退出方式,它会让线程退出前对未卸载的DLL调用DllMain。
  11. 线程的创建和退出不会对调用了DisableThreadLibraryCalls的DLL调用DllMain。

不要在DllMain中做的事情

一、直接或者间接调用LoadLibrary(Ex)

假如我们在A.dll中的DllMain收到DLL_PROCESS_ATTACH时,加载了B.dll;而B.dll中的DllMain在收到DLL_PROCESS_ATTACH时又去加载A.dll。则产生了循环依赖。但是注意不要想当然认为这个过程是A.dll的DllMain调用了B.dll的DllMain,B.dll的DllMain再调用了A.dll的DllMain这样的死循环。即使不出现循环依赖,如果出现《DllMain中不当操作导致死锁问题的分析——线程中调用GetModuleFileName、GetModuleHandle等导致死锁》中第三个例子的情况,也会死锁的。

二、使用CoInitializeEx

在CoInitializeEx底层会调用LoadLibraryEx,原因同A。

三、使用CreateProcess

CreateProcess在底层执行了加载DLL的操作。我用IDA查看Kernel32中的CreateProcess可以发现其底层调用的CreateProcessInternalW中有

四、使用User32或Gdi32中的函数

User32和Gdi32中部分函数在调用的底层会加载其他DLL。

五、使用托管代码

运行托管代码需要加载其他DLL。

六、与其他线程同步执行

《DllMain中不当操作导致死锁问题的分析--加载卸载DLL与DllMain死锁的关系》《DllMain中不当操作导致死锁问题的分析--导致DllMain中死锁的关键隐藏因子》和《DllMain中不当操作导致死锁问题的分析--线程退出时产生了死锁》可知,进程创建和销毁以及DLL的加载都要进入PEB的LoadLock临界区。如果占用了LoaderLock临界区的线程在等待一个需要经过临界区才能结束的线程时,就发生了死锁。以上3篇博文中均有案例。

七、同步对象

如果该同步对象的释放需要获得PEB中的LoaderLock,而占用该临界区的线程又要去等待这个同步对象,则会死锁。其实F中的线程也算是个同步对象。案例详见《DllMain中不当操作导致死锁问题的分析——线程中调用GetModuleFileName、GetModuleHandle等导致死锁》中例子。

八、使用CreateThread

理由同六。

九、使用ExitThread

理由同六。

十、寄希望于DisableThreadLibraryCalls解决死锁问题

《DllMain中不当操作导致死锁问题的分析--DisableThreadLibraryCalls对DllMain中死锁的影响》可知。DisableThreadLibraryCalls的实现逻辑是:找到PEB结构中用于保存加载器信息的结构体对象Ldr。

Ldr对象的InMemoryOrderModuleList用户保存已经加载的DLL的链表。

它遍历这个链表,找到调用DisableThreadLibraryCalls的DLL的信息,将该信息中的Flags字段设置或上0x40000。

而创建线程在底层将调用LdrpInitializeThread(详见《DllMain中不当操作导致死锁问题的分析--DisableThreadLibraryCalls对DllMain中死锁的影响》)。该函数一开始便进入了PEB中LoaderLock临界区,在该临界区中根据PEB中LDR的InMemoryOrderModuleList遍历加载的DLL,然后判断该DLL信息的Flags字段是否或上了0x40000。如果或上了,就不调用DllMain。如果没或上,就调用DllMain。这说明DisableThreadLibraryCalls对创建线程时是否进入临界区无关。

在退出线程时底层将调用LdrShutdownThread(详见《DllMain中不当操作导致死锁问题的分析--线程退出时产生了死锁》)。该函数逻辑和LdrpInitializeThread相似,只是在调用DllMain时传的是DLL_THREAD_DETACH。所以DisableThreadLibraryCalls对LdrShutdownThread是否进入临界区也是没有影响的。

时间: 2024-10-07 05:16:22

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

[转载]DllMain中不当操作导致死锁问题的分析——线程中调用GetModuleFileName、GetModuleHandle等导致死锁

(转载于breaksoftware的csdn博客) 之前的几篇文章已经讲解了在DllMain中创建并等待线程导致的死锁的原因.是否还记得,我们分析了半天汇编才知道在线程中的死锁位置.如果对于缺乏调试经验的同学来说,可能发现这个位置有点麻烦.那么本文就介绍几个例子,它们会在线程明显的位置死锁掉. DLL中的代码依旧简单.它获取叫EVENT的命名事件,然后等待这个事件被激活.激活的操作自然放在线程中.这次我们不用在DLL中创建线程,而是在Exe中创建. switch (ul_reason_for_c

[转载]DllMain中不当操作导致死锁问题的分析--导致DllMain中死锁的关键隐藏因子2

(转载于breaksoftware的csdn博客) 本文介绍使用Windbg去验证<DllMain中不当操作导致死锁问题的分析--导致DllMain中死锁的关键隐藏因子>中的结论,调试对象是文中刚开始那个例子. 1 g 让程序运行起来 2 ctrl+break 中断程序 3 ~ 查看线程数 其实该程序自己运行起来的线程只有ID为0.TID为afc的线程.18c4线程是我们在windbg中输入ctrl+break,导致windbg在我们调试的进程中插入的一个中断线程.以后我们看到是这个线程的操作

[转载]DllMain中不当操作导致死锁问题的分析--线程退出时产生了死锁

(转载于breaksoftware的csdn博客) 我们回顾下之前举得例子 case DLL_PROCESS_ATTACH: { printf("DLL DllWithoutDisableThreadLibraryCalls_A:\tProcess attach (tid = %d)\n", tid); HANDLE hThread = CreateThread(NULL, 0, ThreadCreateInDllMain, NULL, 0, NULL); WaitForSingleO

[转载]DllMain中不当操作导致死锁问题的分析--进程对DllMain函数的调用规律的研究和分析

(转载于breaksoftware的csdn博客) 不知道大家是否思考过一个过程:系统试图运行我们写的程序,它是怎么知道程序起始位置的?很多同学想到,我们在编写程序时有个函数,类似Main这样的名字.是的!这就是系统给我们提供的控制程序最开始的地方(注意这儿是提供给我们的,而实际有比这个还要靠前的main).于是看到DllMain就可以想到它是干嘛的了:Dll的入口点函数.那何时调用这个函数的呢?以及各种调用场景都传给了它什么参数呢? 进程对DLL的载入卸载,以及新线程的创建和退出都会导致对Dl

MySQL Innodb表导致死锁日志情况分析与归纳

发现当备份表格的sql语句与删除该表部分数据的sql语句同时运行时,mysql会检测出死锁,并打印出日志 案例描述在定时脚本运行过程中,发现当备份表格的sql语句与删除该表部分数据的sql语句同时运行时,mysql会检测出死锁,并打印出日志.两个sql语句如下:(1)insert into backup_table select * from source_table(2)DELETE FROM source_table WHERE Id>5 AND titleWeight<32768 AND

【discuzX2】/source/class/class_core.php文件中数据库操作类DB及db_mysql分析

<?php /** * Discuz MySQL 类的支持 程序中一般不直接使用此类,而是使用DB类,DB类对db_mysql类中的方法又进行了二次封装 * */ class db_mysql { var $tablepre; var $version = ''; var $querynum = 0; var $slaveid = 0; var $curlink; var $link = array(); var $config = array(); var $sqldebug = array(

在Activity的onCreate方法中显示PopupWindow导致异常的原因分析及解决方案

一.前言 在某些情况下,我们需要一进入Activity就显示PopupWindow,比如常见的选择界面.但由于PopupWindow是依附于Activity的,如果Activity没有创建完成,Activity还没完全显示出来就显示PopupWindow的话,会出现异常现象. 二.问题复现 我在Activity的onCreate()方法中调用如下方法: public void show( ){ if( null != mPopupWindow ){ mPopupWindow.showAtLoca

[译]async/await中使用阻塞式代码导致死锁

原文:[译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的两篇博文中翻译过来. 原文1:Don'tBlock on Async Code 原文2:why the AspNetSynchronizationContext was removed 示例代码:async_await中使用阻塞式代码导致死锁.rar 一.async/await 异步代码运行流

ExtJs中disabled和readOnly美观度的分析

ExtJs中disabled和readOnly美观度的分析 ExtJs中,如果设置输入框为只读属性,一般第一考虑的都是readonly=true 它的效果和正常输入框一样,但是不允许输入: 然而,它很容易引起歧义,让用户第一感觉是它是输入框,有输入信息的冲动,其实不然: 这时候,可以考虑使用disabled=true属性 这下绝对不会认为可输入,一看就知道不允许修改,但字体颜色明显很模糊,所以效果不佳: 因此,使用中常常仍然使用readOnly=true,但修改背景颜色来做到disabled的更