Устранение неполадок .NET "Неустранимая ошибка механизма выполнения"

Резюме:

Я периодически получаю ошибку .NET Fatal Execution Engine в приложении, которое я не могу отлаживать. В появившемся диалоговом окне предлагается закрыть программу или отправить информацию об ошибке Microsoft. Я попытался просмотреть более подробную информацию, но я не знаю, как ее использовать.

Ошибка:

Ошибка видна в средстве просмотра событий в разделе "Приложения" и выглядит следующим образом:

.NET Runtime версии 2.0.50727.3607 - Ошибка машинного сбоя (7A09795E) (80131506)

Компьютер под управлением Windows XP Professional SP 3. (Intel Core2Quad Q6600 2.4 ГГц с 2.0 ГБ ОЗУ). Другие .NET-проекты, которые не имеют многопоточной загрузки (см. ниже), выглядят просто отлично.

Применение:

Приложение написано на С#/.NET 3.5 с использованием VS2008 и установлено через проект установки.

Приложение является многопоточным и загружает данные с нескольких веб-серверов с помощью System.Net.HttpWebRequest и его методов. Я решил, что ошибка .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 и удостоверился, что все обновления применяются.
  • Сбой случайных объектов ячейки в расстройстве.
  • Переписанные части кода, которые занимаются потоковой загрузкой и загрузкой при попытках ловить и записывать исключения, хотя регистрация, похоже, усугубляет проблему (и никогда не предоставляла никаких данных).

Вопрос:

Какие шаги можно предпринять для устранения неполадок или отладки этой ошибки? Дампы памяти и т.д., Похоже, являются следующим шагом, но я не имею опыта их интерпретации. Возможно, есть что-то еще, что я могу сделать в коде, чтобы попытаться поймать ошибки... Было бы неплохо, если бы "Fatal Execution Engine Error" была более информативной, но поиск в Интернете только сказал мне, что это общая ошибка для многих Элементы, связанные с .NET.

Ответ 1

Ну, у тебя большая проблема. Это исключение выражается CLR, когда оно обнаруживает, что целостность собранной мусора собрана скомпрометирована. Кучевое повреждение, проклятие любого программиста, который когда-либо писал код на неуправляемом языке, таком как C или С++.

Эти языки очень легко коррумпируют кучу, все, что требуется, - это написать за конец массива, выделенного в куче. Или используя память после ее освобождения. Или имеет плохое значение для указателя. Тип bugz, который управлял кодом, был изобретен для решения.

Но вы используете управляемый код, судя по вашему вопросу. Ну, в основном, ваш код управляется. Но вы выполняете много неуправляемого кода. Весь низкоуровневый код, который фактически делает работу HttpWebRequest, неуправляемым. Так же как и CLR, он был написан на С++, так что технически точно так же может испортить кучу. Но после более чем четырех тысяч его пересмотров и миллионов программ, использующих его, шансы, что он все еще страдает от кучевых кучей, очень малы.

То же самое не относится ко всем другим неуправляемым кодам, которые хотят часть HttpWebRequest. Код, о котором вы не знаете, потому что вы его не пишете и не документируете Microsoft. Ваш брандмауэр. Ваш антивирус. Монитор использования вашей компании в Интернете. Лорд знает, чей "ускоритель загрузки".

Изолируйте проблему, предположите, что это не ваш код, а код Microsoft, который вызывает проблему. Предположим, что это прежде всего экология и избавиться от crapware.

Для эпической экологической истории FEEE прочитайте этот поток.

Ответ 2

Поскольку предыдущие предложения носили довольно общий характер, я подумал, что было бы полезно опубликовать мою собственную битву против этого исключения с конкретными примерами кода, изменениями фона, которые я реализовал, чтобы вызвать это исключение, и тем, как я его решил.

Сначала короткая версия: я использовал внутреннюю библиотеку DLL, написанную на C++ (неуправляемую). Я передал массив определенного размера из моего исполняемого файла .NET. Неуправляемый код пытался записать в расположение массива, которое не было выделено управляемым кодом. Это вызвало повреждение памяти, которое позже было установлено как сборщик мусора. Когда сборщик мусора готовится собирать память, он сначала проверяет состояние памяти (и границы). Когда он обнаружит коррупцию, БУМ.

Ниже приведена версия TL; DR:

Я использую неуправляемую DLL, разработанную собственными силами, написанную в C++. Моя собственная разработка GUI находится на С#.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);

В графическом интерфейсе можно перемещаться по разным страницам, содержащим различные графические элементы и пользовательский ввод. Предыдущий метод позволил мне получить данные для заполнения 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(Object)) значением по умолчанию для свойства IsKeepAlive является значение true. Но проблема более гнусна, чем эта. Даже если вы установите значение IsKeepAlive в конструкторе этой страницы специально на false, сборщик мусора все равно останется один, как если бы он был true. Теперь для многих моих страниц это было не страшно. У них были небольшие следы памяти, и не так много всего происходило. Но многие другие из этих страниц имели большие подробные графические изображения для иллюстрации. Вскоре обычное использование этого интерфейса операторами нашего оборудования вызвало огромные выделения памяти, которые никогда не очищались и в конечном итоге забивали все процессы на машине. После того, как стремительное развитие началось с цунами, а затем и приливов, я, наконец, решил раз и навсегда решить проблему утечек памяти. Я не буду вдаваться в подробности всех приемов, которые я реализовал для очистки памяти (слабая ссылка на изображения, отсоединение обработчиков событий в Unload(), использование настраиваемого таймера, реализующего интерфейс IWeakEventListener, и т.д.). Ключевое изменение, которое я сделал, состояло в том, чтобы перейти к страницам, используя Uri вместо объекта (метод NavigationService.Navigate(Uri)). Есть два важных различия при использовании этого типа навигации:

  1. IsKeepAlive по умолчанию имеет значение false.
  2. Сборщик мусора теперь будет пытаться очистить объект навигации, как если бы для IsKeepAlive было установлено значение false.

Теперь моя навигация выглядит так:

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

Здесь следует еще кое-что отметить: это влияет не только на то, как объекты очищаются сборщиком мусора, но и на то, как они изначально распределяются в памяти, как я скоро выясню.

Казалось, все работало отлично. Моя память быстро очищалась до моего начального состояния, когда я перемещался по страницам, интенсивно использующим графику, пока я не попал на эту конкретную страницу с конкретным вызовом dll dataSource, чтобы заполнить некоторые поля со списком. Затем я получил эту неприятную 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], и на этом все. * взрыв! * * крушение! * Я изменил размер выделения на 3, и ошибка исчезла.

Теперь этот конкретный код уже работал без этой ошибки, по крайней мере, год. Но простой переход на эту страницу через Uri в отличие от Object привел к появлению сбоя. Это подразумевает, что исходный объект должен быть размещен по-другому из-за метода навигации, который я использовал. Поскольку с моим старым методом навигации память просто складывалась на место и оставлялась, как мне показалось, вечной, казалось, не имеет значения, была ли она немного повреждена в одном или двух небольших местах. Как только сборщик мусора должен был что-то сделать с этой памятью (например, очистить ее), он обнаружил повреждение памяти и выдал исключение. По иронии судьбы, моей главной утечкой памяти было сокрытие фатальной ошибки памяти!

Очевидно, что мы собираемся пересмотреть этот интерфейс, чтобы избежать таких простых предположений, вызывающих такие сбои в будущем. Надеюсь, что это поможет некоторым другим узнать, что происходит в их собственном коде.

Ответ 3

Презентация, которая может быть хорошим учебным пособием о том, с чего начать эту проблему, такова: Отладка Hardcore в .NET от Ingo Rammer.

Я немного разбираюсь в коде С++/CLI, и повреждение кучи обычно не приводит к этой ошибке; обычно повреждение кучи приводит к повреждению данных и последующему нормальному исключению или ошибке защиты памяти, что, вероятно, ничего не значит.

В дополнение к try.net 4.0 (который загружает неуправляемый код по-разному) вы должны сравнивать версии x86 и x64 CLR - если возможно - версия x64 имеет большее адресное пространство и, таким образом, совершенно другое поведение malloc (+ фрагментация) и так что вам просто повезет, и там будет другая (более отлаживаемая) ошибка (если это вообще произойдет).

Также вы включили неуправляемую отладку кода в отладчике (опция проекта), когда вы запускаете визуальную студию? И у вас есть управляемые помощники по отладке?

Ответ 4

В моем случае я установил обработчик исключений с AppDomain.CurrentDomain.FirstChanceException. Этот обработчик регистрировал некоторые исключения, и все было в порядке в течение нескольких лет (на самом деле этот код отладки не должен оставаться в производстве).

Но после ошибки конфигурации логгер начал сбой, и сам обработчик метался, что, по-видимому, привело к появлению FatalExecutionEngineError, казалось бы, из ниоткуда.

Таким образом, любой, кто сталкивается с этой ошибкой, может потратить несколько секунд на поиск вхождений FirstChanceException в любом месте кода и, возможно, сохранить несколько часов царапин на голове:)

Ответ 5

Если вы используете thread.sleep(), это может быть причиной. Неуправляемый код можно спать только из функции kernell.32 sleep().