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

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by _Great_, 7 Sep 2007.

  1. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139
    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'), а потом по нужным смещениям записываются данные.
    Сперва идет структура, идентифицирующая сам крешдамп:
    Code:
    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, описание которой я дам позже.


    Вот так нехитро устроено начало дампа. Дальше по определенным смещениям запихнуты еще несколько блоков данных, для начала определим некоторые константы:

    Code:
    // 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-ов и эти константы определяют индексы в этом массиве, где содержатся данные. То есть, другими словами, на си:
    Code:
    ULONG* blocks = (ULONG*) HeaderPage;
    //&blocks[DH_xxx] - адрес соответствующего блока
    Рассмотрим теперь эти блоки по отдельности:

    DH_PHYSICAL_MEMORY_BLOCK
    Тут содержится описатель физической памяти, структура PHYSICAL_MEMORY_DESCRIPTOR:
    Code:
    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МБ физической памяти) они выглядят так:
    То есть у меня доступны физические страницы с 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. Соответствующие значения этого поля:
    Code:
    // 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
    Тут сохраняется тип системы, описываемый перечислением
    Code:
    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 без особых комментариев (анализ ее я еще не закончил), отметив лишь, что она содержит оффсеты некоторых структур, которые следуют дальше в сыром виде:
    Code:
    // 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)
    Вторая страница дампа начинается со структуры
    Code:
    // 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
    Количество страниц памяти ядра, включенных в дамп (совпадает с числом всех установленных битов битовой карты).

    Непосредственно за этой структурой следует битовая карта, число бит в ней равно числу физических страниц в системе, а установленные соответсвующие биты сообщают, включена ли конкретная страница в дамп, или нет. Для работы с такой битовой картой предназначены структура:
    Code:
    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.
     
    10 people like this.
  2. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139
    II. Написание анализатора дампа

    Теперь у нас есть достаточно знаний, чтобы написать простейший анализатор крешдампа.
    Загруженный дамп в нашем анализаторе будет представлен структурой:
    Code:
    // 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, которая будет проецировать вид файла, если он еще не спроецирован:

    Code:
    //
    // 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-дампа по её физическому адресу в момент краха:
    Code:
    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 страницы на диске, и наоборот. С этим справятся следующие две функции:
    Code:
    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;
    }
    Дальше можно описать функцию, получающую страницу из дампа по ее физическому адресу на момент краха и общую функцию, получающую тип дампа и вызывающую нужную из двух функций получения адреса:
    Code:
    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, которые нам могут понадобиться:
    Code:
    //
    // 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.

    С учетом всего этого, функция трансляции адресов:
    Code:
    //
    // 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 ) );
    }
    Всё готово. Напишем небольшой демонстрационный код для анализа крешдампа:
     
  3. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139
    Code:
    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.

    Вывод примерно следующий:
    III. Создание собственного дампа.

    Мы подошли к самому интересному моменту, а именно к созданию собственного файда дампа.
    Сперва стоит описать недокументированную структуру KdDebuggerDataBlock, поскольку она нам очень поможет при создании дампа. Итак, по адресу KdDebuggerDataBlock лежит следующее:
    Code:
    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;
     
  4. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139
    Это же очень удобно! Достаточно получить лишь один неэкспортируемый адрес KdDebuggerDataBlock, как все остальные нужные адреса как на ладони.
    Поле ValidBlock должно содержать сигнатуру 'GBDK', а Size должно точно равняться sizeof(KD_DEBUGGER_DATA_BLOCK).

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

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

    Ну а теперь, поскольку мы всё знаем, приведу отрывочный код заполнения заглавной страницы дампа:
    Code:
    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)
     
    1 person likes this.
  5. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139
    Лог анализа сгенеренного дампа в WinDbg:

     
    1 person likes this.
  6. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139
    Поправочка:
    Тут я, мягко говоря, нагнал. Надо так:

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

    z01b Муджахид

    Joined:
    5 Jan 2007
    Messages:
    494
    Likes Received:
    382
    Reputations:
    22
    Тулза полезная ) +1
    P.S. Gr8t вылезай из 0 колица )))
     
    3 people like this.
  8. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139