Описание формата DMP — Архив WASM.RU

Все статьи

Описание формата DMP — Архив WASM.RU

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.

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 ) );
}

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

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;

Это же очень удобно! Достаточно получить лишь один неэкспортируемый адрес 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 драйвера.

Файлы к статье:

2002-2013 (c) wasm.ru