![]() |
Описание формата Dmp крешдампов и написание простейшего дампера
Article: Описание формата DMP крешдампов и написание простейшего дампера
Author: Great Date: 07.09.2007 Lang: C++ I. Структура крешдампа Не так давно я зарелизил свой аналог livekd под названием gr8lkd, который является драйвером-фильтром файловой системы, аттачащийся к диску C:\ и создающий на нем виртуальный креш дамп, перехватывая и обрабатывая все запросы к нему. Теперь у меня возникло желание описать формат крешдампов в Windows - .DMP. В интернете можно найти всего 1 страницу, и то на английском, с каким-то не очень подробным и довольно кривым описанием) Я постараюсь описать этот формат наиболее полно и со всех сторон. Все нижеизложенное основывается только лишь на собственных исследованиях дизассемблерного листинга ядра, а именно функции IoWriteCrashDump() и связанных с ней. Приступим... Во-первых, немного о крешдампах и о том, как они генерируются. Когда происходит что-то плохое в ядре, например, освобождение уже освобожденной памяти, и когда ExFreePool (функция освобождения пула) это обнаруживает, продолжать работу может быть небезопасно, поэтому систему решается аварийно завершить. ExFreePool вызывает KeBugCheckEx( KeBugCheck2, KeBugCheck3, в зависимости от версии Windows, в первом случае это экспортируемая документированная функция, показывающая синий экран, во втором и третьем - внутренние, делающие тоже самое), которая показывает синий экран BAD_POOL_CALLER и вызывает IoWriteCrashDump для записи крешдампа. Крешдамп записывается аккурат в сектора диска, занимаемые файлом подкачки (для этого заблаговременно вызывается FSCTL_GET_RETRIEVAL_POINTERS для получения карты размещения файла подкачки), потому что на данном этапе вызывать драйвер файловой системы небезопасно - вдруг он и есть причина сбоя. Поэтому используется специальный драйвер, который сбрасывает дамп в файл подкачки. При следующей загрузке он достается оттуда и сохраняется по пути, прописанному в реестре. Как известно, в Windows существует три типа креш-дампов: 1) Minidump или, как он называется в ядре, triage dump. Размер такого дампа мал (обычно 64к) и они содержат минимум информации о системе в момент краха. Такой тип дампов стоит по умолчанию, но, к сожалению, он не подходит для обычных разработок в области ядра операционной системы, поэтому мы двинемся дальше и посмотрим на следующий тип: 2) Kernel Memory Dump или, как он называется в ядре, summary dump. В этот дамп включаются только страницы памяти ядра, причем не все, а только те, что необходимы для анализа краха. Этот тип наиболее подходит для обычного анализа причины краха системы, т.к. в дамп включаются код и данные ядра, всех загруженных драйверов и системных структур. Файла подкачки должно быть достаточно для размещения всех этих данных. Примерный размер его трудно предсказать, у меня он обычно составляет 1/8 от размера физической памяти. 3) Full Memory Dump. В такой дамп последовательно включаются все страницы физической памяти. Файла подкачки должно быть достаточно, чтобы вместить все страницы памяти. Рассмотрим сперва структуру, общую для всех дампов - dump header page, это первая страница файла дампа, первые 0x1000 (4096) байт. Структура этой страницы такова - сперва вся страница заполняется заполнителем "PAGE" (байты 'EGAP'), а потом по нужным смещениям записываются данные. Сперва идет структура, идентифицирующая сам крешдамп: Код:
typedef struct _DUMP_HEADER {Это поле не заполняется и в нем оставляются лежать байты 'EGAP' ValidDump Сигнатура дампа - "DUMP" ('PMUD') MajorVersion 0x0F если Free build 0x0C если Checked build MinorVersion Build number системы DirectoryTableBase Значение CR3 в момент краха системы - физический адрес каталога страниц PfnDatabase Виртуальный адрес MmPfnDatabase - база данных фреймов страниц (PFN) PsLoadedModuleList Виртуальный адрес PsLoadedModuleList - список загруженных модулей PsActiveProcessHead Виртуальный адрес PsActiveProcessHead - список активных процессов MachineImageType на x86 это 0x14C, остальные константы можно посмотреть в winnt.h NumberProcessors Число процессоров системы, берется из KeKeNumberProcessors BugCheckCode Стоп-код ошибки BugCheckParameter1 BugCheckParameter2 BugCheckParameter3 BugCheckParameter4 Параметры ошибки VersionUser Версия чего-то, так и не выяснил чего. Первый байт обычно записан в 0, остальное не заполнено PaeEnabled =1 если включена поддержка Physical Address Extensions (PAE), =0 если выключена KdDebuggerDataBlock Виртуальный адрес очень важной структуры KdDebuggerDataBlock, описание которой я дам позже. Вот так нехитро устроено начало дампа. Дальше по определенным смещениям запихнуты еще несколько блоков данных, для начала определим некоторые константы: Код:
// Data BlocksКод:
ULONG* blocks = (ULONG*) HeaderPage;DH_PHYSICAL_MEMORY_BLOCK Тут содержится описатель физической памяти, структура PHYSICAL_MEMORY_DESCRIPTOR: Код:
typedef unsigned long PFN_NUMBER;Например на моей системе (512МБ физической памяти) они выглядят так: Цитата:
Размер данного блока равен sizeof(PHYSICAL_MEMORY_DESCRIPTOR) - sizeof(PHYSICAL_MEMORY_RUN) + sizeof(PHYSICAL_MEMORY_RUN) * NumberOfRuns DH_CONTEXT_RECORD Тут хранится структура CONTEXT, хранящая в себе контекст потока, вызвавшего ошибку. Дополнительных комментариев, думаю, не нужно. DH_EXCEPTION_RECORD Аналогично предыдущему, здесь хранится структура EXCEPTION_RECORD с информацией об исключении, если она была доступна. DH_DUMP_TYPE Здесь хранится дворд, определяющий непосредственно тип данного дампа - triage, summary или full dump. Соответствующие значения этого поля: Код:
// Dump typesТут сохраняется NTSTATUS от вызова BugcheckCallbacks DH_PRODUCT_TYPE Тут сохраняется тип системы, описываемый перечислением Код:
enum _NT_PRODUCT_TYPE {Здесь сохраняется поле KUSER_SHARED_DATA->SuiteMask, и, честно признаться, я не знаю, что оно означает. DH_REQUIRED_DUMP_SPACE Здесь хранится LARGE_INTEGER, содержащий в себе полный размер дампа в байтах. DH_INTERRUPT_TIME Назначение этого поля мне неизвестно, берется из KUSER_SHARED_DATA->InterruptTime DH_SYSTEM_TIME Это поле берется из KUSER_SHARED_DATA->SystemTime и содержит, насколько мне известно, текущий аптайм. Вот, собственно, и всё с начальной страницей дампа. Дальнейшая структура дампа сильно зависит от его типа: 1. Minidump (triage dump) Данный тип дампа не особо интересен, поэтому просто приведу частичную структуру TRIAGE_DUMP_HEADER без особых комментариев (анализ ее я еще не закончил), отметив лишь, что она содержит оффсеты некоторых структур, которые следуют дальше в сыром виде: Код:
// Triage dump header2. Kernel memory dump (Summary dump) Вторая страница дампа начинается со структуры Код:
// Kernel summary dump headerSignature2 Эти поля содержат сигнатуру "SDMP", точнее они просто не заполняются, а вся структура заполняется этими двордами перед заполнением. ValidDump Это поле содержит сигнатуру "DUMP" HeaderSize Полный размер заголовка дампа BitmapSize Размер битовой карты, следующей непосредственно за этой структурой - о ней позже. Pages Количество страниц памяти ядра, включенных в дамп (совпадает с числом всех установленных битов битовой карты). Непосредственно за этой структурой следует битовая карта, число бит в ней равно числу физических страниц в системе, а установленные соответсвующие биты сообщают, включена ли конкретная страница в дамп, или нет. Для работы с такой битовой картой предназначены структура: Код:
typedef struct _RTL_BITMAP {Во второй части статьи я приведу пример простейшего анализатора крешдампа и покажу, как работать с этой картой. 3. Full dump После заглавной страницы дампы данного типа содержат подряд все физические страницы из всех memory runs, начиная с 0 и заканчивая NumberOfRuns-1. |
II. Написание анализатора дампа
Теперь у нас есть достаточно знаний, чтобы написать простейший анализатор крешдампа. Загруженный дамп в нашем анализаторе будет представлен структурой: Код:
// Mapped crash dump descriptorКод:
//Код:
PBYTE GetSummaryDumpPagesByPhysicalAddress( MappedCrashDump *CrashDump, ULONGLONG AddressAtCrashTime )Дальше нужно определить аналогичную функцию для полного дампа. Для начала нужно научиться конвертировать PFN страницы в памяти в PFN страницы на диске, и наоборот. С этим справятся следующие две функции: Код:
PFN_NUMBER GetPhysicalPFN( PPHYSICAL_MEMORY_DESCRIPTOR ppmd, PFN_NUMBER DumpPFN )Код:
PBYTE GetCompleteDumpPagesByPhysicalAddress( MappedCrashDump *CrashDump, ULONGLONG AddressAtCrashTime )Код:
//Напомню, что в режиме PAE (Physical Address Extensions) адресация стала трехуровневой. Регистр CR3 больше не указывает на PDE, теперь он указывает на массив Page Directory Pointer Entries - PDPE, которых должно быть 4 штуки. Каждый из PDPE указывает на массив PDE, а уже PDE указывают на PTE. Структура виртуального адреса немного изменилась, у PDE и PTE "откусили" по одному биту в пользу двухбитового поля номера PDPE. С учетом всего этого, функция трансляции адресов: Код:
// |
Код:
struct CONST_DESCRIPTION {Вывод примерно следующий: Цитата:
Мы подошли к самому интересному моменту, а именно к созданию собственного файда дампа. Сперва стоит описать недокументированную структуру KdDebuggerDataBlock, поскольку она нам очень поможет при создании дампа. Итак, по адресу KdDebuggerDataBlock лежит следующее: Код:
template <class T> |
Это же очень удобно! Достаточно получить лишь один неэкспортируемый адрес KdDebuggerDataBlock, как все остальные нужные адреса как на ладони.
Поле ValidBlock должно содержать сигнатуру 'GBDK', а Size должно точно равняться sizeof(KD_DEBUGGER_DATA_BLOCK). Получить адрес KdDebuggerDataBlock можно через экспортируемый символ KeCapturePersistentThreadState, а именно (для версии xp 2600): KdDebuggerDataBlock = *(PVOID*)((ULONG)KeCapturePersistentThreadState + *(ULONG*)((ULONG)KeCapturePersistentThreadState + 0xC )+ 0x11); Не очень симпотично, зато что нам это дает! Множество неэкспортируемых адресов внутренних структур. Оно нам потребуется для заполнения хидера дампа. Ну а теперь, поскольку мы всё знаем, приведу отрывочный код заполнения заглавной страницы дампа: Код:
BOOLEANhttp://gr8.cih.ms/uploads/gr8lkd2.rar (81.1 Kb) http://gr8.cih.ms/uploads/AnalyseCrashDump.rar (11.6 Kb) http://gr8.cih.ms/uploads/gendump.rar (10.1 Kb) |
Лог анализа сгенеренного дампа в WinDbg:
Цитата:
|
Поправочка:
Цитата:
То есть у меня доступны физические страницы с pfn'ами (номерами, другими словами) от 1 до 0x9f, от 0x100 до 0xfff и от 0x1000 до 0x1fff0. |
Тулза полезная ) +1
P.S. Gr8t вылезай из 0 колица ))) |
|
| Время: 21:53 |