43

概括:

我在似乎无法调试的应用程序上定期收到 .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 并尝试在调试模式下运行,但错误仍然出现,没有提示它发生在源代码中的位置。
  • 从已安装版本(发行版)运行程序时,错误发生的频率更高,通常在启动应用程序的几分钟内。在 VS2008 中以调试模式运行程序时,它可能会运行数小时或数天,然后才会产生错误。
  • 重新安装 .NET 3.5 并确保应用所有更新。
  • 沮丧地打破了随机的隔间物体。
  • 重写了处理线程和下载的部分代码,以尝试捕获和记录异常,尽管记录似乎加剧了问题(并且从未提供任何数据)。

问题:

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

4

5 回答 5

45

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

这些语言使得破坏堆变得非常容易,所需要的只是写到堆上分配的数组的末尾。或者在释放后使用内存。或者指针的值不正确。发明了托管代码来解决的那种错误。

但是从您的问题来看,您正在使用托管代码。好吧,大多数情况下,您的代码是受管理的。但是您正在执行大量非托管代码。实际上使 HttpWebRequest 工作的所有低级代码都是非托管的。CLR 也是如此,它是用 C++ 编写的,所以从技术上讲,它同样可能会破坏堆。但是经过四千多次修改,以及数百万个程序使用它,它仍然遭受堆傻瓜的可能性非常小。

对于所有其他需要 HttpWebRequest 的非托管代码来说,情况并非如此。您不知道的代码,因为您没有编写它并且 Microsoft 没有记录它。你的防火墙。您的病毒扫描程序。贵公司的 Internet 使用监视器。天知道是谁的“下载加速器”。

隔离问题,假设问题既不是您的代码也不是 Microsoft 的代码。假设它首先是环境并摆脱垃圾软件。

有关史诗般的环境费故事,请阅读此线程

于 2010-06-20T23:17:07.847 回答
9

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

首先,TL;DR 版本:我使用的是用 C++(非托管)编写的内部 dll。我从我的 .NET 可执行文件中传入了一个特定大小的数组。非托管代码尝试写入未由托管代码分配的数组位置。这导致内存损坏,后来被设置为垃圾收集。当垃圾收集器准备收集内存时,它首先检查内存的状态(和边界)。当它发现腐败时,BOOM

现在是详细版本

我正在使用内部开发的非托管 dll,用 C++ 编写。我自己的 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将该页面的构造函数中的值专门设置falsetrue. 现在对于我的许多页面来说,这没什么大不了的。他们的内存占用很小,没有那么多事情发生。但这些页面中的许多其他页面上都有一些非常详细的大型图形,用于说明目的。没过多久,我们设备的操作员正常使用这个接口导致了巨大的内存分配,这些内存从未被清除并最终阻塞了机器上的所有进程。在最初的开发热潮从海啸平息到更多的潮汐之后,我终于决定一劳永逸地解决内存泄漏问题。我不会详细介绍我为清理内存而实施的所有技巧(WeakReference到图像,在 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 只有两个选择项,而页面上的其他 ComboBoxes 都具有三个选择项。然而,非托管代码并没有像我看到的那样看待事物。它得到了我提交的数组,并试图将一个大小为3的数组写入我的大小为2的分配中,就是这样。*砰!* *崩溃!* 我将分配大小更改为 3,错误消失了。

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

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

于 2014-08-27T09:52:06.147 回答
3

一个演示文稿可能是一个很好的教程,说明从哪里开始处理这类问题:Ingo Rammer 在 .NET 中进行硬核生产调试

我做了一些 C++/CLI 编码,堆损坏通常不会导致这个错误;通常堆损坏会导致数据损坏和随后的正常异常或内存保护错误 - 这可能没有任何意义。

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

另外,当您在 Visual Studio 上运行时,您是否在调试器中打开了非托管代码调试(一个项目选项)?你有托管调试助手吗?

于 2010-06-23T06:46:24.250 回答
2

就我而言,我已经安装了一个异常处理程序AppDomain.CurrentDomain.FirstChanceException。这个处理程序记录了一些异常,几年来一切都很好(实际上这个调试代码不应该留在生产中)。

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

因此,遇到此错误的任何人都可以花几秒钟的时间搜索FirstChanceException代码中任何地方的出现,并可能节省几个小时的头疼:)

于 2017-09-18T14:36:57.063 回答
-1

如果您正在使用 thread.sleep() 这可能是原因。非托管代码只能从 kernell.32 sleep() 函数中休眠。

于 2013-01-20T19:54:06.943 回答