.NET“致命执行引擎错误”故障排除

问题描述 投票:40回答:5

摘要:

我定期在一个我似乎无法调试的应用程序上获得.NET致命执行引擎错误。出现的对话框仅提供关闭程序或将有关错误的信息发送给Microsoft。我已经尝试查看更详细的信息,但我不知道如何使用它。

错误:

该错误在应用程序下的事件查看器中可见,如下所示:

.NET运行时版本2.0.50727.3607 - 致命执行引擎错误(7A09795E)(80131506)

运行它的计算机是Windows XP Professional SP 3.(Intel Core2Quad Q6600 2.4GHz w / 2.0 GB RAM)其他基于.NET的项目缺少多线程下载(见下文)似乎运行得很好。

应用:

该应用程序使用VS2008在C#/ .NET 3.5中编写,并通过安装项目安装。

该应用程序是多线程的,使用System.Net.HttpWebRequest及其方法从多个Web服务器下载数据。我已经确定.NET错误与线程或HttpWebRequest有关,但由于这个特殊错误似乎无法调试,因此我无法接近。

我尝试过处理多个级别的错误,包括Program.cs中的以下内容:

// handle UI thread exceptions
Application.ThreadException += Application_ThreadException;

// handle non-UI thread exceptions
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

// force all windows forms errors to go through our handler
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

更多笔记和我尝试过的...

  • 在目标计算机上安装了Visual Studio 2008并尝试在调试模式下运行,但仍然出现错误,没有提示源代码在何处发生。
  • 从其安装的版本(Release)运行程序时,错误会更频繁地发生,通常在启动应用程序的几分钟内。在VS2008内部以调试模式运行程序时,它可能会在生成错误之前运行数小时或数天。
  • 重新安装.NET 3.5并确保应用所有更新。
  • 沮丧地打破随机隔间物体。
  • 虽然日志记录似乎加剧了问题(并且从未提供任何数据),但是在尝试捕获和记录异常时处理线程和下载的代码的重写部分。

题:

我可以采取哪些步骤来排除或调试此类错误?内存转储等似乎是下一步,但我没有经验解释它们。也许在代码中我可以做更多的事情来尝试捕获错误...如果“致命执行引擎错误”提供更多信息会很好,但互联网搜索只告诉我这是很多常见错误与.NET相关的项目。

.net .net-4.0 .net-3.5 fatal-error
5个回答
42
投票

好吧,你有一个大问题。当CLR检测到垃圾收集堆完整性受到损害时,会引发该异常。堆腐败,是任何编程人员用C或C ++等非托管语言编写代码的祸根。

这些语言很容易破坏堆,只需要写入堆在堆上的数组的末尾。或者在发布后使​​用内存。或者指针的值不好。管理代码的bugz是为了解决而发明的。

但是,从您的问题判断,您正在使用托管代码。嗯,大多数情况下,您的代码是管理的但是你正在执行大量非托管代码。所有实际使HttpWebRequest工作的低级代码都是不受管理的。 CLR也是如此,它是用C ++编写的,因此在技术上也可能会破坏堆。但经过四千多次修改以及使用它的数百万个程序之后,它仍然受到堆堆积的可能性非常小。

对于想要一个HttpWebRequest的所有其他非托管代码,情况也是如此。您不了解的代码,因为您没有编写代码,而且Microsoft没有记录。你的防火墙。你的病毒扫描程序贵公司的互联网使用情况监控。 Lord知道谁的“下载加速器”。

隔离问题,假设它既不是您的代码也不是Microsoft的代码导致问题。假设它是环境优先的并且摆脱了crapware。

有关史诗般的环境FEEE故事,请阅读this thread


5
投票

由于之前的建议本质上是相当通用的,我认为通过特定的代码示例发布我自己与这个异常的争用可能是有用的,我实现的背景更改导致发生此异常,以及我如何解决它。

首先,简短版本:我使用的是用C ++(非托管)编写的内部DLL。我从.NET可执行文件中传入了一个特定大小的数组。非托管代码尝试写入未由托管代码分配的阵列位置。繁荣。

以下是TL; DR版本:

我正在使用一个用C ++编写的内部开发的非托管DLL。我自己的GUI开发是在C#.Net 4.0中。我正在调用各种非托管方法。该DLL有效地充当了我的数据源。来自dll的extern定义示例:

    [DllImport(@"C:\Program Files\MyCompany\dataSource.dll",
        EntryPoint = "get_sel_list",
        CallingConvention = CallingConvention.Winapi)]
    private static extern int ExternGetSelectionList(
        uint parameterNumber,
        uint[] list,
        uint[] limits,
        ref int size);

然后我将方法包装在我自己的界面中,以便在整个项目中使用:

    /// <summary>
    /// Get the data for a ComboBox (Drop down selection).
    /// </summary>
    /// <param name="parameterNumber"> The parameter number</param>
    /// <param name="messageList"> Message number </param>
    /// <param name="valueLimits"> The limits </param>
    /// <param name="size"> The maximum size of the memory buffer to 
    /// allocate for the data </param>
    /// <returns> 0 - If successful, something else otherwise. </returns>
    public int GetSelectionList(uint parameterNumber, 
           ref uint[] messageList, 
           ref uint[] valueLimits, 
           int size)
    {
        int returnValue = -1;
        returnValue = ExternGetSelectionList(parameterNumber,
                                         messageList, 
                                         valueLimits, 
                                         ref size);
        return returnValue;
    }

此方法的示例调用:

            uint[] messageList = new uint[3];
            uint[] valueLimits = new uint[3];
            int dataReferenceParameter = 1;

            // BUFFERSIZE = 255.
            MainNavigationWindow.MainNavigationProperty.DataSourceWrapper.GetSelectionList(
                          dataReferenceParameter, 
                          ref messageList, 
                          ref valueLimits, 
                          BUFFERSIZE);

在GUI中,可以浏览包含各种图形和用户输入的不同页面。之前的方法允许我获取填充ComboBoxes的数据。我在此异常之前的导航设置和调用示例:

在我的主机窗口中,我设置了一个属性:

    /// <summary>
    /// Gets or sets the User interface page
    /// </summary>
    internal UserInterfacePage UserInterfacePageProperty
    {
        get
        {
            if (this.userInterfacePage == null)
            {
                this.userInterfacePage = new UserInterfacePage();
            }

            return this.userInterfacePage;
        }

        set { this.userInterfacePage = value; }
    }

然后,在需要时,我导航到页面:

MainNavigationWindow.MainNavigationProperty.Navigate(
        MainNavigation.MainNavigationProperty.UserInterfacePageProperty);

一切都运作良好,虽然我确实有一些严重的爬行问题。使用对象(NavigationService.Navigate Method (Object))导航时,IsKeepAlive属性的默认设置是true。但问题比那更加邪恶。即使你将该页面的构造函数中的IsKeepAlive值专门设置为false,它仍然被垃圾收集器单独留下,就好像它是true一样。现在对于我的许多页面来说,这没什么大不了的。它们的记忆足迹很小,并没有那么多。但是这些页面中的许多其他页面上都有一些非常详细的图形用于说明目的。在我们的设备操作员正常使用此接口之前不久,导致大量内存分配从未清除并最终堵塞了机器上的所有进程。在初期开发的匆忙从海啸消退到更多的潮汐之后,我终于决定一劳永逸地解决内存泄漏问题。我不会详细介绍我为清理内存而实现的所有技巧(WeakReferences到图像,在Unload()上解除事件处理程序,使用实现IWeakEventListener接口的自定义计时器等)。我做的关键改变是使用Uri而不是对象(NavigationService.Navigate Method (Uri))导航到页面。使用此类导航时有两个重要区别:

  1. IsKeepAlive默认设置为false
  2. 垃圾收集器现在将尝试清理导航对象,就像IsKeepAlive设置为false一样。

所以现在我的导航看起来像:

MainNavigation.MainNavigationProperty.Navigate(
    new Uri("/Pages/UserInterfacePage.xaml", UriKind.Relative));

还有一点要注意:这不仅会影响垃圾收集器清理对象的方式,这会影响它们最初在内存中的分配方式,我很快就会发现。

一切似乎都很好。当我浏览图形密集页面时,我的记忆很快就会被清理到接近我的初始状态,直到我点击这个特定的页面,然后调用dataSource dll来填充一些组合框。然后我得到了这个令人讨厌的FatalEngineExecutionError。经过几天的研究,找到了模糊的建议,或者不适用于我的高度具体的解决方案,以及在我的个人编程工具中释放了几乎所有的调试武器,我终于决定了我真的要解决这个问题的唯一方法down是重建这个特定页面的精确副本的极端措施,逐个元素,逐个方法,逐行,直到我最终遇到抛出此异常的代码。这就像我暗示的那样乏味和痛苦,但我终于追查了它。

事实证明,非托管dll正在分配内存以将数据写入我发送的数组以进行填充。该特定方法实际上会查看参数编号,并根据该信息,根据预期写入我发送的数组的数据量分配特定大小的数组。崩溃的代码:

            uint[] messageList = new uint[2];
            uint[] valueLimits = new uint[2];
            int dataReferenceParameter = 1;

            // BUFFERSIZE = 255.
            MainNavigationWindow.MainNavigationProperty.DataSourceWrapper.GetSelectionList(
                           dataReferenceParameter, 
                           ref messageList, 
                           ref valueLimits, 
                           BUFFERSIZE);

此代码可能看起来与上面的示例相同,但它有一个很小的区别。我分配的数组大小是2而不是3.我这样做是因为我知道这个特定的ComboBox只有两个选择项,而页面上的其他ComboBox都有三个选择项。但是,非托管代码并没有像我看到的那样看待事物。它得到了我交给的数组,并试图将size [3]数组写入我的大小[2]分配,就是这样。砰! * *崩溃! *我将分配大小更改为3,错误消失了。

现在这个特定的代码已经运行了一年没有这个错误。但是通过Uri而不是Object导航到此页面的简单行为导致崩溃出现。这意味着由于我使用的导航方法,必须以不同方式分配初始对象。由于我的旧导航方法,内存只是堆积到位,并留下我认为适合永恒,如果它在一两个小位置有点腐败似乎并不重要。一旦垃圾收集器必须实际对该内存执行某些操作(例如清理它),它就会检测到内存损坏并引发异常。具有讽刺意味的是,我的主要内存泄漏掩盖了致命的内存错误!

显然,我们将审查此接口,以避免在将来导致此类崩溃的此类简单假设。希望这有助于引导其他人了解他们自己的代码中发生了什么。


3
投票

关于从何处开始解决这类问题可能是一个很好的教程的演示文稿是:Hardcore production debugging in .NET by Ingo Rammer

我做了一些C ++ / CLI编码,堆损坏通常不会导致此错误;通常堆损坏会导致数据损坏和后续正常异常或内存保护错误 - 这可能并不意味着什么。

除了尝试.net 4.0(以不同方式加载非托管代码)之外,您应该比较CLR的x86和x64版本 - 如果可能的话 - x64版本具有更大的地址空间,因此完全不同的malloc(+碎片)行为,所以你只需要可能会很幸运,并且有一个不同的(更可调试的)错误(如果它发生的话)。

此外,当您使用visual studio运行时,您是否在调试器(项目选项)中打开了非托管代码调试?你有托管调试助手吗?


2
投票

在我的例子中,我使用AppDomain.CurrentDomain.FirstChanceException安装了一个异常处理程序。这个处理程序记录了一些例外,几年都很好(实际上这个调试代码不应该停留在生产中)。

但是在配置错误之后,记录器开始失败,并且处理程序本身正在抛出,这显然导致FatalExecutionEngineError似乎无处可去。

因此,任何遇到此错误的人都可能花费几秒钟在代码中的任何地方搜索FirstChanceException的出现,并且可能会节省几个小时的头部抓挠:)


0
投票

如果你使用thread.sleep()可能是原因。非托管代码只能来自kernell.32 sleep()函数。

© www.soinside.com 2019 - 2024. All rights reserved.