【问题标题】:Why is the handling of exceptions from CloseHandle different between .NET 4 and 3.5?为什么 .NET 4 和 3.5 对 CloseHandle 异常的处理不同?
【发布时间】:2012-03-26 06:03:01
【问题描述】:

我遇到了这样一种情况:在调试器下运行时,对 CloseHandle 的 PInvoke 调用会在 .NET 4 应用程序中抛出 SEHException。与others who have encountered similar issues migrating from 3.5 to 4 不同,我并没有特别为这种行为所困扰,并且已经找到了问题所在(第三方库在同一个句柄上两次调用CloseHandle)。但是,我很困惑为什么在 .NET 3.5 应用程序中不会发生这种行为。

以下小而完整的示例演示了我遇到的行为(在 XP SP3 和 Win 7 x64 上测试,始终编译为 x86):

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var hFileMapping = CreateFileMapping(new IntPtr(-1), IntPtr.Zero, 0x04 /* read write */, 0, 0x1000, null);
            CloseHandle(hFileMapping);
            CloseHandle(hFileMapping);
            Console.WriteLine("No exception");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }

        Console.ReadKey();
    }

    [DllImport("kernel32", SetLastError = true)]
    static extern IntPtr CreateFileMapping(IntPtr hFile, IntPtr lpAttributes, int flProtect, int dwMaximumSizeHigh, int dwMaximumSizeLow, string lpName);

    [DllImport("kernel32", SetLastError = true)]
    static extern bool CloseHandle(IntPtr handle);
}

当作为 .NET 4 应用程序运行时,SEHException 会在第二个 CloseHandle 处引发。根据documentation for CloseHandle,这是预期的行为:

如果应用程序在调试器下运行,该函数将 如果接收到的句柄值不是 有效或伪句柄值。如果您关闭句柄,可能会发生这种情况 两次,或者如果您在返回的句柄上调用 CloseHandle FindFirstFile 函数,而不是调用 FindClose 函数。

但是,当编译为 .NET 3.5 应用程序(或 CLR 2.0)时,在第二次调用 CloseHandle 时不会引发异常,并且会打印消息 "No exception"

根据this article,为 .NET 4 发布的更新后的 CLR 具有一些不同的默认行为,其中包含可能破坏进程状态的低级异常。但是,据我从那篇文章中了解到,没有提到以前的 CLR 行为会导致异常被完全忽略。

为什么 .NET 3.5(或 CLR 2.0)应用程序没有表现出 .NET 4 中存在的 CloseHandle 的记录行为?

【问题讨论】:

  • 我很高兴 MS 在这个领域做出了一些改进。我在这里为这些问题苦苦挣扎了很久:social.msdn.microsoft.com/Forums/en-US/adodotnetdataproviders/…
  • 也许在 .NET 4 之前的版本中,他们只是默默地捕捉并忽略了这些异常
  • 这正是我想知道的——为什么会这样:)
  • 实际的第三方库是否相同或者可能取决于您使用的 .NET 框架的版本?
  • 实际的第三方库是相同的,但不相关。除非您考虑 Windows API,否则示例代码不使用第三方库。

标签: c# .net winapi interop pinvoke


【解决方案1】:

Windows 仅在看到附加了调试器时才生成 SEH 异常。 本机调试器。 .NET 4 托管调试器发生了变化,现在使 Windows 可以看到这样的调试器。抱歉,我找不到任何合适的链接来记​​录这种新行为的确切细节。

编辑:我找到了一个不错的。 this blog post底部的子弹8:

在底层,我们构建在原生调试管道之上 在 v2-compat 模式下,ICD 继续拥有通往目标进程的管道(因为那是 V2 模型),但该管道不再是与目标进程共享 IPC 对象的集合,而是同一管道作为本机调试器使用。具体来说,我们通过调用 kernel32!DebugActiveProcess 附加到一个进程,并使用 kernel32!WaitForDebugEvent 获取我们的托管事件(导致调用 ICorDebugManagedCallback 的事情)。 这也意味着 kernel32!IsDebuggerPresent 现在在进行仅托管调试时返回 true。 这还有一个很好的副作用,即在启用内核调试器时避免进行仅托管调试的问题(操作系统假定在未附加调试器时发生的任何断点指令都会导致内核调试器中断)。

顺便说一句,这不仅仅是一个表面修复,尽管处理回收攻击是让微软员工夜不能寐的事情。 CLR 的 .NET 4 版本是使用检查缓冲区溢出的 CRT 版本构建的。当检测到一个时,CRT 立即终止程序。没有 AppDomain.UnhandledException,它会立即崩溃到桌面。但是,此代码与 Windows 执行相同的操作,检查本机调试器并在附加调试器时生成断点。因此,托管调试器开始看起来像原生调试器非常重要,这是真正诊断崩溃的唯一方法。

【讨论】:

  • 这也可以解释为什么当在项目属性中选择“调试本机代码”时它突然开始在 .NET 3.5 中中断(我在前几天晚上试图查看这个时发现再次)。谢谢!
  • 是的,在早期版本中打开非托管调试支持会使调试器调用 WaitForDebugEvent()。
【解决方案2】:

对此没有真正“好的”答案.. 3.5 在 SEH 中有一个在 4.0 中得到修复的错误.. 它正在吃异常。我有一个类似的问题,我们用 MSFT 开了一张票——他们的回答是“错误修复”。

【讨论】:

  • 您是否打开了连接请求?如果有,能发个链接吗?
  • @JohnSaunders - Hans Passant 还说存在错误,这意味着存在错误,链接到票证并不能证明任何事情。
  • 我们没有 Connect,只是打开了一张一次性支持票。看起来 Hans 还提供了有关修复内容的详细信息。
  • @Ramhound:这不是证明的问题,而是它会提供一个链接,指向有关该错误的更多信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-29
  • 2010-12-16
  • 1970-01-01
  • 2011-07-04
  • 1970-01-01
  • 2021-11-12
相关资源
最近更新 更多