Description of DMP Format — Архив WASM.RU

Все статьи

Description of DMP Format — Архив WASM.RU

I. Structure of crash dump

Not so long ago I've released my counterpart of livekd, named gr8lkd, which is filter driver for file system. It attaches to disk C: and creates here virtual crash dump to intercept and process all requests to it.

Now I've decided to describe the format of Windows crash dumps - .DMP. On the internet you can find only one page in English (original article is in Russian) with incomplete and buggy description.

I will try to describe this format as complete as possible. Everything underwritten is based on my own research of core's dissassembler listing - if to be more precise, function IoWriteCrashDump and those ones associated with it.

Let's begin...

First, let's talk about crash dumps and how they are generated. When something bad happens in the core, for example freeing of already freed memory, and ExFreePool (function for pool freeing) founds it, it is insecure to continue to work so it is decided to make emergency system shudown. ExFreePool calls KeBuCheckEx (KeBugCheck2, KeBugCheck3 - depending on Windows version, in first case it is exported documented function which shows blue screen, in second and third ones - inner function which do the same) which shows blue screen BAD_POOL_CALLER and calls IoWriteCrashDump to write a crash dump. The crash dump is writed in the disk sectors which are occupied by swap file (before this FSCTL_GET_RETRIEVAL_POINTERS is called to get swap file map (карта размещения)) because at this stage calling of file system driver is insecure - maybe it is itseft the reason of fail, so special driver is used for writing dump to swap file. At the next boot dump will be get from swap file and written to the path specified in the registry.

As is known, there are three types of Windows crash dumps:

  1. "Minidump", or "triage dump" as it is called in the core. Usually such dumps are small (about 64k) and they contain least of information about system when it crashed. This type of dumps is set by default but unfortunately it is not fit for usual system core developments, so we move ahead and look at the next type:
  2. "Kernel Memory Dump", or "summary dump" as it called in the core. Such dumps include only core's memory pages, and not all but only those which are neccessary for crash ananalysis. This dump type is fit for usual analysis of system crash's reasons because it includes code and data of the core, all loaded drivers and system structures. Swap file should be big enough to contain all of this. It is hard to predict it's size, at my maching it is usually about 1/8 of physical memory.
  3. "Full Memory Dump"
  4. . This dump includes all pages of physical memory. Swap file should be big enough to contain it.

First we will examine the structure which is common for all dumps - dump header page. It is the first page of dump file, first 0x1000 (4096) bytes. The structure of this page is - first, all page is filled with "PAGE" (bytes 'EGAP') and then data is written to required offsets.

The structure which identify the crash dump is first:

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;
  • Signature - This field is not filled and leaved with 'EGAP' bytes.
  • ValidDump The dump signature - "DUMP" ('PMUD')
  • MajorVersion - 0x0F if Free build 0x0C if Checked build
  • MinorVersion - Build number системы System's build number
  • DirectoryTableBase - CR3 value at the moment of system's crash - physical address of page directory
  • PfnDatabase - MmPfnDatabase virtual address - page frame (number? - not sure here [translator]) database (PFN)
  • PsLoadedModuleList - PsLoadedModuleList virtual address - list of loaded modules
  • PsActiveProcessHead - PsActiveProcessHead virtual address - list of active processes
  • MachineImageType - on x86 it is equal to 0x14C, you can check other constants in the winnt.h.
  • NumberProcessors - number of processor (from KeNumberProcessors)
  • BugCheckCode - Stop code of error
  • BugCheckParameter1, BugCheckParameter2, BugCheckParameter3, BugCheckParameter4 - Error's parameters
  • VersionUser - Version of something, I did not find out what exactly. As a rule, first byte written as 0, others are not filled.
  • PaeEnabled - =1 if support of Physical Address Extensions (PAE) is turned on, =0 if turned off
  • KdDebuggerDataBlock - Virtual address of very important structure KdDebuggerDataBlock, which will be described later.

Well, this is beginning of the dump. Some data blocks are written to other offsets, so first let's define several constants:

// 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   // (field is not required)
#define DH_PRODUCT_TYPE                 997   // (field is not required, value is taken from KUSER_SHARED_DATA)
#define DH_SUITE_MASK                   998   // (field is not required, value is taken from KUSER_SHARED_DATA)
#define DH_INTERRUPT_TIME               1006  // (field is not required, value is taken from KUSER_SHARED_DATA)
#define DH_SYSTEM_TIME                  1008  // (field is not required, value is taken from KUSER_SHARED_DATA)

First page is treated as DWORD array, and this constants define indexes of this array where data is contained. So, in other words using C:

ULONG* blocks = (ULONG*) HeaderPage;
//&blocks[DH_xxx] - address of corresponding block

Let's examine this block one-by-one:

DH_PHYSICAL_MEMORY_BLOCK - Here is the physical memory descriptor, the structure PHYSICAL_MEMORY_DESCRIPTOR:

typedef unsigned long PFN_NUMBER;

typedef struct _PHYSICAL_MEMORY_RUN {
    PFN_NUMBER BasePage;
    PFN_NUMBER PageCount;

    ULONG NumberOfRuns;
    PFN_NUMBER NumberOfPages;

It, as well as Run arrays, describes available physical memory. Usually system has 3-4 Runs - solid sets of physical pages. For example, on my system (512Mb of physical memory) they are showed as:

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

I.e. physical pages with PFNs (numbers in other words) from 1 to 0x9e, from 0x100 to 0xeff and from 0x1000 to 0x1eff0.

Size of this block is equal to sizeof(PHYSICAL_MEMORY_DESCRIPTOR) - sizeof(PHYSICAL_MEMORY_RUN) + sizeof(PHYSICAL_MEMORY_RUN) * NumberOfRuns

  • DH_CONTEXT_RECORD The structure CONTEXT is stored here which contains context of thread which triggered error. I think no additional explanations are needed.
  • DH_EXCEPTION_RECORD As in previous, the structure EXCEPTION_RECORD is stored here which contains information (if available) about exception.
  • DH_DUMP_TYPE Here is stored the dword which indicates the dump type - triage, summary of full dump. The values of this field are:

    // Dump types
    #define DUMP_TYPE_TRIAGE				4
    #define DUMP_TYPE_SUMMARY				2
    #define DUMP_TYPE_FULL					1
  • DH_CALLBACKS_STATUS - Here is stored NTSTATUS returned by BugcheckCallbacks.
  • DH_PRODUCT_TYPE - Here is stored the system type described by enumeration:

    enum _NT_PRODUCT_TYPE {
      NtProductWinNt = 0x1,
      NtProductLanManNt = 0x2,
      NtProductServer = 0x3,
  • DH_SUITE_MASK - Here is stored the field KUSER_SHARED_DATA->SuiteMask and if honestly I don't know what it means.
  • DH_REQUIRED_DUMP_SPACE - Here is stored LARGE_INTEGER which contains full size of dump in bytes.
  • DH_INTERRUPT_TIME - I don't know it's purpose but it is taken from KUSER_SHARED_DATA->InterruptTime
  • DH_SYSTEM_TIME - This field is taken from KUSER_SHARED_DATA->SystemTime and, as far as I know, contains current uptime

Well, that's all about first dump page. Following structure of dump is heavily depends on it's type:

  1. Minidump (triage dump) - this dump type is not very interesting, so I will only provide partial structure TRIAGE_DUMP_HEADER without many comments (I have not finished it's analysis). Just note that it constains offsets of some structures which are followed then in row format.

    // 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
  2. Kernel memory dump (Summary dump) - second page of dump is beginning with the next structure:

    // 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
    • Signature1, Signature2 - This field contains "SDMP" signature, if to be more precise, the structure is filled with those at the beginning and then this field is leave as is.
    • ValidDump - This field contains "DUMP" signature.
    • HeaderSize - Full size of dump header.
    • BitmapSize - Size of the bitmap which follows right after this structure - we will talk about it later.
    • Pages Number of core's memory pages included in the dump (matches to the number of all set bits in bitmap).

    The bitmap follows right after this structure, the number of bits in it equal to number of physical pages in the system, and setting of bit indicates if corresponding page included in the dump or not. Next structure is designed for working with such bitmap:

    typedef struct _RTL_BITMAP {
        ULONG SizeOfBitMap;                     // Number of bits in bit map
        PULONG Buffer;                          // Pointer to the bit map itself
    и функции RtlInitializeBitMap, RtlClearAllBits, RtlSetAllBits, RtlFindClearBits, RtlFindSetBits, RtlCheckBit и другие.

    In the second part of this article I will show example of simple crash dump analyzer and how to work with this map.

  3. Full dump - After first page, dumps of this type contain every physical from all memory runs beginning with 0 and ending with NumberOfRuns-1.

II. Writing of dump analyzer

Now we have enough information to write simple crash dump analyzer.

In our analyzer loaded dump will be shown with the next structure:

// Mapped crash dump descriptor
struct MappedCrashDump {
	HANDLE hFile;
	HANDLE hMapping;
	union {
		PBYTE lpMapping;
		PDUMP_HEADER DumpHeader;
		ULONG* DataBlocks;
	ULONG *DumpType;
	ULONG *DumpFlags;
	PCONTEXT Context;
	union {
		// Summary dump type
		struct {
			PSUMMARY_DUMP_HEADER pSummaryDumpHeader;
			RTL_BITMAP KernelMemoryMap;

		// Triage dump type
		PTRIAGE_DUMP_HEADER pTriageDumpHeader;

The plan is: we open a file, create map object and map first 10 pages of dump file. Other will be mapped on demand. To implement this, we will write function MapDumpPage which will map part of file if it is not mapped: already.

// 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 );
	PVOID Ret = 0;

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

	if( mbi.State != MEM_COMMIT ) {
		Ret = MapViewOfFileEx(	hMapping,
		if( Ret == Base ) {
			return DesiredAddress;

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

	return DesiredAddress;

Next we implement function which will get page from summary dump using it's physical address at the time of crash:

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 ) ) {

	return NULL;

To find out if a page is present in the dump and to get it's number, special function was implemented. After we have this information, page is mapped from disk to memory (if not mapped already).

Next we have to define counterpart function for full dump. First, we need to learn how to convert PFN of memory page to PFN of disk page and vice versa. Two following functions will do the job:

	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 )

		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;

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

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


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

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

	return iDumpPFN;

Next we can define a function to get a page from the dump using it's physical address at the time of the crash, and a general function to get the dump type, and call one of the two functions to get it's address (which is more appropriate):

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;

Now we have to learn how to translate virtual adresses to physical ones. Then we will be able to get data from any virtual address of the dump. First, let's define directory structures PDPE, PDE and PTE. We will need them later.

// 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
	DWORD  Offset:12;
	DWORD  Table:10;
	DWORD  Directory:10;

// Page Directory Entry in PAE mode


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)
	DWORD  Offset:12;
	DWORD  Table:9;
	DWORD  Directory:9;
	DWORD  DirectoryPointer:2;

Here is two series of structures defined: with PAE turned off and PAE turned on. I want to remind you, addressing became three-level in PAE mode. CR3 register no more points to PDE, now it points to array of "Page Directory Pointer Entries" or PDPE. There shoud be 4 of them. Every PDPE points to PDE array, and PDE points to PTE. Composition of virtual address also changed a little. One bit of PDE and PTE was cut in favour of two-bit field of PDPE number.

Taking this into account, the function to translate adresses is:

// 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

		*(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;
		// PAE disabled. Use 2-level addressing system: PDE->PTE->Page
		*(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 ) );

Everything is ready. Now we will code a small example for crash dump analyzing.

	ULONG	Value;
	LPSTR	Desc;
#define DEFINE_STRING(x) { x, #x }
#define TABLE_END { 0, 0 }

CONST_DESCRIPTION MachineTypes[] = {


// Bugcheck descriptions
#include "D:\Progs\driverdev\bcdesc.h"    // codes of bsods, I do not provide
										  // this file, there is BugCheckDescf
										  // function which gets bsod name by 
										  // it's number

// Get constant's name by it's value
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 = 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, 

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

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

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

		// Context record:
		CrashDump.Context = (PCONTEXT)( &block[DH_CONTEXT_RECORD] );
		printf(	"Context record:\nEip = 0x%08x   ESP = 0x%08x   EBP = 0x%08x\n",
				CrashDump.Context->Ebp );
		printf("EAX=%08x EBX=%08x ECX=%08x EDX=%08x ESI=%08x EDI=%08x\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->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", 
							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");
		if( CrashDump.lpMapping )
			UnmapViewOfFile( CrashDump.lpMapping );

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

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


	return 0;

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

	return 0;

Path to my crash dump used by my gr9lkd utility is already defined.

Output will be something like following:

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)

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. Creating of own dump

Here is the most interesting thing - creating of own dump file.

First, we will need the undocumented structure KdDebuggerDataBlock, because it will be very helpful for this task. So, it contains following items:

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>  MmSessionBase;
	PFUNC<PVOID>  MmSessionSize;
	PFUNC<PVOID>  Unknown7;


It is very convenient! We need only to get one non-exported address of KdDebuggerDataBlock, and every other required adresses are in our reach. Field "ValidBlock" should contain 'GBDK' signature, and Size should be equal to sizeof(KD_DEBUGGER_DATA_BLOCK).

Getting of KdDebuggerDataBlock address is possible via exported symbol KdDebuggerDataBlock = *(PVOID*)((ULONG)KeCapturePersistentThreadState + *(ULONG*)((ULONG)KeCapturePersistentThreadState + 0xC )+ 0x11);

Not very nice, but powerful: we get a lot of non-exported addresses of innet structures. We will need it to fill a dump header.

Now, as we know everything we need, I will show part of code for filling the first dump page.

	IN	PBYTE HeaderPage
	ULONG* blocks;
	PVOID KeCapturePersistentThreadState;
	UNICODE_STRING uKeCapturePersistentThreadState;
	// Get context
		// 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

		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, ideally we should get
										 // info from registry; if you look at 
										 // at code in the article attachment -
										 // it is how it was done there
	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->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 ],
	} else {
		RtlCopyMemory(	&blocks[ DH_PHYSICAL_MEMORY_BLOCK ],

	// Save context record

	RtlCopyMemory(	&blocks[ DH_CONTEXT_RECORD ],
	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 ],

	// Initialize dump type & size


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

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

	return TRUE;

That's all. In the article's attachments you can find more fresh version of gr8lkd and sources of AnalyseCrashDump and gendump driver.


2002-2013 (c)