Анализ ошибок приложения при помощи adplus и cdb

Метки:  , , , , , , , , ,

Честно говоря, до определенного времени как то не задавался вопросом о причинах падения приложений, какова же настоящая природа этого явления? Всегда приходилось решать подобные инциденты с глючащим программным обеспечением на уровне дилетанта: изучать записи в журнале событий, силясь найти описания идентификаторов ошибок в сети, пытаться просто "в тупую" переустановить сбойное программное обеспечение, различные сопутствующие компоненты. Время от времени случались настолько непонятные инциденты, что в итоге все заканчивалось переустановкой системы с нуля. В какой то момент времени мне это всё порядком поднадоело, и результатом изучения вопроса явились сначала короткие заметки, которые через определенное время были доработаны и доведены до уровня статьи.
В данном посте мы будем рассматривать анализ ошибок приложения, то есть диагностику внештатных ситуаций, в которых код пользовательского режима ведет себя неординарно, что приводит к тому, что приложения аварийно завершаются ("падают") либо перестают отвечать на запросы операционной системы ("зависают"). Стоит отметить, что в отличии от системного фатального сбоя (синий экран смерти, BSOD), падение некритического для системы пользовательского процесса не сказывается на стабильности работы системы в целом. На пользовательском уровне всё устроено несколько иначе, во время выполнения кода программы может возникнуть ситуация, когда состояние данных, устройств ввода-вывода, или системы в целом делает последующие вычисления в соответствии с базовым алгоритмом бессмысленными или вовсе невозможными.

Примерами таких ситуаций могут быть:

  • Доступ к области памяти, помеченной как "недоступная". В данном случае программа не получает необходимые данные и дальнейшие вычисления не имеют смысла.
  • Исчерпание памяти. Если в какой-то определенный момент времени система не сможет выделить программе область памяти необходимого размера, то программа не сможет нормально функционировать.
  • Целочисленное деление на ноль. Результата у подобной операции быть не может, поэтому дальнейшие вычисления смысла не имеют.
  • Ошибка получения данных с внешнего устройства. Если данные не получены, то дальнейшие действия над ним так же бессмысленны.

Подобные ситуации генерируют системные события, которые носят название "исключение".

Исключение - ошибка времени выполнения либо другая проблема, которая может возникнуть при выполнении программы и приводит к невозможности (бессмысленности) дальнейшей отработки программой её базового алгоритма.

Исключения могут быть двух видов:

  • Уровня операционной системы. К подобным исключениям относятся исключения, возникающие в коде, который использует структурную обработку исключений (Structured Exception Handling, SEH), то есть встроенный в операционную систему Windows механизм обработки программных и аппаратных исключений. Структурная обработка исключений - механизм, который позволяет приложениям самим получать управление в случае возникновения исключительной ситуации (примеры описаны выше) и обрабатывать их собственными силами, не привлекая операционную систему.
  • Уровня языка/фреймворка. К подобным исключениям относятся исключения, возникающие в программах, написанных на C++ либо .NET. Однако, внутри себя C++/.NET CLR все-равно используют системную структурную обработку исключений (SEH) для обеспечения поддержки специфических механизмов обработки исключений на уровне приложения.

Исключения уровня операционной системы, в свою очередь, по источнику проблемы, могут быть двух типов:

  • Аппаратные. Возникает в ответ на прерывание процессора. Относятся: Invalid memory access, Integer devide by zero и прч.
  • Программные. Возникает при явном вызове Win32 API функции RaiseException. Относятся любые исключения, которые описал разработчик ПО.

Обработкой исключений в системе занимается специализированный код ядра системы (размещенный в ntdll.dll), называемый иначе системным обработчиком исключений или кодом диспетчеризации исключений. Он предоставляет системные механизмы обработки исключений, так называемые "внутренние обработчики исключений". Если программа сама не предоставляет обработчика для какого-либо исключения, то исключение попадает на системный обработчиком исключений и, в зависимости от настроек, либо виновный процесс принудительно закрывается с целью предотвратить дальнейшие проблемы, либо производятся другие действия, например запуск отладчика для изучения и прч. В данной заметке мы будем проводить анализ ошибок приложения пользовательского режима с помощью специализированных средств adplus и cdb.
С точки зрения пользователя ошибка приложения выглядит как информационное окно с сообщением о том, что прекращена работа программы "XXXXXX". Вид окна в операционной системе Windows 7 приобрел такой вот незамысловатый вид:

Анализ ошибок приложения Excel

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

Внезапный крах приложения - необрабатываемое исключение, то есть ошибка, которая не обрабатывается самим приложением.

Одним из подобных средств анализа ошибок приложения является отладчик. Отладчик может быть подключен (приаттачен, прилинкован) к процессу, и детали сбоя могут быть зафиксированы. Фиксируемая отладчиком информация являются более подробной, нежели информация из журнала событий, то есть предоставляет значительно больше информации для размышления. Большинство пользователей представляют себе отладчик в виде полноценного графического интерфейса с огромным количеством информации о регистрах, коде, процессах, процедура, стеке и прочем, однако бывают и достаточно простые отладчики, которые работают в текстовом режиме и ориентированы на определенные специфические задачи, такие как сбор информации, логгирование событий, расстановка точек останова, запись дампа памяти.
Подобный отладчик может сформировать понятный пользователю лог-файл, который описывает поведение процесса во времени. Дополнительно, он может сформировать дамп памяти приложения для дальнейшего изучения последнего.

Дамп памяти приложения - содержимое всей рабочей памяти, занимаемой приложением (процессом).

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

Для анализа ошибок приложений Microsoft предоставляет несколько утилит в составе комплекта Debugging Tools for Windows, это:

  1. Adplus - утилита, которая позволяет продиагностировать любой "падающий" или "зависший" процесс. Adplus являет собой приложение, которое автоматизирует действия отладчика Microsoft CDB с целью установки точек останова, создания дампов памяти и лог-файлов одного или сразу множества процессов.
  2. CDB - упрощенный консольный отладчик пользовательского режима (user-mode) для слежения за процессом.

Microsoft рекомендует использовать ADPlus для анализа следующих видов процессов:

  • Процессы, которые перестали отвечать на запросы, "виснут".
  • Процессы, которые показывают 100% загрузку процессора на одноядерном процессоре, 50% ЦПУ на двухядерном процессоре, 25% ЦПУ на четырехядерном процессоре, и т.д.
  • Процессы, которые неожиданно "падают" или "закрываются".

Microsoft не рекомендует использовать ADPlus в следующих случаях:

  • Процессы, которые не запускаются, то есть закрываются прямо на этапе запуска.
  • DLL или программы, которые порождают большое количество EH-исключений Microsoft Visual C++.

Настройка среды отладки

1. Установка Debugging Tools for Windows.
Как мы уже упоминали, ADPlus содержится в составе комплекта Debugging Tools for Windows. Для установки самой свежей и актуальной для моей системы версии Debugging Tools for Windows, я использую полный комплект Windows SDK (Software Developers Kit). Подробнее о процессе установки можно почитать Установка Debugging Tools for Windows.

2. Создание рабочей директории ADplus. Именно по этому пути у нас будут складываться логи, скрипты и дампы. Создадим рабочую директорию для утилиты adplus.exe. Для этих целей я лично предпочитаю использовать каталог хранения временных файлов, заданный системной переменной %TEMP%. На моей системе он почти всегда имеет имя C:\TEMP. Исходя из этого создадим директорию c:\temp\adplus. Каждый раз при запуске ADPlus, отладочная информация будет складываться в новый уникальный подкаталог и каждый файл, создаваемый в этом подкаталоге будет иметь уникальное имя.

Отладка сбойного приложения

1. Определение имени образа или идентификатора сбойного процесса. В начале, чтобы правильно указать за каким именно процессом будет следить adplus, нам необходимо определить либо имя образа проблемного процесса либо PID. Для этих целей, на вскидку, приходит на ум два способа. Можно использовать Диспетчер задач, предварительно в меню "Вид" - "Выбрать столбцы" - включить чекбокс "ИД процесса (PID)":

Определить имя образа или идентификатор процесса

либо использовать консольную команду tasklist без опций:

и в выведенном длинном списке, в колонке "Имя образа" найти имя, либо в колонке "PID" найти идентификатор того процесса, который вы хотите отладить. В нашем случае завис Excel, поэтому имя образа будет excel.exe, а идентификатор будет равен значению 5100.

2. Линковка (присоединение) отладчика к сбойному приложению.

Все последующие команды выполняйте с привилегиями локального администратора.

Запустите интерпретатор командной строку (cmd) и войдите в директорию установки Windows Debugging Tools (по-умолчанию это C:\Program Files\Debugging Tools for Windows (x64)):

Теперь сделаем небольшое отступление с целью понять режимы работы утилиты ADPlus:

  • Режим "Завис" (Hang). Используется для диагностики зависших процессов, процессов, создающих 100% нагрузку на процессор и других проблем, которые не приводят к падению. Когда Вы используете утилиту ADPlus в режиме "hang", то должны дождаться пока процесс или группа процессов перестанут отвечать. Вот после этого Вы и должны запустить утилиту. По сути, в режиме "hang" утилита ADPlus всего-лишь создает полный дамп памяти приложения. Поэтому, в данном режиме ADPlus удобно использовать для простой операции создания дампа памяти нормально функционирующего приложения, с целью последующего изучения. На время пока память процесса скидывается в файл, процесс останавливается. После сброса дампа памяти на диск, процесс возобновляется методом неразрушающего (ненавязчивого, non-invasive) присоединения/отсоединения отладчика.
  • Режим "Упал" (Crash). Используется для диагностики исключений, которые приводят к ошибкам, по которым запускается Dr. Watson, или любым другим типам ошибок, которые приводят к немедленному закрытию программы или сервиса. Когда Вы используете утилиту ADPlus в режиме "crash", то должны запустить ADPlus до момента падения. Когда утилита ADPlus запускается в этом режиме, то отладчик cdb присоединяется к каждому приложению, описанному в командной строке запуска ADPlus, и висит на нем до момента падения исследуемого приложения, либо до вмешательства пользователя. В данном режиме обрабатываются следующие типы исключений: Invalid Handle, Illegal Instruction, Integer Divide by Zero, Floating Point Divide by Zero, Integer Overflow, Invalid Lock Sequence, Access Violation, Stack Overflow, C++ EH Exception, Unknown Exception. Режим "crash" использует метод разрушающего (навязчивого, invasive) присоединения отладчика к процессу, поэтому имеются некоторые ограничения, например процесс завершается когда отладчик отсоединяется от него.

И так, вернемся к процессу отладки. Мы переместились в рабочую директорию средств отладки. Напомню, что в нашем случае мы имеет дело в "падающим" приложением, приложение Excel аварийно завершается. Поэтому, мы будем использовать режим "crash" утилиты adplus. Для того, чтобы начать отладку падающего процесса, мы привяжемся к идентификатору сбойного приложения, выполнив команду:
adplus.exe -crash -p 5100 -o c:\temp\adplus

Если же Вы знаете имя образа сбойного процесса, то команда у нас слегка изменится:
adplus.exe -crash -pn excel.exe -o c:\temp\adplus

для того, чтобы опции командной строки были более понятными, приведем краткий синтаксис:

Параметр Описание
-crash
-hang
Режим работы отладчика
-p <ID процесса> Задает идентификатор процесса, к которому будет прилинкован отладчик.
-pn <имя образа> Задает имя образа процесса, к которому будет прилинкован отладчик.
-sc <запускаемое приложение> Задает имя запускаемого внешнего приложения и его параметров.
-y <путь к символам> Задает путь к каталогу локальных символов.
-mss <каталог локального кеша символов> Указывает каталог для локального кеша символов, получаемых с сервера символов Microsoft. Сюда скачиваются необходимые файлы символов. Обычно C:\Symbols.
-o <целевой каталог> Рабочая директория отладчика. В неё он складывает логи и дампы.
-c <файл конфигурации> Задает файл конфигурации ADPlus. Начиная с версии 6.0+ ADPlus может использовать расширенную конфигурацию во внешнем файле.
-dbg <отладчик> Задает отладчик, который будет использован: cdb, windbg, ntsd. По-умолчанию стоит cdb.
-quiet Подавляет весь графический вывод ADPlus. Говорит о том, что не стоит использовать диалоговые окна. Полезен при удаленном запуске утилиты.
-notify <имя компьютера|имя пользователя> Если запущен в режиме -crash, извещает пользователя или станцию посредством сервиса Windows Messenger о падении приложения.

Я рекомендую использовать утилиты adplus/cdb с символами отладки. Конечно же, можно запускать adplus/cdb и без символов, однако, в этом случае мы будем видеть лишь голые адреса переменных и функций без сопоставления с именами. Использование отладочных символов системы и приложений рекомендовано и самими разработчиками по причине того, что в случае наличия символов, вывод, генерируемый adplus/cdb становится более детализированным как для средств отладки Microsoft так и для самих пользователей: правильно отображаются идентификаторы переменных и функций вместо голых адресов.

Если всё прошло нормально, в виде иконки в трее, в режиме свернутого окна запускается отладчик cdb. В трее иконка cdb выглядит так:

Иконка трея отладчика cdb

В большинстве случаев нет необходимости взаимодействовать с рабочим окном cdb, поэтому рекомендую просто оставить его минимизированным. В случае же, если Вам потребуется, по каким-либо причинам, отключить отладчик от исследуемого процесса, просто разверните окно cdb и нажмите комбинацию клавиш CTRL+C.
Вот так выглядит рабочее окно cdb при попытке его развернуть:
Анализ ошибок приложения отладчик cdb

Немного об алгоритме работы консольного отладчика CDB и отладчиков в целом. Когда отладчик прилинкован к приложению, то он видит все виды возникающих исключений ДО логики самой программы.
Отладчик уведомляется о каждом исключении дважды (перед программой и после программы):

  1. Исключение первого шанса (first chance exception), не фатальное. В случае возникновения исключения в приложении впервые, прилинкованный к приложению отладчик имеет первый шанс увидеть это исключение, отсюда и название! Исключения первого шанса называют не фатальным потому, что при первом обнаружении исключения есть шанс, что оно впоследствии будет обработано самим приложением, то есть не станет причиной падения приложения. Отладчик может обрабатывать исключение первого шанс, однако в большинстве случаев отладчик сконфигурирован таким образом, чтобы не реагировать на исключение первого шанса и продолжить выполнение кода программы, в этом случае программа увидит исключение в обычном порядке.
  2. Исключение второго шанса (second chance exception), фатальное. После того, как отладчик передает исключение приложению, то приложение имеет два варианта - обработать исключение, либо не обрабатывать его. Если программа сама обрабатывает исключение собственным обработчиком исключений (exception handler), то исключение не вызывает падения приложения и не становится исключением второго шанса. Если же программа не обрабатывает исключение, то есть не имеет сконфигурированного обработчика исключения, то отладчик получает второй шанс на обработку этого же исключения. Интерактивный отладчик в этот момент всплывает своим интерфейсом, в нашем же случае с консольным отладчиком CDB ничего подобного не происходит, а просто создается запись в лог-файле и дамп приложения. Фатальным исключение второго шанса называют потому, что в случае, когда оно не обрабатывается самим приложением, системный обработчик просто закрывает приложение, то есть считается, что приложение "упало".

В обычном режиме, то есть в ситуации когда отладчик отсутствует вовсе, приложение "падает", а пользователь видит на экране ошибку приложения с заветной надписью вида "прекращена работа приложения..".
ADPlus детектирует исключения первого шанса (не фатальные) для всех типов исключений, за исключением неизвестных типов и SEH исключений? ADPlus детектирует исключение второго шанса (фатальные) для всех типов исключений, включая SEH и неизвестные исключения.

Отладчик cdb по умолчанию ничего не знает о .NET, поэтому не работает с метаданными среды .NET. По этой причине, для работы с типами и методами .NET нам необходимо использовать специализированный транслятор, расширение SOS (Son of Strike). Расширение позволяет cdb получать информацию, специфическую для .NET. Расширение располагается в директории инсталляции .NET Framework и нам необходимо всего-лишь сказать утилите Adplus о том, как их грузить. Об этом потом!

Окончание мониторинга и запись результатов

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

  • Режим "Crash". В этом режиме отладчик cdb висит запущенным до момента падения наблюдаемого приложения и выходит сразу после закрытия (падения) приложения. Создаются логи и дамп.
  • Режим "Hang". В этом режиме отладчик cdb создает логи и дамп сразу после запуска и завершает работу.

В процессе работы отладчика cdb в заданной рабочей директории (в нашем случае это c:\temp\adplus) создается новый подкаталог, который имеет уникальное имя. В этот подкаталог помещается серия носящих уникальные имена текстовых и лог-файлов.

Файл Назначение
Adplus_log_****_YYYY-MM-DD_HH-MM-SS-***.log Весь вывод, который сгенерировал отладчик cdb.
ADPlust_report.txt Параметры запуска Adplus. Если Вы захотите посмотреть, с какими параметрами запускался сеанс Adplus, сколлекционировавший логи и дампы, то вам как раз сюда.
DebuggerScript.txt Скрипт, сгенерированный утилитой Adplus и запущенный в отладчике cdb в начале процесса отладки.
FULLDUMP_****Mode_***.***__****_YYYY-MM-DD-HH-MM-SS-***_****.dmp Дамп памяти приложения.

На этом этап сбора информации у нас завершается, и мы переходим непосредственно к анализу ошибок приложения.

Анализ результатов

К моменту завершения работы adplus/cdb, у нас на руках оказываются лог-файлы отчетов и дамп(ы) памяти приложения. Анализ результатов работы слегка разнится для файлов, полученных в разных режимах работы adplus: crash и hang. По большому счету, нам было бы вполне достаточно одного лишь дампа для того, чтобы начать процесс анализа памяти приложения в момент падения/зависания, но для чего же нам тогда было использовать специальные средства, ведь для простого создания дампа мы могли бы использовать хорошо известные традиционные средства?. Правильно, в ситуации с adplus/cdb разработчики пошли чуть дальше, и в режиме "crash" предоставляют в лог-файле достаточно подробную хронологию возникающих в процессе работы приложения проблем.

Анализ результатов режима Hang

У нас на руках имеется дамп памяти приложения, при этом, в лог-файлы adplus/cdb никакой специфической информации о "подвисании" приложения не попадает, поскольку отладчик и не может ничего нам сообщить, ведь зависший процесс не производит никакой активности и не генерирует никаких исключений. Он просто "весит". Но нам и дампа памяти подвисшего процесса вполне достаточно. Теперь мы можем перейти к его анализу при помощи отладчика WinDbg.

Анализ результатов режима Crash

Здесь все значительно интереснее. Дело в том, что cdb в момент возникновения исключений первого и второго шансов записывает некоторую отладочную информацию в файл Adplus_log_****_YYYY-MM-DD_HH-MM-SS-***.log. А именно, мы имеем возможность наблюдать стек выполнения момента возникновения исключения для исключений первого и второго шанса. Вот наглядный пример стека:

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

  • Поделиться:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *