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

StackOverflow https://stackoverflow.com/questions/2823440

Вопрос

Краткое содержание:

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

Ошибка:

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

.NET Runtime Версия 2.0.50727.3607 - Ошибка движения Fatal выполнение (7A09795E) (80131506)

На компьютере установлена ​​ОС Windows XP Professional SP 3.(Intel Core2Quad Q6600 2,4 ГГц с 2,0 ГБ ОЗУ). Другие проекты на базе .NET, в которых отсутствует многопоточная загрузка (см. ниже), похоже, работают нормально.

Приложение:

Приложение написано на C#/.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 и убедился, что все обновления установлены.
  • В отчаянии разбил случайные предметы из кабинки.
  • Переписаны части кода, связанные с потоковой передачей и загрузкой, в попытках перехватить и зарегистрировать исключения, хотя журналирование, похоже, усугубляло проблему (и никогда не давало никаких данных).

Вопрос:

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

Это было полезно?

Решение

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

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

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

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

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

Чтобы узнать эпическую историю об экологическом FEEE, читайте эта тема.

Другие советы

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

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

Теперь версия TL;DR:

Я использую неуправляемую dll, разработанную собственными силами и написанную на C++.Моя собственная разработка графического интерфейса ведется на 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);

В графическом интерфейсе можно перемещаться по различным страницам, содержащим различную графику и вводимые пользователем данные.Предыдущий метод позволил мне получить данные для заполнения 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 (объект)), настройка по умолчанию для IsKeepAlive собственность true.Но проблема гораздо гнуснее.Даже если вы установите IsKeepAlive значение в конструкторе этой страницы специально для false, сборщик мусора все еще оставляет его в покое, как если бы он был true.Для многих моих страниц это не имело большого значения.У них был небольшой объем памяти, и происходило не так уж много всего.Но на многих других страницах были большие и очень подробные графики для иллюстративных целей.Вскоре обычное использование этого интерфейса операторами нашего оборудования привело к выделению огромных объемов памяти, которая никогда не очищалась и в конечном итоге засоряла все процессы на машине.После того, как натиск первоначальной разработки сменился цунами и превратился в приливную волну, я, наконец, решил раз и навсегда разобраться с утечками памяти.Я не буду вдаваться в подробности всех приемов, которые я реализовал для очистки памяти(Слабая ссылкаs к изображениям, отцепляя обработчики событий в 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.После нескольких дней исследований и поиска расплывчатых предложений или весьма конкретных решений, которые меня не касались, а также использования практически всех средств отладки из моего личного арсенала программирования, я, наконец, решил, что единственный способ добиться этой цели — 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.Я сделал это, потому что знал, что в этом конкретном поле со списком будет только два элемента выбора, в отличие от других полей со списком на странице, у которых у всех было три элемента выбора.Однако неуправляемый код видел вещи не так, как я.Он получил переданный мной массив и попытался записать массив size[ 3 ] в мое выделение size[ 2 ], вот и все.* хлопнуть! * * крушение! * Я изменил размер выделения на 3, и ошибка исчезла.

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

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

Презентация, которая может стать хорошим руководством по тому, с чего начать с такого рода проблем, такова: Хардкорная производственная отладка в .NET от Инго Раммера.

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

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

Кроме того, включили ли вы отладку неуправляемого кода в отладчике (опция проекта) при работе с включенной Visual Studio?А включены ли у вас управляемые помощники по отладке?

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top