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

时间:2010-05-12 23:15:31

标签: .net .net-4.0 .net-3.5 fatal-error

要点:

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

错误:

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

  

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

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

应用

应用程序使用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相关的项目。

5 个答案:

答案 0 :(得分:42)

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

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

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

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

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

对于史诗般的环境FEEE故事,请阅读this thread

答案 1 :(得分:5)

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

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

以下是TL; DR版本:

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

    [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一样。现在对于我的许多页面来说,这没什么大不了的。它们的记忆足迹很小,并没有那么多。但是这些页面中的许多其他页面上都有一些非常详细的图形用于说明目的。在我们的设备操作员正常使用此接口之前不会太长时间导致大量内存分配从未清除并最终堵塞了机器上的所有进程。在初期开发的匆忙从海啸消退到更多的潮汐之后,我终于决定一劳永逸地解决内存泄漏问题。我不会详细介绍我为清理内存而实现的所有技巧(WeakReference到图像,在Unload()上解除事件处理程序,使用实现IWeakEventListener的自定义计时器界面等...)。我做的关键更改是使用Uri而不是对象(NavigationService.Navigate Method (Uri))导航到页面。使用此类导航时有两个重要区别:

    默认情况下,
  1. IsKeepAlive设置为false
  2. 垃圾收集器现在将尝试清理导航对象,就像IsKeepAlive设置为false一样。
  3. 所以现在我的导航看起来像:

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

    此处需要注意的其他事项:这不仅会影响垃圾收集器清理对象的方式,这会影响它们最初在内存中分配的方式,我很快就会发现。

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

    事实证明,非托管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]分配,就是这样。 * bang! * * 崩溃! *我将分配大小更改为3,错误消失了。

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

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

答案 2 :(得分:3)

这个问题可能是一个很好的教程,可以从这个问题开始:Hardcore production debugging in .NET by Ingo Rammer

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

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

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

答案 3 :(得分:2)

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

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

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

答案 4 :(得分:0)

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