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

Метки:  , , , ,

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

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

WinDbg объединяет в себе широкий спектр функционала, огромное количество внутренних и внешних команд, макросов и расширений, количество которых достаточно велико, что позволяет существенно расширить функционал отладчика. К основным функциям отладчика относятся пошаговое выполнение исследуемого кода, установка точек останова, просмотр переменных, трассировка стека вызова, просмотр областей памяти, исследование аварийных дампов системы и многое другое. Отладчик 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.

Анализ дампа в WinDbg

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

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

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

На скриншоте выше я выделил красным цветом важную для нас строку 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 не видит символы

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

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

.symfix
.reload

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

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

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

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

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