PDA

Просмотр полной версии : Описание формата Dmp крешдампов и написание простейшего дампера


_Great_
07.09.2007, 19:38
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 {
/* 00 */ ULONG Signature;
/* 04 */ ULONG ValidDump;
/* 08 */ ULONG MajorVersion;
/* 0c */ ULONG MinorVersion;
/* 10 */ ULONG DirectoryTableBase;
/* 14 */ PULONG PfnDataBase;
/* 18 */ PLIST_ENTRY PsLoadedModuleList;
/* 1c */ PLIST_ENTRY PsActiveProcessHead;
/* 20 */ ULONG MachineImageType;
/* 24 */ ULONG NumberProcessors;
/* 28 */ ULONG BugCheckCode;
/* 2c */ ULONG BugCheckParameter1;
/* 30 */ ULONG BugCheckParameter2;
/* 34 */ ULONG BugCheckParameter3;
/* 38 */ ULONG BugCheckParameter4;
/* 3c */ CHAR VersionUser[32];
/* 5c */ BYTE PaeEnabled;
BYTE NotUsed[3];
/* 60 */ PVOID KdDebuggerDataBlock;
} DUMP_HEADER, *PDUMP_HEADER;

Signature
Это поле не заполняется и в нем оставляются лежать байты '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
#define DH_PHYSICAL_MEMORY_BLOCK 25
#define DH_CONTEXT_RECORD 200
#define DH_EXCEPTION_RECORD 500
#define DH_DUMP_TYPE 994
#define DH_CALLBACKS_STATUS 996 // (поле необязательно)
#define DH_PRODUCT_TYPE 997 // (поле необязательно, значение берется из KUSER_SHARED_DATA)
#define DH_SUITE_MASK 998 // (поле необязательно, значение берется из KUSER_SHARED_DATA)
#define DH_REQUIRED_DUMP_SPACE 1000
#define DH_INTERRUPT_TIME 1006 // (поле необязательно, значение берется из KUSER_SHARED_DATA)
#define DH_SYSTEM_TIME 1008 // (поле необязательно, значение берется из KUSER_SHARED_DATA)

Первая страница рассматривается как массив DWORD-ов и эти константы определяют индексы в этом массиве, где содержатся данные. То есть, другими словами, на си:
ULONG* blocks = (ULONG*) HeaderPage;
//&blocks[DH_xxx] - адрес соответствующего блока

Рассмотрим теперь эти блоки по отдельности:

DH_PHYSICAL_MEMORY_BLOCK
Тут содержится описатель физической памяти, структура PHYSICAL_MEMORY_DESCRIPTOR:
typedef unsigned long PFN_NUMBER;

typedef struct _PHYSICAL_MEMORY_RUN {
PFN_NUMBER BasePage;
PFN_NUMBER PageCount;
} PHYSICAL_MEMORY_RUN, *PPHYSICAL_MEMORY_RUN;

typedef struct _PHYSICAL_MEMORY_DESCRIPTOR {
ULONG NumberOfRuns;
PFN_NUMBER NumberOfPages;
PHYSICAL_MEMORY_RUN Run[1];
} PHYSICAL_MEMORY_DESCRIPTOR, *PPHYSICAL_MEMORY_DESCRIPTOR;

Она, вместе с массивами Run, описывает доступную физическую память. Обычно в системе есть три-четыре Run'а - сплошных набора физических страниц.
Например на моей системе (512МБ физической памяти) они выглядят так:
PPHYSICAL_MEMORY_DESCRIPTOR:
NumberOfRuns = 0x00000003 NumberOfPages = 0x0001ff8d
PPHYSICAL_MEMORY_RUN[0]: BasePage = 0x00000001 PageCount = 0x0000009e
PPHYSICAL_MEMORY_RUN[1]: BasePage = 0x00000100 PageCount = 0x00000eff
PPHYSICAL_MEMORY_RUN[2]: BasePage = 0x00001000 PageCount = 0x0001eff0
То есть у меня доступны физические страницы с pfn'ами (номерами, другими словами) от 1 до 0x9e, от 0x100 до 0xeff и от 0x1000 до 0x1eff0.

Размер данного блока равен 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
#define DUMP_TYPE_TRIAGE 4
#define DUMP_TYPE_SUMMARY 2
#define DUMP_TYPE_FULL 1

DH_CALLBACKS_STATUS
Тут сохраняется NTSTATUS от вызова BugcheckCallbacks

DH_PRODUCT_TYPE
Тут сохраняется тип системы, описываемый перечислением enum _NT_PRODUCT_TYPE {
NtProductWinNt = 0x1,
NtProductLanManNt = 0x2,
NtProductServer = 0x3,
};

DH_SUITE_MASK
Здесь сохраняется поле 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 header
typedef struct _TRIAGE_DUMP_HEADER {
ULONG ServicePackBuild; // 00
ULONG SizeOfDump; // 04
ULONG ValidOffset; // 08
ULONG ContextOffset; // 0c
ULONG ExceptionOffset; // 10
ULONG MmOffset; // 14
ULONG UnloadedDriversOffset; // 18
ULONG PrcbOffset; // 1c
ULONG ProcessOffset; // 20
ULONG ThreadOffset; // 24
ULONG Unknown1; // 28
ULONG Unknown2; // 2c
ULONG DriverListOffset; // 30
ULONG DriverCount; // 34

...

ULONG TriageOptions; // 44

} TRIAGE_DUMP_HEADER, *PTRIAGE_DUMP_HEADER;


2. Kernel memory dump (Summary dump)
Вторая страница дампа начинается со структуры // Kernel summary dump header
typedef struct _SUMMARY_DUMP_HEADER {
ULONG Signature1; // 00
ULONG ValidDump; // 04
ULONG Signature2; // 08
ULONG HeaderSize; // 0c
ULONG BitmapSize; // 10
ULONG Pages; // 14
ULONG Unknown3; // 18
ULONG Unknown4; // 1c

} SUMMARY_DUMP_HEADER, *PSUMMARY_DUMP_HEADER;

Signature1
Signature2
Эти поля содержат сигнатуру "SDMP", точнее они просто не заполняются, а вся структура заполняется этими двордами перед заполнением.

ValidDump
Это поле содержит сигнатуру "DUMP"

HeaderSize
Полный размер заголовка дампа

BitmapSize
Размер битовой карты, следующей непосредственно за этой структурой - о ней позже.

Pages
Количество страниц памяти ядра, включенных в дамп (совпадает с числом всех установленных битов битовой карты).

Непосредственно за этой структурой следует битовая карта, число бит в ней равно числу физических страниц в системе, а установленные соответсвующие биты сообщают, включена ли конкретная страница в дамп, или нет. Для работы с такой битовой картой предназначены структура:
typedef struct _RTL_BITMAP {
ULONG SizeOfBitMap; // Number of bits in bit map
PULONG Buffer; // Pointer to the bit map itself
} RTL_BITMAP;
typedef RTL_BITMAP *PRTL_BITMAP; и функции RtlInitializeBitMap, RtlClearAllBits, RtlSetAllBits, RtlFindClearBits, RtlFindSetBits, RtlCheckBit и другие.
Во второй части статьи я приведу пример простейшего анализатора крешдампа и покажу, как работать с этой картой.


3. Full dump
После заглавной страницы дампы данного типа содержат подряд все физические страницы из всех memory runs, начиная с 0 и заканчивая NumberOfRuns-1.

_Great_
07.09.2007, 19:39
II. Написание анализатора дампа

Теперь у нас есть достаточно знаний, чтобы написать простейший анализатор крешдампа.
Загруженный дамп в нашем анализаторе будет представлен структурой:
// Mapped crash dump descriptor
struct MappedCrashDump {
HANDLE hFile;
HANDLE hMapping;
union {
PBYTE lpMapping;
PDUMP_HEADER DumpHeader;
ULONG* DataBlocks;
};
ULONG *DumpType;
ULONG *DumpFlags;
PLARGE_INTEGER DumpSize;
PPHYSICAL_MEMORY_DESCRIPTOR PhysicalMemoryDescriptor;
PCONTEXT Context;
union {
// Summary dump type
struct {
PSUMMARY_DUMP_HEADER pSummaryDumpHeader;
RTL_BITMAP KernelMemoryMap;
};

// Triage dump type
PTRIAGE_DUMP_HEADER pTriageDumpHeader;
};
};

План действий будет таков - мы открываем файл, создаем объект проекции и проецируем первые 10 страниц файла дампа, остальное будет проецироваться по мере необходимости. Для реализации этого напишем функцию MapDumpPages, которая будет проецировать вид файла, если он еще не спроецирован:

//
// This routine maps dump page at given file offset (DesiredAddress is the sum of BaseMapping and desired offset)
//

PVOID MapDumpPage( HANDLE hMapping, PBYTE BaseMapping, PBYTE DesiredAddress )
{
ULONG FileOffset = (ULONG) ( ( (ULONG_PTR)DesiredAddress-(ULONG_PTR)BaseMapping ) & 0xFFFF0000 );
PVOID Base = (PVOID)(ULONG_PTR) ( ((ULONG)(ULONG_PTR)DesiredAddress)&0xFFFF0000 );
MEMORY_BASIC_INFORMATION mbi = {0};
PVOID Ret = 0;

VirtualQuery( DesiredAddress, &mbi, sizeof(mbi) );

if( mbi.State != MEM_COMMIT ) {
Ret = MapViewOfFileEx( hMapping,
FILE_MAP_READ,
0,
FileOffset,
0x10000,
Base
);
if( Ret == Base ) {
return DesiredAddress;
}

// Loaded at different address. Fail
UnmapViewOfFile( Ret );
return NULL;
}

return DesiredAddress;
}

Дальше мы реализуем функцию, которая получит страницу из summary-дампа по её физическому адресу в момент краха:
PBYTE GetSummaryDumpPagesByPhysicalAddress( MappedCrashDump *CrashDump, ULONGLONG AddressAtCrashTime )
{
PBYTE StartingAddress = (PBYTE)(ULONG_PTR) ( (ULONG)(ULONG_PTR)CrashDump->pSummaryDumpHeader + CrashDump->pSummaryDumpHeader->HeaderSize - 0x1000 );
ULONG NumberOfPage = (ULONG) (AddressAtCrashTime / 0x1000);
ULONG OffsetInPage = (ULONG) (AddressAtCrashTime & 0xFFF);

if( NumberOfPage >= CrashDump->KernelMemoryMap.SizeOfBitMap )
return NULL;

if( RtlCheckBit( &CrashDump->KernelMemoryMap, NumberOfPage ) == 0 )
return NULL; // not included in dump

// Calculate page number in dump
ULONG PageNumber = 0;

for( ULONG i=0; i<CrashDump->KernelMemoryMap.SizeOfBitMap; i++ ) {
if( i == NumberOfPage ) {
PBYTE Result = StartingAddress + PageNumber*0x1000 + OffsetInPage;

return (PBYTE) MapDumpPage( CrashDump->hMapping, CrashDump->lpMapping, Result );
}

if( RtlCheckBit( &CrashDump->KernelMemoryMap, i ) ) {
PageNumber++;
}
}

return NULL;
}
Тут реализована работа с битовой картой, чтобы узнать, присутствует ли страница в дампе, и чтобы узнать ее номер на диске. После этого она проецируется с диска в оперативную память, если еще не спроецирована.

Дальше нужно определить аналогичную функцию для полного дампа. Для начала нужно научиться конвертировать PFN страницы в памяти в PFN страницы на диске, и наоборот. С этим справятся следующие две функции:
PFN_NUMBER GetPhysicalPFN( PPHYSICAL_MEMORY_DESCRIPTOR ppmd, PFN_NUMBER DumpPFN )
{
PFN_NUMBER iBuffPage, iPage = iBuffPage = DumpPFN;

if( iBuffPage >= ppmd->NumberOfPages )
return -1;

// Calculate page in memory
ULONG NumberOfRunsRequired = 0;
PFN_NUMBER TotalPageCount = 0;

for( ; NumberOfRunsRequired<ppmd->NumberOfRuns; NumberOfRunsRequired++ )
{
PPHYSICAL_MEMORY_RUN Runs = ppmd->Run;

if( iBuffPage >= TotalPageCount &&
iBuffPage < TotalPageCount + Runs[NumberOfRunsRequired].PageCount )
break;

TotalPageCount += (Runs[NumberOfRunsRequired].PageCount);
}

PFN_NUMBER PreviousEnd = 0;
NumberOfRunsRequired ++;

for( ULONG i=0; i<NumberOfRunsRequired; i++ )
{
PPHYSICAL_MEMORY_RUN Runs = ppmd->Run;

iPage += (Runs[i].BasePage - PreviousEnd);
PreviousEnd = Runs[i].BasePage + Runs[i].PageCount;
}

return iPage;
}

PFN_NUMBER GetDumpPFN( PPHYSICAL_MEMORY_DESCRIPTOR ppmd, PFN_NUMBER PhysicalPFN )
{
for( int run=0; run<ppmd->NumberOfRuns; run++ )
{
PPHYSICAL_MEMORY_RUN Runs = ppmd->Run;

if( PhysicalPFN >= Runs[run].BasePage &&
PhysicalPFN < (Runs[run].BasePage + Runs[run].PageCount) )
break;
}

if( run == ppmd->NumberOfRuns )
return -1;

PFN_NUMBER iDumpPFN = 0;

for( int i=0; i<run; i++ )
{
iDumpPFN += ppmd->Run[i].PageCount;
}

iDumpPFN += PhysicalPFN - ppmd->Run[i].BasePage;

return iDumpPFN;
}

Дальше можно описать функцию, получающую страницу из дампа по ее физическому адресу на момент краха и общую функцию, получающую тип дампа и вызывающую нужную из двух функций получения адреса:
PBYTE GetCompleteDumpPagesByPhysicalAddress( MappedCrashDump *CrashDump, ULONGLONG AddressAtCrashTime )
{
PBYTE StartingAddress = (PBYTE)(ULONG_PTR)( (ULONG)(ULONG_PTR)CrashDump->lpMapping + 0x1000 );
PFN_NUMBER PFN = (ULONG) (AddressAtCrashTime >> 12);
ULONG OffsetInPage = (ULONG) (AddressAtCrashTime & 0xFFF);

// Calculate page number in dump
for( ULONG i=0; i<CrashDump->PhysicalMemoryDescriptor->NumberOfRuns; i++ ) {
PPHYSICAL_MEMORY_RUN Runs = (PPHYSICAL_MEMORY_RUN) &CrashDump->PhysicalMemoryDescriptor->Run;

if( PFN >= Runs[i].BasePage && PFN < (Runs[i].BasePage + Runs[i].PageCount) ) {

PFN_NUMBER DumpPFN = GetDumpPFN( CrashDump->PhysicalMemoryDescriptor, PFN );
PBYTE Result = StartingAddress + DumpPFN*0x1000 + OffsetInPage;

return (PBYTE) MapDumpPage( CrashDump->hMapping, CrashDump->lpMapping, Result );
}
}
return NULL;
}

PBYTE GetDumpPagesByPhysicalAddress( MappedCrashDump *CrashDump, ULONGLONG AddressAtCrashTime )
{
if( *CrashDump->DumpType == DUMP_TYPE_SUMMARY ) {

return GetSummaryDumpPagesByPhysicalAddress( CrashDump, AddressAtCrashTime );

} else if( *CrashDump->DumpType == DUMP_TYPE_COMPLETE ) {

return GetCompleteDumpPagesByPhysicalAddress( CrashDump, AddressAtCrashTime );

} else {

return NULL;
}
}

Теперь нам нужно научиться транслировать виртуальные адреса в физические, и тогда мы полностью сможем вынуть данные по любому виртуальному адресу из дампа. Для начала определим структуры каталогов PDPE, PDE и PTE, которые нам могут понадобиться:
//
// Page Directory Entry
//

struct PDE {
DWORD Present:1;
DWORD ReadWrite:1;
DWORD UserSupervisor:1;
DWORD WriteThrough:1;
DWORD CacheDisabled:1;
DWORD Accessed:1;
DWORD Reserved:1; // Dirty, ignored
DWORD PageSize:1;
DWORD GlobalPage:1; // Ignored
DWORD Available:3;
DWORD Pte:19;
};


//
// Page Table Entry
//

struct PTE {
DWORD Present:1;
DWORD ReadWrite:1;
DWORD UserSupervisor:1;
DWORD WriteThrough:1;
DWORD CacheDisabled:1;
DWORD Accessed:1;
DWORD Dirty:1;
DWORD PageTableAttributeIndex:1;
DWORD GlobalPage:1;
DWORD Available:3;
DWORD PageFrameNumber:19;
};

// Virtual address
struct VIRTUAL_ADDRESS {
DWORD Offset:12;
DWORD Table:10;
DWORD Directory:10;
};


//
// Page Directory Entry in PAE mode
//

#define QWORD ULONGLONG

struct LongPDE {
QWORD Present:1;
QWORD ReadWrite:1;
QWORD UserSupervisor:1;
QWORD WriteThrough:1;
QWORD CacheDisabled:1;
QWORD Accessed:1;
QWORD Reserved:1; // Dirty, ignored
QWORD PageSize:1;
QWORD GlobalPage:1; // Ignored
QWORD Available:3;
QWORD Pte:24;
QWORD ReservedHigh:28;
};


//
// Page Table Entry in PAE mode
//

struct LongPTE {
QWORD Present:1;
QWORD ReadWrite:1;
QWORD UserSupervisor:1;
QWORD WriteThrough:1;
QWORD CacheDisabled:1;
QWORD Accessed:1;
QWORD Dirty:1;
QWORD PageTableAttributeIndex:1;
QWORD GlobalPage:1;
QWORD Available:3;
QWORD PageFrameNumber:24;
QWORD ReservedHigh:28;
};

//
// Page Directory Pointer Table (PAE mode only)
//

struct PDPE {
QWORD Present:1;
QWORD Reserved1:2;
QWORD WriteThough:1;
QWORD CacheDisabled:1;
QWORD Reserved2:4;
QWORD Available:3;
QWORD Pdt:24;
QWORD ReservedHigh:28;
};

// Virtual address (PAE)
struct PAE_VIRTUAL_ADDRESS {
DWORD Offset:12;
DWORD Table:9;
DWORD Directory:9;
DWORD DirectoryPointer:2;
};

Тут определены две серии структур - без включенного PAE и с включенным PAE.
Напомню, что в режиме PAE (Physical Address Extensions) адресация стала трехуровневой. Регистр CR3 больше не указывает на PDE, теперь он указывает на массив Page Directory Pointer Entries - PDPE, которых должно быть 4 штуки. Каждый из PDPE указывает на массив PDE, а уже PDE указывают на PTE. Структура виртуального адреса немного изменилась, у PDE и PTE "откусили" по одному биту в пользу двухбитового поля номера PDPE.

С учетом всего этого, функция трансляции адресов:
//
// Translates virtual address to physical address
//

ULONGLONG VirtualToPhysical( MappedCrashDump *CrashDump, ULONG VirtualAddress )
{
ULONG CR3 = CrashDump->DumpHeader->DirectoryTableBase;

CR3 &= 0xFFFFFFF0; // clear flags in cr3

if( CrashDump->DumpHeader->PaeEnabled )
{
// PAE enabled. Use 3-level addressing system: PDPE->PDE->PTE->Page
PAE_VIRTUAL_ADDRESS va;

*(ULONG*)&va = VirtualAddress;

PDPE* dirptr = (PDPE*)GetDumpPagesByPhysicalAddress( CrashDump, CR3 );

if( dirptr != NULL && dirptr[va.DirectoryPointer].Present )
{
LongPDE *dir = (LongPDE*)GetDumpPagesByPhysicalAddress( CrashDump, dirptr[va.DirectoryPointer].Pdt << 12 );

if( dir != NULL && dir[va.Directory].Present )
{
LongPTE* tbl = (LongPTE*)GetDumpPagesByPhysicalAddress( CrashDump, dir[va.Directory].Pte << 12 );

if( tbl != NULL && tbl[va.Table].Present )
{
return ( tbl[va.Table].PageFrameNumber << 12 ) | va.Offset;
}
}
}
}
else
{
// PAE disabled. Use 2-level addressing system: PDE->PTE->Page
VIRTUAL_ADDRESS va;
*(ULONG*)&va = VirtualAddress;

PDE *dir = (PDE*)GetDumpPagesByPhysicalAddress( CrashDump, CR3 );

if( dir != NULL && dir[va.Directory].Present )
{
PTE* tbl = (PTE*)GetDumpPagesByPhysicalAddress( CrashDump, dir[va.Directory].Pte << 12 );

if( tbl != NULL && tbl[va.Table].Present )
{
return ( tbl[va.Table].PageFrameNumber << 12 ) | va.Offset;
}
}
}

return NULL;
}

//
// Translates virtual address to physical address and loads that physical pages
//

PVOID RetrieveDumpData( MappedCrashDump* CrashDump, PVOID VirtualAddress )
{
return GetDumpPagesByPhysicalAddress( CrashDump, VirtualToPhysical( CrashDump, (ULONG)(ULONG_PTR)VirtualAddress ) );
}

Всё готово. Напишем небольшой демонстрационный код для анализа крешдампа:

_Great_
07.09.2007, 19:39
struct CONST_DESCRIPTION {
ULONG Value;
LPSTR Desc;
#define DEFINE_STRING(x) { x, #x }
#define TABLE_END { 0, 0 }
};

CONST_DESCRIPTION MachineTypes[] = {
DEFINE_STRING( IMAGE_FILE_MACHINE_I386 ),
DEFINE_STRING( IMAGE_FILE_MACHINE_IA64 ),
TABLE_END
};

CONST_DESCRIPTION DumpTypes[] = {
DEFINE_STRING( DUMP_TYPE_TRIAGE ),
DEFINE_STRING( DUMP_TYPE_SUMMARY ),
DEFINE_STRING( DUMP_TYPE_FULL ),
TABLE_END
};

// Bugcheck descriptions
typedef ULONG NTSTATUS;
#include "D:\Progs\driverdev\bcdesc.h" // багчек коды бсодов, я не прилагаю этот файл, скажу лишь что там лежит функция BugCheckDescf, получающая имя бсода по его номеру.

// Получение имени константы по значению
LPSTR LookupConstDesc( CONST_DESCRIPTION* Table, ULONG Value )
{
while( Table->Desc ) {
if( Table->Value == Value ) {
return Table->Desc;
}
Table ++;
}
return "(unknown)";
}

void ExtractDumpHeader( MappedCrashDump* CrashDump, char* saveto )
{
HANDLE hFile = CreateFile( saveto, GENERIC_WRITE, 0, 0, OPEN_ALWAYS, 0, 0 );
DWORD wr;
WriteFile( hFile, CrashDump->lpMapping, 0x1000, &wr, 0 );
SetEndOfFile( hFile );
CloseHandle( hFile );
}


int AnalyseDump( char* filename )
{
MappedCrashDump CrashDump = {0};
CrashDump.hFile = INVALID_HANDLE_VALUE;

__try
{
CrashDump.hFile = CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0 );
if( CrashDump.hFile == INVALID_HANDLE_VALUE )
return 0;

CrashDump.hMapping = CreateFileMapping( CrashDump.hFile, 0, PAGE_READONLY, 0, 0, NULL );
if( CrashDump.hMapping == NULL )
return 0;

CrashDump.lpMapping = (PBYTE) MapViewOfFile( CrashDump.hMapping, FILE_MAP_READ, 0, 0, 0x10000 ); //0x12000 );
if( CrashDump.lpMapping == NULL )
return 0;

PDUMP_HEADER hdr = CrashDump.DumpHeader;

if( hdr->ValidDump != 'PMUD' || hdr->Signature != 'EGAP' ) {
printf("Invalid dump header\n");
return 0;
}

printf("Crash dump '%s' analysing started. Version: %d.%d\n\n", filename, hdr->MajorVersion, hdr->MinorVersion);

// Header
printf("CR3 = 0x%08x PfnDatabase = 0x%08x\n"
"PsLoadedModuleList = 0x%08x PsActiveProcessHead = 0x%08x\n", hdr->DirectoryTableBase, (ULONG_PTR)hdr->PfnDataBase,
(ULONG_PTR)hdr->PsLoadedModuleList, (ULONG_PTR)hdr->PsActiveProcessHead );
printf("Machine type: %s, NumberProcessors: %d\n", LookupConstDesc( MachineTypes, hdr->MachineImageType ), hdr->NumberProcessors);
printf("PaeEnabled = %d, KdDebuggerDataBlock = 0x%08x\n", hdr->PaeEnabled, (ULONG_PTR)hdr->KdDebuggerDataBlock);
printf("\n");

// Bug check
printf("Bugcheck code %s (0x%08x)\n", BugCheckDescf(hdr->BugCheckCode), hdr->BugCheckCode);
printf("Arguments[0] = 0x%08x\n", hdr->BugCheckParameter1);
printf("Arguments[1] = 0x%08x\n", hdr->BugCheckParameter2);
printf("Arguments[2] = 0x%08x\n", hdr->BugCheckParameter3);
printf("Arguments[3] = 0x%08x\n", hdr->BugCheckParameter4);
printf("\n");

// Data blocks
ULONG* block = CrashDump.DataBlocks;

// Dump type & size
CrashDump.DumpType = &block[ DH_DUMP_TYPE ];
CrashDump.DumpFlags = &block[ DH_DUMP_TYPE + 1 ];
CrashDump.DumpSize = (PLARGE_INTEGER) &block[ DH_REQUIRED_DUMP_SPACE ];
printf( "Dump type: %s, DumpSize: %d bytes (0x%08x)\n",
LookupConstDesc( DumpTypes, *CrashDump.DumpType ),
CrashDump.DumpSize->LowPart,
CrashDump.DumpSize->LowPart );
printf("\n");

// Physical memory descriptor
CrashDump.PhysicalMemoryDescriptor = (PPHYSICAL_MEMORY_DESCRIPTOR)( &block[DH_PHYSICAL_MEMORY_BLOCK] );
if( CrashDump.PhysicalMemoryDescriptor->NumberOfRuns == 'EGAP' )
{
printf("PPHYSICAL_MEMORY_DESCRIPTOR: Invalid\n");
}
else
{
printf( "PPHYSICAL_MEMORY_DESCRIPTOR:\nNumberOfRuns = 0x%08x NumberOfPages = 0x%08x\n",
CrashDump.PhysicalMemoryDescriptor->NumberOfRuns,
CrashDump.PhysicalMemoryDescriptor->NumberOfPages );
for( ULONG i=0;i<CrashDump.PhysicalMemoryDescriptor->NumberOfRuns;i++ )
{
printf( "PPHYSICAL_MEMORY_RUN[%d]: BasePage = 0x%08x PageCount = 0x%08x\n",
i,
CrashDump.PhysicalMemoryDescriptor->Run[i].BasePage,
CrashDump.PhysicalMemoryDescriptor->Run[i].PageCount );
}
}
printf("\n");

// Context record:
CrashDump.Context = (PCONTEXT)( &block[DH_CONTEXT_RECORD] );
printf( "Context record:\nEip = 0x%08x ESP = 0x%08x EBP = 0x%08x\n",
CrashDump.Context->Eip,
CrashDump.Context->Esp,
CrashDump.Context->Ebp );
printf("EAX=%08x EBX=%08x ECX=%08x EDX=%08x ESI=%08x EDI=%08x\n",
CrashDump.Context->Eax,
CrashDump.Context->Ebx,
CrashDump.Context->Ecx,
CrashDump.Context->Edx,
CrashDump.Context->Esi,
CrashDump.Context->Edi,
CrashDump.Context->Eax);
printf("\n");

// Analyse dump
if( *CrashDump.DumpType == DUMP_TYPE_TRIAGE )
{
//
// Minidump
//

CrashDump.pTriageDumpHeader = (PTRIAGE_DUMP_HEADER)( CrashDump.lpMapping + 0x1000 );

printf("Analysing triage dump header\n");

printf("[-] Not implemented\n");

__asm nop;
}
else if( *CrashDump.DumpType == DUMP_TYPE_SUMMARY )
{
//
// Kernel summary dump - only kernel address space available
//

CrashDump.pSummaryDumpHeader = (PSUMMARY_DUMP_HEADER) ( CrashDump.lpMapping + 0x1000 );
CrashDump.KernelMemoryMap.SizeOfBitMap = CrashDump.pSummaryDumpHeader->BitmapSize;
CrashDump.KernelMemoryMap.Buffer = (PULONG) ( CrashDump.pSummaryDumpHeader + 1 );

if( CrashDump.pSummaryDumpHeader->ValidDump != 'PMUD' ) {
printf("Invalid summary dump header\n");
return 0;
}

printf("Analyzing summary dump header\n");
printf( "HeaderSize = 0x%08x BitmapSize = 0x%08x\n",
CrashDump.pSummaryDumpHeader->HeaderSize,
CrashDump.pSummaryDumpHeader->BitmapSize );
printf( "Number of kernel pages in dump: 0x%08x\n\n",
CrashDump.pSummaryDumpHeader->Pages );


ULONG Virtual = CrashDump.Context->Eip;
ULONGLONG Physical;
Physical = VirtualToPhysical( &CrashDump, Virtual );

printf("Translating virtual address [0x%08x]: 0x%08x\n", Virtual, Physical);

PBYTE MappedEIP = (PBYTE)RetrieveDumpData( &CrashDump, (void*)(ULONG_PTR)CrashDump.Context->Eip );

if( MappedEIP )
{
printf("Bytes at [EIP=%08x]: %02x %02x %02x %02x %02x %02x %02x %02x\n", CrashDump.Context->Eip,
MappedEIP[0], MappedEIP[1], MappedEIP[2], MappedEIP[3],
MappedEIP[4], MappedEIP[5], MappedEIP[6], MappedEIP[7] );
}
else printf("Memory pointed by EIP is not present\n");

__asm nop;
}
else if( *CrashDump.DumpType == DUMP_TYPE_COMPLETE )
{
//
// Complete memory dump - full address space available
//

ULONG Virtual = CrashDump.Context->Eip;
ULONGLONG Physical;
Physical = VirtualToPhysical( &CrashDump, Virtual );

printf("Translating virtual address [0x%08x]: 0x%08x\n", Virtual, Physical);

PBYTE MappedEIP = (PBYTE)RetrieveDumpData( &CrashDump, (void*)(ULONG_PTR)CrashDump.Context->Eip );

printf("Bytes at [EIP=%08x]: %02x %02x %02x %02x %02x %02x %02x %02x\n", CrashDump.Context->Eip,
MappedEIP[0], MappedEIP[1], MappedEIP[2], MappedEIP[3],
MappedEIP[4], MappedEIP[5], MappedEIP[6], MappedEIP[7] );

ExtractDumpHeader( &CrashDump, "crashdump.hdr" );

__asm nop;

}


printf("\nDump analysis finished\n");
}
__finally
{
if( CrashDump.lpMapping )
UnmapViewOfFile( CrashDump.lpMapping );

if( CrashDump.hMapping )
CloseHandle( CrashDump.hMapping );

if( CrashDump.hFile != INVALID_HANDLE_VALUE )
CloseHandle( CrashDump.hFile );

Sleep(INFINITE);
}

return 0;
}


int main()
{
AnalyseDump( "C:\\gr8lkd.dmp" );
//AnalyseDump( "D:\\memory.dmp" );

return 0;
}

В нем уже забит путь к моему крешдампу c:\gr8lkd.dmp от моей утилиты gr8lkd.

Вывод примерно следующий:
Crash dump 'C:\gr8lkd.dmp' analysing started. Version: 15.2600

CR3 = 0x00373000 PfnDatabase = 0x80557b48
PsLoadedModuleList = 0x805531a0 PsActiveProcessHead = 0x80559258
Machine type: IMAGE_FILE_MACHINE_I386, NumberProcessors: 1
PaeEnabled = 1, KdDebuggerDataBlock = 0x80544ce0

Bugcheck code KMODE_EXCEPTION_NOT_HANDLED (0x0000001e)
Arguments[0] = 0x80000004
Arguments[1] = 0xf3b21315
Arguments[2] = 0x00000000
Arguments[3] = 0x00000000

Dump type: DUMP_TYPE_FULL, DumpSize: 536403968 bytes (0x1ff8e000)

PPHYSICAL_MEMORY_DESCRIPTOR:
NumberOfRuns = 0x00000003 NumberOfPages = 0x0001ff8d
PPHYSICAL_MEMORY_RUN[0]: BasePage = 0x00000001 PageCount = 0x0000009e
PPHYSICAL_MEMORY_RUN[1]: BasePage = 0x00000100 PageCount = 0x00000eff
PPHYSICAL_MEMORY_RUN[2]: BasePage = 0x00001000 PageCount = 0x0001eff0

Context record:
Eip = 0xf3b21315 ESP = 0xf8ab4928 EBP = 0xf8ab4c1c
EAX=00000000 EBX=816b7000 ECX=00000000 EDX=00004e24 ESI=00000000 EDI=f8ab4c00

Translating virtual address [0xf3b21315]: 0x1d9ac315
Bytes at [EIP=f3b21315]: 58 89 85 d0 fd ff ff 9c

Dump analysis finished

III. Создание собственного дампа.

Мы подошли к самому интересному моменту, а именно к созданию собственного файда дампа.
Сперва стоит описать недокументированную структуру KdDebuggerDataBlock, поскольку она нам очень поможет при создании дампа. Итак, по адресу KdDebuggerDataBlock лежит следующее:
template <class T>
struct PFUNC {
T VirtualAddress;
ULONG ZeroField;
};

typedef struct _KD_DEBUGGER_DATA_BLOCK {
ULONG Unknown1[4];
ULONG ValidBlock; // 'GBDK'
ULONG Size; // 0x290
PFUNC<PVOID> _imp__VidInitialize;
PFUNC<PVOID> RtlpBreakWithStatusInstruction;
ULONG Unknown2[4];
PFUNC<PVOID> KiCallUserMode;
ULONG Unknown3[2];
PFUNC<PVOID> PsLoadedModuleList;
PFUNC<PVOID> PsActiveProcessHead;
PFUNC<PVOID> PspCidTable;
PFUNC<PVOID> ExpSystemResourcesList;
PFUNC<PVOID> ExpPagedPoolDescriptor;
PFUNC<PVOID> ExpNumberOfPagedPools;
PFUNC<PVOID> KeTimeIncrement;
PFUNC<PVOID> KeBugCheckCallbackListHead;
PFUNC<PVOID> KiBugCheckData;
PFUNC<PVOID> IopErrorLogListHead;
PFUNC<PVOID> ObpRootDirectoryObject;
PFUNC<PVOID> ObpTypeObjectType;
PFUNC<PVOID> MmSystemCacheStart;
PFUNC<PVOID> MmSystemCacheEnd;
PFUNC<PVOID> MmSystemCacheWs;
PFUNC<PVOID> MmPfnDatabase;
PFUNC<PVOID> MmSystemPtesStart;
PFUNC<PVOID> MmSystemPtesEnd;
PFUNC<PVOID> MmSubsectionBase;
PFUNC<PVOID> MmNumberOfPagingFiles;
PFUNC<PVOID> MmLowestPhysicalPage;
PFUNC<PVOID> MmHighestPhysicalPage;
PFUNC<PVOID> MmNumberOfPhysicalPages;
PFUNC<PVOID> MmMaximumNonPagedPoolInBytes;
PFUNC<PVOID> MmNonPagedSystemStart;
PFUNC<PVOID> MmNonPagedPoolStart;
PFUNC<PVOID> MmNonPagedPoolEnd;
PFUNC<PVOID> MmPagedPoolStart;
PFUNC<PVOID> MmPagedPoolEnd;
PFUNC<PVOID> MmPagedPoolInfo;
PFUNC<PVOID> Unknown4;
PFUNC<PVOID> MmSizeOfPagedPoolInBytes;
PFUNC<PVOID> MmTotalCommitLimit;
PFUNC<PVOID> MmTotalCommittedPages;
PFUNC<PVOID> MmSharedCommit;
PFUNC<PVOID> MmDriverCommit;
PFUNC<PVOID> MmProcessCommit;
PFUNC<PVOID> MmPagedPoolCommit;
PFUNC<PVOID> Unknown5;
PFUNC<PVOID> MmZeroedPageListHead;
PFUNC<PVOID> MmFreePageListHead;
PFUNC<PVOID> MmStandbyPageListHead;
PFUNC<PVOID> MmModifiedPageListHead;
PFUNC<PVOID> MmModifiedNoWritePageListHead;
PFUNC<PVOID> MmAvailablePages;
PFUNC<PVOID> MmResidentAvailablePages;
PFUNC<PVOID> PoolTrackTable;
PFUNC<PVOID> NonPagedPoolDescriptor;
PFUNC<PVOID> MmHighestUserAddress;
PFUNC<PVOID> MmSystemRangeStart;
PFUNC<PVOID> MmUserProbeAddress;
PFUNC<PVOID> KdPrintCircularBuffer;
PFUNC<PVOID> KdPrintWritePointer;
PFUNC<PVOID> KdPrintWritePointer2;
PFUNC<PVOID> KdPrintRolloverCount;
PFUNC<PVOID> MmLoadedUserImageList;
PFUNC<PVOID> NtBuildLab;
PFUNC<PVOID> Unknown6;
PFUNC<PVOID> KiProcessorBlock;
PFUNC<PVOID> MmUnloadedDrivers;
PFUNC<PVOID> MmLastUnloadedDriver;
PFUNC<PVOID> MmTriageActionTaken;
PFUNC<PVOID> MmSpecialPoolTag;
PFUNC<PVOID> KernelVerifier;
PFUNC<PVOID> MmVerifierData;
PFUNC<PVOID> MmAllocateNonPagedPool;
PFUNC<PVOID> MmPeakCommitment;
PFUNC<PVOID> MmTotalCommitLimitMaximum;
PFUNC<PVOID> CmNtCSDVersion;
PFUNC<PPHYSICAL_MEMORY_DESCRIPTOR*> MmPhysicalMemoryBlock;
PFUNC<PVOID> MmSessionBase;
PFUNC<PVOID> MmSessionSize;
PFUNC<PVOID> Unknown7;

} KD_DEBUGGER_DATA_BLOCK, *PKD_DEBUGGER_DATA_BLOCK;

_Great_
07.09.2007, 19:40
Это же очень удобно! Достаточно получить лишь один неэкспортируемый адрес KdDebuggerDataBlock, как все остальные нужные адреса как на ладони.
Поле ValidBlock должно содержать сигнатуру 'GBDK', а Size должно точно равняться sizeof(KD_DEBUGGER_DATA_BLOCK).

Получить адрес KdDebuggerDataBlock можно через экспортируемый символ KeCapturePersistentThreadState, а именно (для версии xp 2600):
KdDebuggerDataBlock = *(PVOID*)((ULONG)KeCapturePersistentThreadState + *(ULONG*)((ULONG)KeCapturePersistentThreadState + 0xC )+ 0x11);

Не очень симпотично, зато что нам это дает! Множество неэкспортируемых адресов внутренних структур. Оно нам потребуется для заполнения хидера дампа.

Ну а теперь, поскольку мы всё знаем, приведу отрывочный код заполнения заглавной страницы дампа:
BOOLEAN
InitializeDumpHeader(
IN PBYTE HeaderPage
)
{
PDUMP_HEADER hdr;
ULONG* blocks;
PKD_DEBUGGER_DATA_BLOCK KdDebuggerDataBlock;
EXCEPTION_RECORD exception;
PVOID KeCapturePersistentThreadState;
UNICODE_STRING uKeCapturePersistentThreadState;
PPHYSICAL_MEMORY_DESCRIPTOR MmPhysicalMemoryBlock;
CONTEXT ctx = {CONTEXT_FULL};
PEXCEPTION_POINTERS pei;

// Get context
__asm
{
// Common registers
mov [ctx.Eax], eax
mov [ctx.Ebx], ebx
mov [ctx.Ecx], ecx
mov [ctx.Edx], edx
mov [ctx.Esi], esi
mov [ctx.Edi], edi

// Control registers
mov [ctx.Esp], esp
mov [ctx.Ebp], ebp

call _1
// This address will appear in kd as crash address:
_1: pop eax
mov [ctx.Eip], eax

pushfd
pop eax
mov [ctx.EFlags], eax

// Debug registers
__emit 0x0F
__emit 0x21
__emit 0xC0 ; mov eax, dr0
mov [ctx.Dr0], eax
__emit 0x0F
__emit 0x21
__emit 0xC8 ; mov eax, dr1
mov [ctx.Dr1], eax
__emit 0x0F
__emit 0x21
__emit 0xD0 ; mov eax, dr2
mov [ctx.Dr2], eax
__emit 0x0F
__emit 0x21
__emit 0xD8 ; mov eax, dr3
mov [ctx.Dr3], eax
__emit 0x0F
__emit 0x21
__emit 0xF0 ; mov eax, dr6
mov [ctx.Dr6], eax
__emit 0x0F
__emit 0x21
__emit 0xF8 ; mov eax, dr7
mov [ctx.Dr7], eax

// Segment registers
push cs
pop eax
mov [ctx.SegCs], eax
xor eax,eax
mov ax, ss
mov [ctx.SegSs], eax
mov ax, ds
mov [ctx.SegDs], eax
mov ax, es
mov [ctx.SegEs], eax
mov ax, fs
mov [ctx.SegFs], eax
mov ax, gs
mov [ctx.SegGs], eax
}

// Get KeCapturePersistentThreadState address
RtlInitUnicodeString( &uKeCapturePersistentThreadState, L"KeCapturePersistentThreadState" );
KeCapturePersistentThreadState = MmGetSystemRoutineAddress( &uKeCapturePersistentThreadState );

// Initialize dump header
hdr = (PDUMP_HEADER) HeaderPage;
hdr->ValidDump = 'PMUD';
hdr->MinorVersion = (USHORT) *NtBuildNumber;
hdr->MajorVersion = (USHORT) 0xF; // checked/free, в идеале нужно брать инфу из реестра; если посмотреть код в приложении к статье - там так и сделано
hdr->DirectoryTableBase = CR3();

//
// Capture KdDebuggerDataBlock
//

__try {
hdr->KdDebuggerDataBlock = *(PVOID*)((ULONG)KeCapturePersistentThreadState + *(ULONG*)((ULONG)KeCapturePersistentThreadState + 0xC )+ 0x11);

} __except( (pei=GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER ) {
ULONG i;

DbgPrint("An exception occurred while trying to get KdDebuggerDataBlock address:\n");
DbgPrint("Exception code: 0x%08x\n", pei->ExceptionRecord->ExceptionCode);
DbgPrint("Number of arguments: 0x%08x\n", pei->ExceptionRecord->NumberParameters);

for( i = 0; i < pei->ExceptionRecord->NumberParameters; i++ ) {
DbgPrint("Argument[%d]: 0x%08x\n", i, pei->ExceptionRecord->ExceptionInformation[i]);
}

return FALSE;
}
hdr->MachineImageType = 0x14c;
hdr->NumberProcessors = 1;
hdr->BugCheckCode = KMODE_EXCEPTION_NOT_HANDLED;
hdr->BugCheckParameter1 = STATUS_BREAKPOINT;
hdr->BugCheckParameter2 = ctx.Eip;
hdr->BugCheckParameter3 = 0;
hdr->BugCheckParameter4 = 0;
hdr->PaeEnabled = (CR4() & PAE_ENABLED) ? TRUE : FALSE;

KdDebuggerDataBlock = (PKD_DEBUGGER_DATA_BLOCK) hdr->KdDebuggerDataBlock;

// Check KdDebuggerDataBlock
if( KdDebuggerDataBlock->ValidBlock != 'GBDK' || KdDebuggerDataBlock->Size != sizeof(*KdDebuggerDataBlock) )
{
// Invalid debugger data block
DbgPrint( "KdDebuggerDataBlock is not valid.\nSignature = 0x%08x (should be 0x%08x)\nSize = 0x%08x (should be 0x%08x)\n",
KdDebuggerDataBlock->ValidBlock, 'GBDK',
KdDebuggerDataBlock->Size, sizeof(*KdDebuggerDataBlock) );
return FALSE;
}

DbgPrint("Got valid KdDebuggerDataBlock=0x%08x\n", KdDebuggerDataBlock);

hdr->PfnDataBase = (PULONG) KdDebuggerDataBlock->MmPfnDatabase.VirtualAddress;
hdr->PsLoadedModuleList = (PLIST_ENTRY) KdDebuggerDataBlock->PsLoadedModuleList.VirtualAddress;
hdr->PsActiveProcessHead = (PLIST_ENTRY) KdDebuggerDataBlock->PsActiveProcessHead.VirtualAddress;

DbgPrint("PfnDataBase = 0x%08x\n", hdr->PfnDataBase);
DbgPrint("PsLoadedModuleList = 0x%08x\n", hdr->PsLoadedModuleList);
DbgPrint("PsActiveProcessHead = 0x%08x\n", hdr->PsActiveProcessHead);

blocks = (ULONG*)(ULONG_PTR)HeaderPage;

//
// Get physical memory descriptor
//

MmPhysicalMemoryBlock = *(KdDebuggerDataBlock->MmPhysicalMemoryBlock.VirtualAddress);

DbgPrint("MmPhysicalMemoryBlock = 0x%08x\n", MmPhysicalMemoryBlock);

if( MmPhysicalMemoryBlock->NumberOfRuns == 'EGAP' ) {
RtlCopyMemory( &blocks[ DH_PHYSICAL_MEMORY_BLOCK ],
MmPhysicalMemoryBlock,
sizeof(PHYSICAL_MEMORY_DESCRIPTOR)
);
} else {
RtlCopyMemory( &blocks[ DH_PHYSICAL_MEMORY_BLOCK ],
MmPhysicalMemoryBlock,
sizeof(PHYSICAL_MEMORY_DESCRIPTOR) - sizeof(PHYSICAL_MEMORY_RUN) +
sizeof(PHYSICAL_MEMORY_RUN)*MmPhysicalMemoryBlock->NumberOfRuns
);
}

//
// Save context record
//

RtlCopyMemory( &blocks[ DH_CONTEXT_RECORD ],
&ctx,
sizeof(CONTEXT)
);
DbgPrint("Context record saved.\n");
DbgPrint("EAX=%08x EBX=%08x ECX=%08x EDX=%08x\n", ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx);
DbgPrint("ESI=%08x EDI=%08x EBP=%08x ESP=%08x\n", ctx.Esi, ctx.Edi, ctx.Ebp, ctx.Esp);
DbgPrint("EIP=%08x CS=%08x FS=%08x DS=%08x\n", ctx.Eip, ctx.SegCs, ctx.SegFs, ctx.SegDs);

//
// Create & store exception record
//

exception.ExceptionCode = STATUS_BREAKPOINT;
exception.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
exception.ExceptionRecord = NULL;
exception.ExceptionAddress = (PVOID) ctx.Eip;
exception.NumberParameters = 0;

RtlCopyMemory( &blocks[ DH_EXCEPTION_RECORD ],
&exception,
sizeof(EXCEPTION_RECORD)
);

//
// Initialize dump type & size
//

blocks[ DH_DUMP_TYPE ] = DUMP_TYPE_COMPLETE;

((LARGE_INTEGER*)&blocks[DH_REQUIRED_DUMP_SPACE])->QuadPart = ( MmPhysicalMemoryBlock->NumberOfPages << 12 ) + 0x1000;

DbgPrint("Header page initialized OK\n");

return TRUE;
}

На этом всё. В приложении к статье можно найти более новую версию gr8lkd, которую я еще никуда не выкладывал, исходники AnalyseCrashDump и исходники gendump драйвера.

http://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)

_Great_
07.09.2007, 19:59
Лог анализа сгенеренного дампа в WinDbg:

Microsoft (R) Windows Debugger Version 6.6.0007.5
Copyright (c) Microsoft Corporation. All rights reserved.


Loading Dump File [D:\CRASHDMP.DMP]
Kernel Complete Dump File: Full address space is available

Symbol search path is: D:\Symbols
Executable search path is:
Windows XP Kernel Version 2600 (Service Pack 2) UP Free x86 compatible
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 2600.xpsp_sp2_rtm.040803-2158
Kernel base = 0x804d7000 PsLoadedModuleList = 0x805531a0
Debug session time: Sun Feb 13 06:34:57.897 17420 (GMT+4)
System Uptime: 3 days 4:14:47.773
WARNING: Process directory table base 00373000 doesn't match CR3 05A4E5E0
WARNING: Process directory table base 00373000 doesn't match CR3 05A4E5E0
Loading Kernel Symbols
.................................................. .................................................. ..................................
Loading User Symbols

Loading unloaded module list
..............
*** ERROR: Module load completed but symbols could not be loaded for LiveKdD.SYS
************************************************** *****************************
* *
* Bugcheck Analysis *
* *
************************************************** *****************************

Use !analyze -v to get detailed debugging information.

BugCheck 1E, {80000003, f8beb4d7, 0, 0}

Probably caused by : gendump.sys ( gendump!InitializeDumpHeader+77 )

Followup: MachineOwner
---------

kd> !analyze -v
************************************************** *****************************
* *
* Bugcheck Analysis *
* *
************************************************** *****************************

KMODE_EXCEPTION_NOT_HANDLED (1e)
This is a very common bugcheck. Usually the exception address pinpoints
the driver/function that caused the problem. Always note this address
as well as the link date of the driver/image that contains this address.
Arguments:
Arg1: 80000003, The exception code that was not handled
Arg2: f8beb4d7, The address that the exception occurred at
Arg3: 00000000, Parameter 0 of the exception
Arg4: 00000000, Parameter 1 of the exception

Debugging Details:
------------------


EXCEPTION_CODE: (HRESULT) 0x80000003 (2147483651) - One or more arguments are invalid

FAULTING_IP:
gendump!InitializeDumpHeader+77 [d:\progs\driverdev\gendump\gendump.cpp @ 171]
f8beb4d7 58 pop eax

EXCEPTION_PARAMETER1: 00000000

EXCEPTION_PARAMETER2: 00000000

DEFAULT_BUCKET_ID: DRIVER_FAULT

BUGCHECK_STR: 0x1E

PROCESS_NAME: System

LAST_CONTROL_TRANSFER: from 00000000 to f8bc02d1

STACK_TEXT:
f188ac50 00000000 00000000 00000000 00000001 LiveKdD+0x12d1


STACK_COMMAND: .bugcheck ; kb

FOLLOWUP_IP:
gendump!InitializeDumpHeader+77 [d:\progs\driverdev\gendump\gendump.cpp @ 171]
f8beb4d7 58 pop eax

FAULTING_SOURCE_CODE:
167: mov [ctx.Ebp], ebp
168:
169: call _1
170: // This address will appear in kd as crash address:
> 171: _1: pop eax
172: mov [ctx.Eip], eax
173:
174: pushfd
175: pop eax
176: mov [ctx.EFlags], eax


SYMBOL_NAME: gendump!InitializeDumpHeader+77

FOLLOWUP_NAME: MachineOwner

MODULE_NAME: gendump

IMAGE_NAME: gendump.sys

DEBUG_FLR_IMAGE_TIMESTAMP: 45004080

FAILURE_BUCKET_ID: 0x1E_gendump!InitializeDumpHeader+77

BUCKET_ID: 0x1E_gendump!InitializeDumpHeader+77

Followup: MachineOwner
---------

_Great_
07.09.2007, 20:12
Поправочка:
Например на моей системе (512МБ физической памяти) они выглядят так:
Цитата:
PPHYSICAL_MEMORY_DESCRIPTOR:
NumberOfRuns = 0x00000003 NumberOfPages = 0x0001ff8d
PPHYSICAL_MEMORY_RUN[0]: BasePage = 0x00000001 PageCount = 0x0000009e
PPHYSICAL_MEMORY_RUN[1]: BasePage = 0x00000100 PageCount = 0x00000eff
PPHYSICAL_MEMORY_RUN[2]: BasePage = 0x00001000 PageCount = 0x0001eff0

То есть у меня доступны физические страницы с pfn'ами (номерами, другими словами) от 1 до 0x9e, от 0x100 до 0xeff и от 0x1000 до 0x1eff0.
Тут я, мягко говоря, нагнал. Надо так:

То есть у меня доступны физические страницы с pfn'ами (номерами, другими словами) от 1 до 0x9f, от 0x100 до 0xfff и от 0x1000 до 0x1fff0.

z01b
13.09.2007, 17:00
Тулза полезная ) +1
P.S. Gr8t вылезай из 0 колица )))

_Great_
11.10.2007, 06:16
Эта статья на васме

клац: http://www.wasm.ru/article.php?article=dmp_format