为什么在.NET 4和3.5之间处理来自CloseHandle的异常?

jef*_*ora 18 .net c# pinvoke winapi interop

我遇到的情况CloseHandle是,SEHException在调试器下运行时,PInvoke调用将在.NET 4应用程序中抛出.与其他遇到类似问题从3.5迁移到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);
}
Run Code Online (Sandbox Code Playgroud)

当作为.NET 4应用程序运行时,SEHException会抛出第二个CloseHandle.根据文档CloseHandle,这是预期的行为:

如果应用程序在调试器下运行,则该函数在收到无效的句柄值或伪句柄值时将抛出异常.如果关闭句柄两次,或者如果在FindFirstFile函数返回的句柄上调用CloseHandle而不是调用FindClose函数,则会发生这种情况.

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

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

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

Han*_*ant 9

Windows只在看到连接了调试器时才生成SEH异常.本调试器..NET 4托管调试器发生了变化,现在让Windows看到这样的调试器.对不起,我找不到任何记录这种新行为的确切细节的体面链接.

编辑:我发现了一个体面的.本篇博客文章底部的子弹8 :

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

这不仅仅是一个美化修复btw,虽然处理回收攻击是让微软员工在夜间保持清醒的事情.CLR的.NET 4版本使用CRT版本构建,用于检查缓冲区溢出.当检测到一个时,CRT立即终止该程序.没有AppDomain.UnhandledException,它立即崩溃到桌面.但是,此代码与Windows执行的操作相同,检查本机调试器并在连接时生成断点.因此,托管调试器开始看起来像本机调试器是非常重要的,这是真正诊断崩溃的唯一方法.