WinDbg - анализ аварийного дампа памяти

Метки:  , , , , ,

Публикация продолжает цикл статей по обзору инструментария, предназначенного для исследования аварийных дампов памяти системы и приложений, и в данной статье, в качестве инструмента изучения мы будем рассматривать отладчик широкого применения под названием WinDbg. Для начала, не лишним будет обмолвиться о том, что отладчик WinDbg является, пожалуй, самым функциональным многоцелевым отладчиком для операционных систем Microsoft Windows. В значительной степени это заслуга того, что WinDbg самый "древний" отладчик и разрабатывается уже достаточно давно, ориентировочно со времен появления первых версий системы Windows NT. WinDbg по праву занимает достойное место в арсенале специалистов по исследованию кода, поскольку ни один другой отладчик, за всю историю существования операционных систем Windows, не может похвастаться столь продолжительным жизненным циклом. Конечно же, во многом это обусловлено и тем фактом, что разрабатывает WinDbg сама корпорация Microsoft, то есть WinDbg является "родным", нативным отладчиком для Windows. WinDbg (Windows DeBuGger) - мощный многофункциональный отладчик, который позволяет отлаживать приложения и драйвера пользовательского режима (user mode) а так же непосредственно модули/драйвера режима ядра (kernel mode).

Отладчик - программа, разработанная для помощи в обнаружении ошибок в программном обеспечении.

WinDbg объединяет в себе широкий спектр функционала, огромное количество внутренних и внешних команд, макросов и расширений, количество которых достаточно велико, что позволяет существенно расширить функционал отладчика. К основным функциям отладчика относятся пошаговое выполнение исследуемого кода, установка точек останова, просмотр переменных, трассировка стека вызова, просмотр областей памяти, исследование аварийных дампов системы и многое другое. Отладчик позволяет отлаживать как современный 64-битный код, так и уходящий уже в прошлое 32-битный. Что касается именно дампов и разрядности, то Вы можете отлаживать 64-разрядный дамп на 32-разрядной системе и наоборот. К недостаткам WinDbg можно отнести не удобный для большинства интерфейс, требующий дополнительной настройки, некоторые проблемы с отладкой в исходных текстах и знаменитую глючность. :) Основным предназначением отладчика WinDbg, как Вы уже догадались, является интерактивная отладка целевого кода.

Темой данной статьи является использование отладчика WinDbg для быстрого анализа аварийных дампов памяти системы и пользовательских процессов посредством серии команд (!analyze -v и прочие), без углубленной отладки дампа системы с учетом параметров и специфики конкретной STOP-ошибки.

Подготовка

К глубокому сожалению, отладчик WinDbg не входит в состав дистрибутива операционной системы и, с определенного времени, не доступен в качестве отдельного продукта, а поставляется исключительно в составе пакета отладки Debugging Tools for Windows. Соответственно, для начала нам потребуется установить Debugging Tools for Windows. После окончания процедуры установки, которая детально описана в статье, мы готовы будем продолжить.

Первый запуск WinDbg

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

Стартовое окно WinDbg

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

Настройка символов в WinDbg

Отладочные символы — информация, позволяющая использовать "символические" (понятные человеку) данные о бинарном файле, такие как имена процедур, функций и переменных, используемых автором в исходном коде.

Я бы настоятельно рекомендовал настраивать отладчик на работу с отладочными символами, особенно в ситуации с 32-битным кодом. В сети я встречал мнение, что WinDbg не нужны отладочные символы для вычисления сбойного драйвера/модуля, в контексте которого произошел сбой, но применительно к 32-битному коду часто наблюдал ситуацию, когда разбор стека отладчиком заканчивается совершенно парадоксальными результатами, совершенно далекими от истины. Особенно часто подобное проявляется в ситуации, когда отсутствуют символы для стороннего драйвера/модуля, находящегося в стеке момента сбоя и разбор стека отладчиком в этом случае затруднен. К тому же, при настроенных символах мы получаем дополнительную информацию по сбою и можем более детально проанализировать, к примеру, стек вызовов момента сбоя на уровне процедур и функций.
Настроить отладчик на работу с отладочным символам можно несколькими способами. Первый, самый простой и традиционный, заключается в указании составного пути к символам в настройках отладчика WinDbg: srv*c:\symbols*http://msdl.microsoft.com/download/symbols. Для настройки отладочных символов выберем в меню пункт File - Symbol File Path ... и просто вставим указанное выше значение в поле ввода, затем нажмем клавишу ОК.

прописать символы в winDbg

Обратите внимание, что обозначение пути должно идти без пробелов и переносов строк.

Теперь при запуске процесса отладки, отладчик WinDbg сначала произведет поиск символов для модулей, участвующих в процессе отладки, в локальной папке c:\symbols, если там символов не будет обнаружено, то WinDbg попытается загрузить их из сети Интернет с сайта http://msdl.microsoft.com/download/symbols и закешировать (сохранить) в ту же папку c:\symbols. Отладчик WinDbg загружает символы только по мере необходимости и только к модулям, которые обнаруживаются в процессе анализа дампа. Причем директорию c:\symbols руками создавать не требуется, поскольку отладчик создаст её сам на стадии кеширования символов (в случае наличия у него соответствующих разрешений, конечно же).

Для того, чтобы не настраивать путь поиска символов каждый раз после запуска WinDbg, необходимо сохранить рабочее окружение отладчика. Для этого зайдите в меню "File" и выберите пункт "Save Workspace".

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

Анализ аварийного дампа системы

Первый сценарий, рассматриваемый нами в статье, подразумевает изучение системного (при останове системы) аварийного дампа памяти. Перво-наперво нам необходимо выполнить загрузку файла дампа в отладчик, для этого выбираем пункт меню File - Open Crash Dump..., после чего откроется окно выбора файла, в нем мы указываем местоположение аварийного дампа, выбираем сам файл и нажимаем кнопку ОК. Следствием данного действия является изменение интерфейса отладчика Windbg: открывается дочернее информационное окно вывода, которое предназначается для отображения логики работы отладчика и в нижней части окна появится командная строка.

winDbg командное окно

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

В нижней части окна Command командная строка с подсказкой kd> станет активной. Этот факт сигнализирует нам о том, что возможен ввод пользовательских команд. На скриншоте выше я выделил красным цветом важную для нас строку Probably caused by:, которая в переводе на русский язык означает "Вероятно вызван:", и которой отладчик сообщает нам о предполагаемом виновнике сбоя. Тем не менее, результат предварительного анализа можно получить не во всех сценариях, в вашем случае он может и отсутствовать. Почему имеет место быть такая расплывчатая формулировка "вероятно.."? Меня тоже всегда это удивляло. Вероятно, так уж устроена архитектура x86, что не возможно сделать точное заключение о причине сбоя, поскольку источником может быть аппаратная часть, что не всегда очевидно из анализа структур дампа, но это лишь моё предположение. К тому же, отладчик WinDbg проводит быстрый анализ дампа, выводя имя источника проблемы просто на основе исследования соответствующих структур внутри дампа, что, предположительно, тоже может не являться достаточным условием для точного предположения о виновнике сбоя. В нашем случае отладчик посчитал, что предполагаемым виновником является драйвер режима ядра portcls.sys. И все, виновник найден? Но что-то по самому сбою на экране отладки у нас довольно мало информации, не думаю, что дотошному исследователю её будет достаточно. Как Вы могли уже заметить по приведенному выше выводу информационного окна, в нижней части его присутствует строка Use !analyze -v to get detailed debugging information, которая явно указывает нам на то, что есть возможность произвести более глубокий анализ проблемы и для этого можно выполнить команду !analyze -v (при этом на странице присутствует активная, кликабельная ссылка). Сейчас мы еще раз, но более детально, проанализируем аварийный дамп, для чего можно либо щелкнуть по ссылке, либо ввести в командной строке:

!analyze –v

Это команда расширения WinDbg, которая выполняет ряд программных скриптов, предназначенных для анализа отдельных аспектов содержащейся в дампе информации. Ко всему прочему, присутствует опция -v, которая задает режим подробного вывода. В результате, в информационном окне можно увидеть намного больше информации, нежели в предоставленном нам ранее стандартном анализе.
На всякий случай приведу скриншот расширенного анализа:

Результат команды !analyze в WinDbg

Ну и теперь посмотрим весь текстовый вывод анализатора при расширенном анализе:

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

  • Символическое имя STOP-ошибки (BugCheck), в нашем случае это BAD_POOL_HEADER;
  • Краткое описание сбоя, которое помогает исследователю выстроить алгоритм для самостоятельного анализа в зависимости от специфики конкретной ошибки;
  • Четыре входных аргумента (параметра) функции KeBugCheckEx: Arg1, Arg2, Arg3, Arg4. Именно значения этих параметров позволяют посредством анализа соответствующих структур углубляться в проблему;
  • Стек вызовов. Помогает увидеть цепочку вызовов потока, что позволяет анализировать все стадии выполнения;

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

Каждая запись стека вызовов (STACK_TEXT) формируется по стандартному шаблону вида имя_модуля!имя_функции+смещение, где имя_модуля - имя исполняемого файла, код которого содержит функцию, имя_функции - наименование функции, смещение - количество байт (в шестнадцатеричной системе) от точки входа функции до инструкции, следующей за инструкцией, вызвавшей следующую функцию, одним словом - точка возврата из вышестоящей функции. В ряде ситуаций имя функции может быть недоступно, тогда адрес отображается в формате имя_модуля+смещение.

Многие начинающие исследователи (к каковым отношусь и я :) безоговорочно доверяют выводу трассировки стека, полученному при помощи команды !analyze, при этом совершенно упуская из виду тот факт, что WinDbg является всего-лишь инструментом, опирающимся в своих процедурах анализа на данные из файла дампа. Иногда информация, необходимая для корректной трассировки стека попросту отсутствует, и это может привести к неправильной трассировке стека и, соответственно, выводу в отчет некорректной информации. Иногда (но не всегда) признаком неправильного разбора стека могут являться сообщения "Following frames may be wrong", а так же иные признаки, такие как присутствие в стеке функций, абсолютно не относящихся к проблеме, неправильные адреса и прочее. В нашем случае нам повезло и виновником действительно оказался драйвер, на который указывал отладчик, однако будьте внимательны, поскольку в запутанном деле анализа дампов памяти системы часто имеются нюансы.
Для многих далеко не все поля, которые можно видеть в информационном окне, окажутся понятными. Поэтому, попробую привести краткую собственную интерпретацию значений:

BUGCHECK_STR BugCheck код или, по-другому, код STOP-ошибки в сокращенном формате. Собственный классификатор Microsoft для описания сбоев. Поле описывает код возникшего исключения в нескольких возможных форматах: 0xXXXXXXXX, 0xXX либо DD.DD.DDDD XXXXX. Чаще можно встретить сокращенную форму записи, в нашем случае это 0x19_20. Первая половина четко указывает нам на STOP 0x00000019, а вот что означает вторая?
POOL_ADDRESS Адрес блока памяти, в котором находится страница, содержащая сбойную инструкцию.
CUSTOMER_CRASH_COUNT Поле отображается только в минидампах и показывает, сколько раз система упала с идентичной ошибкой после первого аналогичного случая. Вместо того, чтобы каждый раз создавать идентичный дамп, просто увеличивается данный счетчик.
DEFAULT_BUCKET_ID Поле отражает основную категорию сбоев, к которой принадлежит текущий сбой. В данном случае значение DRIVER_FAULT говорит о сбое в драйвере. Вероятно, где-то имеется каталог возможных категорий.
PROCESS_NAME Поле указывает на имя процесса, в рамках которого возник сбой. Другими словами это процесс, который выполнялся процессором (было выделено процессорное время) во время сбоя. Крайне редко рассматривается в качестве причины ошибки, потому как процесс был текущим, то есть просто выполнялся процессором в момент сбоя.
LAST_CONTROL_TRANSFER Поле показывает последний вызов в стеке. В нашем случае код по адресу 8054b583 вызвал функцию по адресу 804f9f5f. Для определения функции можно посмотреть данные адреса командой ln address.
STACK_TEXT Поле отражает карту стека в момент сбоя. Первое поле - EBP (фрейм), второе поле - адрес возврата, третье поле - первый передаваемый параметр, четвертое поле - второй передаваемый параметр, пятое поле - третий передаваемый параметр. Последнее поле - имя модуля и вызываемого метода внутри него в формате модуль!функция+смещение.
STACK_COMMAND Поле указывает на команду, применяемую для отображения стека вызовов. В нашем случае это kb, то есть отображение стека вызовов с первыми тремя параметрами, переданными каждой функции. Помните описание поля STACK_TEXT? Начиная с третьего поля там - это первый, второй и третий передаваемые параметры.
FOLLOWUP_IP Поле описывает участок кода, где возникла ошибка. Первое значение поля указывает на сбойную инструкцию в формате модуль!функция+смещение, во втором значении следует адрес, машинный код команды и сама дизассемблированная команда в мнемонике языка ассемблер, при выполнении которой и произошел сбой.
SYMBOL_STACK_INDEX
SYMBOL_NAME Описывает символическое имя инструкции, вызвавшей сбой. Символическое имя представляет собой смещение до сбойной инструкции в формате модуль!функция+смещение. В нашем случае это portcls!PcDispatchProperty+150.
FOLLOWUP_NAME Вероятно (!) говорит об имени следующего выполняемого отладчиком набора команд. Значение MachineOwner может указывать на то, что после выполнения команды ожидается пользовательский ввод.
MODULE_NAME Имя модуля в таблице объектов ядра. Если анализатору удалось обнаружить проблемный драйвер, имя отображается в полях MODULE_NAME и IMAGE_NAME.
IMAGE_NAME Имя исполняемого образа, файла. Если анализатору удалось обнаружить проблемный драйвер, имя отображается в полях MODULE_NAME и IMAGE_NAME.
DEBUG_FLR_IMAGE_TIMESTAMP Вероятно (!) время, прошедшее с момента загрузки исполняемого образа в память.
FAILURE_BUCKET_ID Вероятно (!) некий классификатор сбоя. Группирует в себе код сбоя (BUGCHECK_STR), символическое имя ошибки, имя сбойного модуля/образа (IMAGE_NAME) и, если применимо, наименование функции со смещением (SYMBOL_NAME).
BUCKET_ID Вероятно (!) некий классификатор сбоя. Группирует в себе код сбоя (BUGCHECK_STR), символическое имя ошибки, имя сбойного модуля/образа (IMAGE_NAME) и, если применимо, наименование функции со смещением (SYMBOL_NAME).

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

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

Анализ аварийного дампа приложения

Второй основной сценарий, в котором может применяться отладчик WinDbg с точки зрения анализа различного вида дампов - это изучение дампа памяти сбойного приложения (процесса). Полученный при помощи специализированных средств (Диспетчер задач Windows, Отчеты об ошибках Windows (WER), внешние отладчики cdb,...) дамп памяти глючащего приложения, можно так же с успехом проанализировать в отладчике. При этом, общий сценарий исследования и поиска причин меняется несущественно, в него, всего-лишь, добавляется дополнительная команда !dlls. Эта команда позволяет создать отчет о загруженных непосредственно до записи дампа в адресное пространство процесса библиотеках, что дает возможность получить зачастую достаточно важную дополнительную информацию об адресном пространстве изучаемой программы. Синтаксис команды таков:

!dlls

Затем, выполняем уже изученную нами по предыдущему разделу команду:

!analyze –v

WinDbg не видит символы

При отсутствии сконфигурированного пути поиска символов и отсутствии в локальном кеше отладочных символов необходимых модулей/драйверов, отладчик WinDbg предупреждает нам об этом посредством сообщений об ошибках. Вот примерно такой текст мы можем наблюдать в информационном окне, когда Windbg не видит символы:

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

.symfix
.reload

Если есть подключение к сети Интернет, мы должны увидеть что-то вроде такого:

Команда .symfix просто (добавляет) подставляет в переменную пути строку srv*, что предписывает отладчику WinDbg в случае необходимости подключаться к серверу символов Microsoft по адресу http://msdl.microsoft.com/download/symbols и искать символы там. Команда также позволяет задавать путь к локальному кешу символов, и если локальный путь не задан, то по умолчанию используется поддиректория sym по пути инсталляции дистрибутива отладки. А команда .reload производит перечисление всех загруженных в адресное пространство процесса модулей и пытается найти ассоциированные файлы символов (в локальном хранилище, а так же на серверах Microsoft) для каждого из этих модулей. В случае же отсутствия подключения к сети Интернет, проблема приобретает совершенно другой вид и нам потребуется вручную скачивать символы к необходимым модулям, но это уже совершенно другая история. Ну и не лишним будет еще раз напомнить о том, что для того, что бы сохранить данные настройки в рабочем окружении (workspace) отладчика, необходимо выбрать меню "File" и далее пункт "Save Workspace".

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

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