仅当动态卸载DLL时,DLL才应释放堆内存吗?

时间:2018-12-17 16:04:17

标签: winapi visual-c++ dll entry-point dllmain

问题目的:对MS docs of DllMain的真实性检查。

“常见”知识是您在DllMain中不应该做太多事情,有些绝对不能做的事,有些best practises

我现在偶然发现了文档中的一个新宝石,这对我来说意义不大:(我的英语)

  

在处理lpReserved时,DLL应该释放资源,例如   仅在动态卸载DLL时才堆内存(   {{1}}参数为NULL)。如果该过程正在终止(   lpvReserved参数为非NULL),进程中的所有线程   当前线程已经退出或已显式   通过调用ExitProcess函数终止,这可能会导致   一些过程资源,例如处于不一致状态。在这   在这种情况下,DLL清理资源是不安全的。代替,   DLL应该允许操作系统回收内存。

由于在DllMain / DETACH期间清除了全局C ++对象,这意味着全局C ++对象一定不能释放任何动态内存,因为堆可能处于不一致状态。 /当DLL被“静态链接”到可执行文件时。 /当然不是我所能看到的-各种(我们的和第三方)库的全局C ++对象(如果有的话)在其析构函数中进行分配和释放。 (除非有其他订购错误,否则)

那么,此警告针对的是什么具体的技术问题?

由于该段提到了线程终止,所以当某些线程未正确清理时会出现堆破坏问题吗?

2 个答案:

答案 0 :(得分:1)

ExitProcess API通常会执行以下操作:

  • 输入Loader Lock关键部分
  • 通过GetProcessHeap() (GetProcessHeap())(当然,通过RtlLockHeap
  • 锁定主进程堆(由HeapLock返回)(这非常避免死锁的重要步骤)
  • 然后终止所有进程中的线程,当前线程除外(通过调用NtTerminateProcess(0, 0)
  • 然后调用LdrShutdownProcess-在此api加载程序内,按加载的模块列表移动,并发送DLL_PROCESS_DETACH为非空的lpvReserved
  • 最后调用NtTerminateProcess(NtCurrentProcess(), ExitCode ),终止该过程。

这里的问题是线程终止在任意位置。例如,线程可以在终止时从任何堆中分配或释放内存,并位于堆关键部分内。结果,如果在DLL_PROCESS_DETACH期间的代码试图从同一堆中释放一个块,则当尝试进入该堆的关键部分时(如果使用堆,当然要使用它),它将死锁。

请注意,这不会影响主进程堆,因为在终止之前终止所有线程(当前线程除外),我们为此调用了HeapLock。这样做的目的是:我们在此调用中等待,直到所有其他线程都从进程堆关键部分退出,并且在获取关键部分之后,其他线程无法进入它-因为主进程堆已锁定。

因此,当我们在锁定主堆之后终止线程时-我们可以确保没有其他被杀死的线程处于不一致状态的主堆关键部分或堆结构内。感谢RtlLockHeap的来电。但这仅与主进程堆有关。进程中的任何其他堆均未锁定。因此,这些可以在DLL_PROCESS_DETACH期间处于不一致状态,或者可以由已经终止的线程专门获取。

因此-在此处将HeapFree用于GetProcessHeap或说LocalFree是安全的(但是没有记录)。

如果在进程终止期间调用HeapFree,则对其他任何堆使用DllMain是不安全的。

另外,如果您通过多个线程使用另一个自定义数据结构-它可能处于不一致状态,因为另一个(可以使用它的)线程在任意点终止。

因此,此注释警告说,当 lpvReserved 参数为 non-NULL (在进程终止期间调用 DllMain 意味着什么)时,您需要在清理资源时要特别小心。无论如何,当进程终止时,操作系统将释放所有内部内存分配。

答案 1 :(得分:0)

作为RbMm出色答案的附录,我将在ExitProcess中添加一个引号,其作用要比DllMain文档好得多,用于解释为什么堆操作(或任何操作,实际上)可以受到损害:

  

如果进程中终止的线程之一持有锁,并且   已加载的DLL之一中的DLL分离代码尝试获取相同的代码   锁定,然后调用ExitProcess会导致死锁。相反,如果   进程通过调用TerminateProcess终止,   进程已附加到,不会通知进程终止。   因此,如果您不知道自己所有线程的状态   在此过程中,调用TerminateProcessExitProcess更好。注意   从应用程序的主要功能返回的结果是   致电ExitProcess

因此,这全都归结为:如果应用程序具有“失控”线程,这些线程可能持有 any 锁,那么(CRT)堆锁就是一个突出的例子,关闭时您会遇到很大的问题,当您需要访问“失控”线程正在使用的相同结构(例如堆)时。

这只是表明您应该以受控方式关闭所有个线程。