调试System.AccessViolationException

System.AccessViolationException异常通常发生在非托管代码尝试从尚未分配的内存读取或写入内存时。

制造错误

捕捉异常并不像你想象的那么容易。让我们从一个简单处理System.AccessViolationException的示例开始。以下程序引发并捕获异常:
class Program
{
    static void Main(string[] args)
    {
        try
        {
            var intPtr = new IntPtr(1);
            Marshal.WriteByte(intPtr, 1);
        }
        catch (AccessViolationException e)
        {
            Console.WriteLine(e);
        }
    }
}
本例中的WriteByte方法抛出一个新的System.AccessViolationException。捕捉异常只需要一个普通的C#批处理块。异常通常会生成以下相当一般的错误消息:
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

在大多数情况下(至少在我的经验中),通过使用DllImport调用C++代码时会引发Access违规事件。我记得在一天内创建了.NET程序,通过旧的C++ API将代码连接到电话。代码在很大程度上依赖于在非托管代码中调用方法,处理AccessViolationException是编写程序的一个必需部分。我想这就是你试图与旧的buggy代码集成得到的结果:D

这确实使用DllImport重新创建异常,我编写了下面的相当错误的C++代码:

#include "pch.h"
#include "Violator.h"

void ViolateMe()
{
    int* data1 = 0;
    *data1 = 0;
}

Visual Studio 2019甚至在生成代码时创建警告:

6011: Dereferencing NULL pointer 'data1'.

要从C#调用这个,我使用以下代码:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            ViolateMe();
        }
        catch (AccessViolationException e)
        {
            Console.WriteLine(e);
        }
    }

    [DllImport(@"C:\path\to\ViolatorLib.dll")]
    private static extern int ViolateMe();
}
在运行代码时,实际上会抛出AccessViolationException,但我们的catch块永远不会被命中。有多种方法可以确保这一点。一种是将andleProcessCorruptedStateExceptionsAttribute属性添加到方法中,如下所示
class Program
{
    [HandleProcessCorruptedStateExceptions]
    static void Main(string[] args)
    {
        ...
    }
    
    ...
}
将[HandleProcessCorruptedStateExceptions]添加到主方法中,确实会导致catch blog实际捕获异常。另一种方法是在app/web.config中将legacyCorruptedStateExceptionsPolicy设置为true:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  ...
  <runtime>
    <legacyCorruptedStateExceptionsPolicy enabled="true"/>
  </runtime>
  ...
</configuration>

微软似乎建议不要添加这种配置,但我不太同意。他们这样做的原因是,这实际上是一个严重错误,可能会导致应用程序关闭。但您总是想告诉用户发生了错误,并将异常记录到错误日志中。至于.NET Core,HandleProcessCorruptedStateExceptionsAttribute类在框架中,但似乎不会导致捕获AccessViolationException。我很想听听人们在.NET Core中对此的体验。

调试这个错误

老实说,AccessViolationException可能是调试的噩梦。在大多数情况下,错误是在您无权访问的非托管代码中产生的。让我们深入到不同的场景中,讨论如何在每种情况下威胁异常。

当您有权访问非托管代码时

让我们继续上面的例子。在这种情况下,我实际上可以访问失败的C++代码。如果使用F11进入了ValueTeMeE方法,您会注意到VisualStudio只是跳过C++代码,直接转到catch块。这是由于本机代码调试作为默认设置被禁用所致。若要解决此问题,请右键单击C#项目,单击“属性”并选择“调试”选项卡。在调试器引擎下面,请确保选中“启用本机代码调试”(或单击CTRL+t)。当项目被保存并启动应用程序时,VisualStudio现在在C++代码内部中断:

 

能够看到导致此错误的确切行和变量是一个很大的帮助。

当您无法访问非托管代码时

好家伙。你一直在读,因为你无法访问失败的代码,对吧?在这种情况下,除了与代码的维护人员联系之外,没有其他事情可做。为了帮助他们,可以生成应用程序的内存转储。为此,请通过Visual Studio运行应用程序,并等待异常发生。然后单击调试|将转储另存为。。。并命名文件。非托管代码的开发人员可以使用类似WinDbg的工具来尝试找出发生了什么。
既然我已经提到了WinDbg,你可能会想“为什么不自己检查一下WinDbg有什么问题?”好问题。这是绝对可能的。WinDbg确实帮我省了一两次。不过,使用WinDbg与典型的.NET开发人员所需的技能相去甚远。如果对它有普遍的需求,我会写一些东西。现在,您只需要知道可以使用Visual Studio创建进程内存转储。

使用WebBrowser控件时

Windows窗体和WPF都附带了一个捆绑的web浏览器组件,简单地称为web browser。我既经历过AccessViolationException,也看到很多人在这种情况下抱怨这个异常。
浏览器组件在过去是相当麻烦的。近年来,我相信大多数问题都是由线程问题引起的。C#中的Web控件通常用于生成某种URL的无头快照。我的意思是,这些天你生产了多少具有嵌入式浏览器功能的Windows窗体应用程序?
在线程中创建WebBrowser控件时,请确保在线程上设置正确的ApartmentState:

var thread = new Thread(() =>
{
   var br = new WebBrowser();
   br.Navigate(url);
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();

posted on 2020-04-01 08:46  活着的虫子  阅读(7886)  评论(5编辑  收藏  举报

导航