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.
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 ) ); } Всё готово. Напишем небольшой демонстрационный код для анализа крешдампа:
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;
Это же очень удобно! Достаточно получить лишь один неэкспортируемый адрес 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)
Поправочка: Тут я, мягко говоря, нагнал. Надо так: То есть у меня доступны физические страницы с pfn'ами (номерами, другими словами) от 1 до 0x9f, от 0x100 до 0xfff и от 0x1000 до 0x1fff0.