Как я могу отладить внутреннюю ошибку в .NET Runtime?

Я пытаюсь отлаживать некоторую работу, обрабатывающую большие файлы. Сам код работает, но есть спорадические ошибки из самого .NET Runtime. Для контекста обработка здесь представляет собой файл размером 1,5 ГБ (только один раз в памяти), который обрабатывается и освобождается в цикле, преднамеренно пытается воспроизвести эту непредсказуемую ошибку.

Мой тестовый фрагмент в основном:

try {
    byte[] data =File.ReadAllBytes(path);
    for(int i = 0 ; i < 500 ; i++)
    {
        ProcessTheData(data); // deserialize and validate

        // force collection, for tidiness
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
    }
} catch(Exception ex) {
    Console.WriteLine(ex.Message);
    // some more logging; StackTrace, recursive InnerException, etc
}

(с некоторыми моментами и другими вещами, которые были брошены)

Цикл будет обрабатываться отлично для недетерминированного числа итераций полностью успешно - никаких проблем вообще; то процесс прекратится резко. Обработчик исключений не попадает. Тест действительно требует много использования памяти, но он видел очень хорошо во время каждой итерации (нет очевидной утечки памяти, и у меня много запаса прочности - 14 ГБ неиспользуемой первичной памяти на худшем точка в пиле). Этот процесс является 64-разрядным.

Журнал ошибок Windows содержит 3 новые записи, которые (через код выхода 80131506) указывают на ошибку Engine Engine - неприятный маленький тредтер. Ответ , предлагает ошибку GC с "исправлением", чтобы отключить параллельный GC; однако это "исправление" не предотвращает проблему.

Уточнение: эта ошибка низкого уровня не попадает в событие CurrentDomain.UnhandledException.

Уточнение: GC.Collect существует только для наблюдения за памятью пильного диска, для проверки утечек памяти и сохранения предсказуемости; удаление его не устраняет проблему: он просто заставляет больше хранить память между итерациями и делает файлы dmp больше, p

Добавив больше трассировки консоли, я заметил, что это ошибка во время каждого из:

  • во время десериализации (много распределений и т.д.)
  • во время GC (между подходом GC) и GC "complete", используя API уведомления GC)
  • во время проверки (просто foreach по некоторым данным) - любопытно сразу после GC завершается во время проверки

Так много разных сценариев.

Я могу получить файлы crash-dump (dmp); как я могу исследовать это дальше, чтобы увидеть, что делает система, когда она терпит неудачу настолько эффектно?

Ответ 1

Если у вас есть дампы памяти, я бы предложил использовать WinDbg, чтобы посмотреть на них, предполагая, что вы уже этого не делаете.

Попытка запуска комментария !EEStack (смешанная трассировка с использованием собственных и управляемых стеков) и посмотреть, есть ли что-нибудь, что может выскочить в трассировке стека. В моей тестовой программе я нашел это один раз в качестве трассировки стека, где произошел FEEE (я целенаправленно искажал кучу):

0:000> !EEStack
---------------------------------------------
Thread   0
Current frame: ntdll!NtWaitForSingleObject+0xa
Child-SP         RetAddr          Caller, Callee
00000089879bd3d0 000007fc586610ea KERNELBASE!WaitForSingleObjectEx+0x92, calling ntdll!NtWaitForSingleObject
00000089879bd400 000007fc5869811c KERNELBASE!RaiseException+0x68, calling ntdll!RtlRaiseException
[...]
00000089879bec80 000007fc49109cf6 clr!WKS::gc_heap::gc1+0x96, calling clr!WKS::gc_heap::mark_phase
00000089879becd0 000007fc49109c21 clr!WKS::gc_heap::garbage_collect+0x222, calling clr!WKS::gc_heap::gc1
00000089879bed10 000007fc491092f1 clr!WKS::GCHeap::RestartEE+0xa2, calling clr!Thread::ResumeRuntime
00000089879bed60 000007fc4910998d clr!WKS::GCHeap::GarbageCollectGeneration+0xdd, calling clr!WKS::gc_heap::garbage_collect
00000089879bedb0 000007fc4910df9c clr!WKS::GCHeap::Alloc+0x31b, calling clr!WKS::GCHeap::GarbageCollectGeneration
00000089879bee00 000007fc48ff82e1 clr!JIT_NewArr1+0x481

Так как это может быть связано с повреждением кучи из сборщика мусора, я бы попробовал команду !VerifyHeap. По крайней мере, вы можете убедиться, что куча не повреждена (и ваша проблема лежит в другом месте) или обнаружите, что ваша проблема может быть связана с GC или некоторыми процедурами P/Invoke, которые ее разлагают.

Если вы обнаружите, что куча повреждена, я могу попытаться выяснить, какая часть кучи повреждена, что вы можете сделать с помощью !HeapStat. Это может просто показать всю кучу коррумпированных с определенной точки, однако.

Сложно предложить какие-либо другие методы для анализа этого через WinDbg, так как я не знаю, что делает ваш код и как он структурирован.

Я полагаю, что если вы обнаружите, что это проблема с кучей, и, следовательно, это означает, что это может быть GC-странность, я бы посмотрел на события CLR GC в трассировке событий для Windows.


Если мини-диски, которые вы получаете, не разрезают его, и вы используете Windows 7/2008R2 или более позднюю версию, вы можете использовать Global Flags (gflags.exe) для присоединения отладчика, когда процесс завершается без исключения, если вы не получаете уведомление WER.

На вкладке Silent Process Exit введите имя исполняемого файла, а не полный путь к нему (т.е. TestProgram.exe). Используйте следующие настройки:

  • Отметьте "Включить мониторинг выхода без звука"
  • Проверить процесс запуска запуска
  • Для процесса мониторинга используйте {path to debugging tools}\cdb.exe -server tcp:port=5005 -g -G -p %e.

И примените настройки.

Когда ваша тестовая программа выйдет из строя, cdb подключится и дождитесь, когда вы подключитесь к ней. Запустите WinDbg, введите Ctrl + R и используйте строку подключения: tcp:port=5005,server=localhost.

Возможно, вы сможете пропустить использование удаленной отладки и вместо этого использовать {path to debugging tools}\windbg.exe %e. Тем не менее, причина, по которой я предложил удаленный вариант, заключалась в том, что WerFault.exe, который, я считаю, является чтением реестра и запускает процесс мониторинга, запустит отладчик в сеансе 0.

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

Ответ 2

Tools->Debugging->General->Enable .Net Framework Debugging

+

Tools->IntelliTace-> IntelliTaceEbents And Call Information

+

Tools->IntelliTace-> Set StorIntelliTace Recordings in this directory

и выберите каталог

должен позволить вам вводить код INTO.net и отслеживать каждый вызов функции. Я попробовал это на небольшом примере проекта, и он работает

после каждого отладочного сеанса предполагается создать запись сеанса отладки. это заданный каталог даже если CLR умирает, если im не ошибается

это должно позволить вам перейти к подробному вызову до сглаживания CLR.

Ответ 3

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

    AppDomain currentDomain = AppDomain.CurrentDomain;
    currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyExceptionHandler);

static void MyExceptionHandler(object sender, UnhandledExceptionEventArgs e) {
        Console.WriteLine(e.ExceptionObject.ToString());
        Console.WriteLine("Press Enter to continue");
        Console.ReadLine();
        Environment.Exit(1);

Ответ 4

Я обычно вызываю проблемы с памятью с помощью Valgrind и gdb.

Если вы запускаете свои вещи в Windows, есть много хороших альтернатив, таких как verysleepy для callgrind, как предлагается здесь:
Есть ли хорошая замена Valgrind для Windows?

Если вы действительно хотите отлаживать внутренние ошибки среды выполнения .NET, у вас есть проблема, что нет источника ни для библиотек классов, ни для виртуальной машины.

Поскольку вы не можете отлаживать то, чего у вас нет, я предлагаю (помимо декомпиляции библиотек .NET Framework в вопросе с помощью ILSpy и добавления их в ваш проект, который по-прежнему не распространяется на vm), вы может использовать моно исполняемый файл.
Там у вас есть как источник библиотек классов, так и виртуальной машины.
Возможно, ваша программа отлично работает с моно, тогда ваша проблема будет решена, по крайней мере, до тех пор, пока это будет только одноразовая задача.

Если нет, существует обширный FAQ по отладке, в том числе GDB поддержка
http://www.mono-project.com/Debugging

У Мигеля также есть этот пост относительно поддержки valgrind:
http://tirania.org/blog/archive/2007/Jun-29.html

В дополнение к этому, если вы разрешите ему работать в Linux, вы также можете использовать strace, чтобы узнать, что происходит в системные вызовы. Если у вас нет обширного использования winforms или вызовов WinAPI,.NET-программы обычно отлично работают на Linux (для проблем, связанных с чувствительностью к файловой системе, вы можете объединять файловую систему без учета регистра и/или использовать MONO_IOMAP).

Если вы ориентированы на Windows, этот пост говорит, что самая близкая вещь Windows - это WinDbg Logger.exe, но информация ltrace не такая обширная.

Моно исходный код доступен здесь:
http://download.mono-project.com/sources/

Вероятно, вас интересуют источники последней моно версии
http://download.mono-project.com/sources/mono/mono-3.0.3.tar.bz2

Если вам нужна фреймворк 4.5, вам понадобится моно 3, вы можете найти прекомпилированные пакеты здесь
https://www.meebey.net/posts/mono_3.0_preview_debian_ubuntu_packages/

Если вы хотите внести изменения в исходный код, вот как его скомпилировать:
http://ubuntuforums.org/showthread.php?t=1591370

Ответ 6

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

http://www.debuginfo.com/articles/easywindbg.html

http://www.debuginfo.com/articles/easywindbg2.html

Проверьте эти информационные слайды, связанные с отладкой windbg:

http://www.slideshare.net/ShanmugaSundaram12/crash-dump-analysisshanmugasundaram

Как вы поняли из приведенных выше подробностей для правильного анализа дампов сбоя, который вы получите с помощью аварийного коммутатора adplus, наиболее важным аспектом является правильный символ или файлы pdb, поскольку они помогут в сопоставлении с текущими вызовами функций на шестнадцатеричный стек и предоставит критически важную информацию о методе, выполняемом до генерации сбоя /AV. Символы взяты из переменной среды _NT_SYMBOL_PATH. В Windbg вам не нужны инструменты командной строки, визуальный интерфейс достаточно хорош, чтобы во время ошибки отображались все сведения о трассе стека.

Насколько я понимаю, вы уже пробовали включить исключения в VS, в том числе предпочтительно включить все из них в диалоговом окне "Исключение", поскольку это всегда первая точка отладки, которая может давать критическую информацию, если это ломается с конкретным исключением, так что первая подсказка. Windbg всегда следует за ним, чтобы получить более глубокое понимание проблемы и что самый известный инструмент Windows.

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

- Создайте меньшие куски памяти и обработайте их, это обеспечит, что если возникнет ошибка из-за внезапного давления памяти и отображения большого рабочего набора, GC получит больше возможностей для сбора памяти и уменьшения общей памяти давление. если 1,5 ГБ можно разделить на 3-5 небольших кусков (500 МБ - 300 МБ). В этом случае вы можете либо сделать, читая файл по частям, заглянув внутрь или после чтения байтового потока, вы можете разделить на меньший байт [], чтобы десериализовать и агрегировать конечный результат. Я видел, как практически видел это, заботясь о многих подобных проблемах.

  • Как вы уже сказали бы, что вызовы GC не производятся, но вы, несомненно, знаете, даже если вызовы GC не гарантируют какого-либо детерминированного поведения, GC все равно вызывается сам по себе, просто чтобы он обеспечивал что выполнение ожидает, что GC выполнит свою работу после итерации программы, однако здесь, как кажется, текущие данные для обработки уже достаточно высоки, чтобы спорадически воспроизвести проблему.

В случае, если вам захочется взглянуть на шаблон использования памяти вашего процесса, потому что порой постоянно увеличивающийся рабочий набор/виртуальные байты может быть корнем проблемы, тогда у меня есть сообщение для отладки исключение из памяти:

Когда я использую Socket.IO, почему я получил сообщение об ошибке Необработанное исключение типа "System.OutOfMemoryException"

В этом случае это может быть переломным моментом непосредственно перед OOM, но в случае, если вы обнаружите, что при предварительном анализе диспетчера задач объем памяти увеличивается без остановок, тогда вам может понадобиться продолжить рассмотрение проблемы.

Кроме того, хотя я не знаю настройки системы, но вы можете играть с переключателями конфигурации загрузки в таких окнах, как /3GB,/USERVA, чтобы настроить память пользовательского процесса на более высокое значение, чего достаточно, чтобы избежать такой проблемы, хотя это потребует ручного анализа для понимания определенной точки давления памяти, когда она, несомненно, приведет к ошибке