/*++ Copyright (c) 1999-2002 Microsoft Corporation Module Name: gen.c Abstract: Generic routins for minidump that work on both NT and Win9x. Author: Matthew D Hendel (math) 10-Sep-1999 Revision History: --*/ #include "pch.h" #include #include "nt4.h" #include "win.h" #include "ntx.h" #include "wce.h" #include "impl.h" #define REASONABLE_NB11_RECORD_SIZE (10 * KBYTE) #define REASONABLE_MISC_RECORD_SIZE (10 * KBYTE) ULONG g_MiniDumpStatus; #if defined (i386) // // For FPO frames on x86 we access bytes outside of the ESP - StackBase range. // This variable determines how many extra bytes we need to add for this // case. // #define X86_STACK_FRAME_EXTRA_FPO_BYTES 4 #endif LPVOID AllocMemory( SIZE_T Size ) { LPVOID Mem = HeapAlloc ( GetProcessHeap (), HEAP_ZERO_MEMORY, Size ); if (!Mem) { // Handle marking the no-memory state for all allocations. GenAccumulateStatus(MDSTATUS_OUT_OF_MEMORY); } return Mem; } VOID FreeMemory( IN LPVOID Memory ) { if ( Memory ) { HeapFree ( GetProcessHeap (), 0, Memory ); } return; } PVOID ReAllocMemory( IN LPVOID Memory, IN SIZE_T Size ) { LPVOID Mem = HeapReAlloc ( GetProcessHeap (), HEAP_ZERO_MEMORY, Memory, Size); if (!Mem) { // Handle marking the no-memory state for all allocations. GenAccumulateStatus(MDSTATUS_OUT_OF_MEMORY); } return Mem; } BOOL ProcessThread32Next( HANDLE hSnapshot, DWORD dwProcessID, THREADENTRY32 * ThreadInfo ) { BOOL succ; // // NB: Toolhelp says nothing about the order of the threads will be // returned in (i.e., if they are grouped by process or not). If they // are groupled by process -- which they emperically seem to be -- there // is a more efficient algorithm than simple brute force. // do { ThreadInfo->dwSize = sizeof (*ThreadInfo); succ = Thread32Next (hSnapshot, ThreadInfo); } while (succ && ThreadInfo->th32OwnerProcessID != dwProcessID); return succ; } BOOL ProcessThread32First( HANDLE hSnapshot, DWORD dwProcessID, THREADENTRY32 * ThreadInfo ) { BOOL succ; ThreadInfo->dwSize = sizeof (*ThreadInfo); succ = Thread32First (hSnapshot, ThreadInfo); if (succ && ThreadInfo->th32OwnerProcessID != dwProcessID) { succ = ProcessThread32Next (hSnapshot, dwProcessID, ThreadInfo); } return succ; } ULONG GenGetAccumulatedStatus( void ) { return g_MiniDumpStatus; } void GenAccumulateStatus( IN ULONG Status ) { g_MiniDumpStatus |= Status; } void GenClearAccumulatedStatus( void ) { g_MiniDumpStatus = 0; } VOID GenGetDefaultWriteFlags( IN ULONG DumpType, OUT PULONG ModuleWriteFlags, OUT PULONG ThreadWriteFlags ) { *ModuleWriteFlags = ModuleWriteModule | ModuleWriteMiscRecord | ModuleWriteCvRecord; if (DumpType & MiniDumpWithDataSegs) { *ModuleWriteFlags |= ModuleWriteDataSeg; } *ThreadWriteFlags = ThreadWriteThread | ThreadWriteContext; if (!(DumpType & MiniDumpWithFullMemory)) { *ThreadWriteFlags |= ThreadWriteStack | ThreadWriteInstructionWindow; #if defined (DUMP_BACKING_STORE) *ThreadWriteFlags |= ThreadWriteBackingStore; #endif } if (DumpType & MiniDumpWithProcessThreadData) { *ThreadWriteFlags |= ThreadWriteThreadData; } } BOOL GenExecuteIncludeThreadCallback( IN HANDLE hProcess, IN DWORD ProcessId, IN ULONG DumpType, IN ULONG ThreadId, IN MINIDUMP_CALLBACK_ROUTINE CallbackRoutine, IN PVOID CallbackParam, OUT PULONG WriteFlags ) { BOOL Succ; MINIDUMP_CALLBACK_INPUT CallbackInput; MINIDUMP_CALLBACK_OUTPUT CallbackOutput; // Initialize the default write flags. GenGetDefaultWriteFlags(DumpType, &CallbackOutput.ModuleWriteFlags, WriteFlags); // // If there are no callbacks to call, then we are done. // if ( CallbackRoutine == NULL ) { return TRUE; } CallbackInput.ProcessHandle = hProcess; CallbackInput.ProcessId = ProcessId; CallbackInput.CallbackType = IncludeThreadCallback; CallbackInput.IncludeThread.ThreadId = ThreadId; CallbackOutput.ThreadWriteFlags = *WriteFlags; Succ = CallbackRoutine (CallbackParam, &CallbackInput, &CallbackOutput); // // If the callback returned FALSE, quit now. // if ( !Succ ) { return FALSE; } // Limit the flags that can be added. *WriteFlags &= CallbackOutput.ThreadWriteFlags; return TRUE; } BOOL GenExecuteIncludeModuleCallback( IN HANDLE hProcess, IN DWORD ProcessId, IN ULONG DumpType, IN ULONG64 BaseOfImage, IN MINIDUMP_CALLBACK_ROUTINE CallbackRoutine, IN PVOID CallbackParam, OUT PULONG WriteFlags ) { BOOL Succ; MINIDUMP_CALLBACK_INPUT CallbackInput; MINIDUMP_CALLBACK_OUTPUT CallbackOutput; // Initialize the default write flags. GenGetDefaultWriteFlags(DumpType, WriteFlags, &CallbackOutput.ThreadWriteFlags); // // If there are no callbacks to call, then we are done. // if ( CallbackRoutine == NULL ) { return TRUE; } CallbackInput.ProcessHandle = hProcess; CallbackInput.ProcessId = ProcessId; CallbackInput.CallbackType = IncludeModuleCallback; CallbackInput.IncludeModule.BaseOfImage = BaseOfImage; CallbackOutput.ModuleWriteFlags = *WriteFlags; Succ = CallbackRoutine (CallbackParam, &CallbackInput, &CallbackOutput); // // If the callback returned FALSE, quit now. // if ( !Succ ) { return FALSE; } // Limit the flags that can be added. *WriteFlags = (*WriteFlags | ModuleReferencedByMemory) & CallbackOutput.ModuleWriteFlags; return TRUE; } BOOL GenGetVersionInfo( IN PWSTR FullPath, OUT VS_FIXEDFILEINFO * VersionInfo ) /*++ Routine Description: Get the VS_FIXEDFILEINFO for the module described by FullPath. Arguments: FullPath - FullPath to the module. VersionInfo - Buffer to copy the Version information. Return Values: TRUE - Success. FALSE - Failure. --*/ { BOOL Succ; ULONG unused; ULONG Size; UINT VerSize; PVOID VersionBlock; PVOID VersionData; CHAR FullPathA [ MAX_PATH + 10 ]; BOOL UseAnsi = FALSE; // // Get the version information. // Size = GetFileVersionInfoSizeW (FullPath, &unused); if (Size == 0 && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) { // We're on an OS that doesn't support Unicode // file operations. Convert to ANSI and see if // that helps. if (WideCharToMultiByte (CP_ACP, 0, FullPath, -1, FullPathA, sizeof (FullPathA), 0, 0 ) > 0) { Size = GetFileVersionInfoSizeA(FullPathA, &unused); UseAnsi = TRUE; } } if (Size) { VersionBlock = AllocMemory (Size); if (VersionBlock) { if (UseAnsi) { Succ = GetFileVersionInfoA(FullPathA, 0, Size, VersionBlock); } else { Succ = GetFileVersionInfoW(FullPath, 0, Size, VersionBlock); } if (Succ) { // // Get the VS_FIXEDFILEINFO from the image. // VerSize = 0; // ?? sizeof (Module->VersionInfo); Succ = VerQueryValue(VersionBlock, "\\", &VersionData, &VerSize); if ( Succ && (VerSize == sizeof (VS_FIXEDFILEINFO)) ) { CopyMemory (VersionInfo, VersionData, sizeof (*VersionInfo)); FreeMemory(VersionBlock); return TRUE; } } FreeMemory (VersionBlock); } } // Files don't have to have version information // so don't accumulate status for this failure. return FALSE; } BOOL GenGetDebugRecord( IN PVOID Base, IN ULONG MappedSize, IN PIMAGE_NT_HEADERS NtHeaders, IN ULONG DebugRecordType, IN ULONG DebugRecordMaxSize, OUT PVOID * DebugInfo, OUT ULONG * SizeOfDebugInfo ) { ULONG i; ULONG Size; ULONG NumberOfDebugDirectories; IMAGE_DEBUG_DIRECTORY UNALIGNED* DebugDirectories; Size = 0; // // Find the debug directory and copy the memory into the. // DebugDirectories = (IMAGE_DEBUG_DIRECTORY UNALIGNED *) ImageDirectoryEntryToData (Base, FALSE, IMAGE_DIRECTORY_ENTRY_DEBUG, &Size); // // Check that we got a valid record. // if (DebugDirectories && ((Size % sizeof (IMAGE_DEBUG_DIRECTORY)) == 0) && (ULONG_PTR)DebugDirectories - (ULONG_PTR)Base + Size <= MappedSize) { NumberOfDebugDirectories = Size / sizeof (IMAGE_DEBUG_DIRECTORY); for (i = 0 ; i < NumberOfDebugDirectories; i++) { // // We should check if it's a NB10 or something record. // if ((DebugDirectories[ i ].Type == DebugRecordType) && (DebugDirectories[ i ].SizeOfData < DebugRecordMaxSize)) { if (DebugDirectories[i].PointerToRawData + DebugDirectories[i].SizeOfData > MappedSize) { break; } *SizeOfDebugInfo = DebugDirectories [ i ].SizeOfData; *DebugInfo = AllocMemory ( *SizeOfDebugInfo ); if (!(*DebugInfo)) { break; } CopyMemory(*DebugInfo, ((PBYTE) Base) + DebugDirectories [ i ].PointerToRawData, *SizeOfDebugInfo); return TRUE; } } } return FALSE; } PVOID GenOpenMapping( IN PCWSTR FilePath, OUT PULONG Size, OUT PWSTR LongPath, IN ULONG LongPathChars ) { HANDLE hFile; HANDLE hMappedFile; PVOID MappedFile; DWORD Chars; // // The module may be loaded with a short name. Open // the mapping with the name given, but also determine // the long name if possible. This is done here as // the ANSI/Unicode issues are already being handled here. // hFile = CreateFileW( FilePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); if ( hFile == NULL || hFile == INVALID_HANDLE_VALUE ) { if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) { // We're on an OS that doesn't support Unicode // file operations. Convert to ANSI and see if // that helps. CHAR FilePathA [ MAX_PATH + 10 ]; if (WideCharToMultiByte (CP_ACP, 0, FilePath, -1, FilePathA, sizeof (FilePathA), 0, 0 ) > 0) { hFile = CreateFileA(FilePathA, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); if (hFile != INVALID_HANDLE_VALUE) { Chars = GetLongPathNameA(FilePathA, FilePathA, ARRAY_COUNT(FilePathA)); if (Chars == 0 || Chars >= ARRAY_COUNT(FilePathA) || MultiByteToWideChar(CP_ACP, 0, FilePathA, -1, LongPath, LongPathChars) == 0) { // Couldn't get the long path, just use the // given path. lstrcpynW(LongPath, FilePath, LongPathChars); } } } } if ( hFile == NULL || hFile == INVALID_HANDLE_VALUE ) { GenAccumulateStatus(MDSTATUS_CALL_FAILED); return NULL; } } else { Chars = GetLongPathNameW(FilePath, LongPath, LongPathChars); if (Chars == 0 || Chars >= LongPathChars) { // Couldn't get the long path, just use the given path. lstrcpynW(LongPath, FilePath, LongPathChars); } } *Size = GetFileSize(hFile, NULL); if (*Size == -1) { CloseHandle( hFile ); GenAccumulateStatus(MDSTATUS_CALL_FAILED); return NULL; } hMappedFile = CreateFileMapping ( hFile, NULL, PAGE_READONLY, 0, 0, NULL ); if ( !hMappedFile ) { CloseHandle ( hFile ); GenAccumulateStatus(MDSTATUS_CALL_FAILED); return NULL; } MappedFile = MapViewOfFile ( hMappedFile, FILE_MAP_READ, 0, 0, 0 ); CloseHandle (hMappedFile); CloseHandle (hFile); if (!MappedFile) { GenAccumulateStatus(MDSTATUS_CALL_FAILED); } return MappedFile; } BOOL GenGetDataContributors( IN OUT PINTERNAL_PROCESS Process, IN PINTERNAL_MODULE Module ) { ULONG i; PIMAGE_SECTION_HEADER NtSection; BOOL Succ = TRUE; PVOID MappedBase; PIMAGE_NT_HEADERS NtHeaders; ULONG MappedSize; BOOL AnsiApi; MappedBase = GenOpenMapping ( Module->FullPath, &MappedSize, NULL, 0 ); if ( MappedBase == NULL ) { return FALSE; } NtHeaders = ImageNtHeader ( MappedBase ); NtSection = IMAGE_FIRST_SECTION ( NtHeaders ); for (i = 0; i < NtHeaders->FileHeader.NumberOfSections; i++) { if ( (NtSection[ i ].Characteristics & IMAGE_SCN_MEM_WRITE) && (NtSection[ i ].Characteristics & IMAGE_SCN_MEM_READ) && ( (NtSection[ i ].Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) || (NtSection[ i ].Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) ) ) { if (!GenAddMemoryBlock(Process, MEMBLOCK_DATA_SEG, SIGN_EXTEND (NtSection[i].VirtualAddress + Module->BaseOfImage), NtSection[i].Misc.VirtualSize)) { Succ = FALSE; } else { #if 0 printf ("Section: %8.8s Addr: %08x Size: %08x Raw Size: %08x\n", NtSection[ i ].Name, (ULONG)(NtSection[ i ].VirtualAddress + Module->BaseOfImage), NtSection[ i ].Misc.VirtualSize, NtSection[ i ].SizeOfRawData ); #endif } } } UnmapViewOfFile(MappedBase); return Succ; } HANDLE GenOpenThread( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadId ) { ULONG Type; ULONG Major; HANDLE hThread; // // First try the OpenThred call in the system, if one exists. This is // thunked to return NULL via the delay-load import mechanism if it // doesn't exist. // hThread = OpenThread (dwDesiredAccess, bInheritHandle, dwThreadId ); if ( hThread != NULL ) { return hThread; } // // Did not succeed. Try alternate methods. // GenGetSystemType ( &Type, &Major, NULL, NULL, NULL ); if ( Type == WinNt && Major == 4 ) { hThread = Nt4OpenThread ( dwDesiredAccess, bInheritHandle, dwThreadId ); } else if ( Type == Win9x ) { // // The Access and Inheritable parameters are ignored on Win9x. // hThread = WinOpenThread ( dwDesiredAccess, bInheritHandle, dwThreadId ); } else { hThread = NULL; } // Errors are sometimes expected due to // thread instability during initial suspension, // so do not accumulate status here. return hThread; } HRESULT GenAllocateThreadObject( IN struct _INTERNAL_PROCESS* Process, IN HANDLE ProcessHandle, IN ULONG ThreadId, IN ULONG DumpType, IN ULONG WriteFlags, PINTERNAL_THREAD* ThreadRet ) /*++ Routine Description: Allocate and initialize an INTERNAL_THREAD structure. Return Values: S_OK on success. S_FALSE if the thread can't be opened. Errors on failure. --*/ { HRESULT Succ; PINTERNAL_THREAD Thread; ULONG64 StackEnd; ULONG64 StackLimit; ULONG64 StoreLimit; ASSERT ( ProcessHandle ); Thread = (PINTERNAL_THREAD) AllocMemory ( sizeof (INTERNAL_THREAD) ); if (Thread == NULL) { return E_OUTOFMEMORY; } *ThreadRet = Thread; Thread->ThreadId = ThreadId; Thread->ThreadHandle = GenOpenThread ( THREAD_ALL_ACCESS, FALSE, Thread->ThreadId); if ( Thread->ThreadHandle == NULL ) { // The thread may have exited before we got around // to trying to open it. If the open fails with // a not-found code return an alternate success to // indicate that it's not a critical failure. Succ = HRESULT_FROM_WIN32(GetLastError()); if (Succ == HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER) || Succ == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { Succ = S_FALSE; } else if (SUCCEEDED(Succ)) { Succ = E_FAIL; } if (FAILED(Succ)) { GenAccumulateStatus(MDSTATUS_CALL_FAILED); } goto Exit; } // If the current thread is dumping itself we can't // suspend. We can also assume the suspend count must // be zero since the thread is running. if (Thread->ThreadId == GetCurrentThreadId()) { Thread->SuspendCount = 0; } else { Thread->SuspendCount = SuspendThread ( Thread->ThreadHandle ); } Thread->WriteFlags = WriteFlags; // // Add this if we ever need it // Thread->PriorityClass = 0; Thread->Priority = 0; // // Initialize the thread context. // Thread->Context.ContextFlags = ALL_REGISTERS; Succ = GetThreadContext (Thread->ThreadHandle, &Thread->Context) ? S_OK : E_FAIL; if ( Succ != S_OK ) { GenAccumulateStatus(MDSTATUS_CALL_FAILED); goto Exit; } Thread->SizeOfContext = sizeof (CONTEXT); Succ = GenGetThreadInfo(ProcessHandle, Thread->ThreadHandle, &Thread->Teb, &Thread->SizeOfTeb, &Thread->StackBase, &StackLimit, &Thread->BackingStoreBase, &StoreLimit); if (Succ != S_OK) { goto Exit; } // // If the stack pointer (SP) is within the range of the stack // region (as allocated by the OS), only take memory from // the stack region up to the SP. Otherwise, assume the program // has blown it's SP -- purposefully, or not -- and copy // the entire stack as known by the OS. // StackEnd = SIGN_EXTEND (STACK_POINTER (&Thread->Context)); #if defined (i386) // // Note: for FPO frames on x86 we access bytes outside of the // ESP - StackBase range. Add a couple of bytes extra here so we // don't fail these cases. // StackEnd -= X86_STACK_FRAME_EXTRA_FPO_BYTES; #endif #ifdef DUMP_BACKING_STORE Thread->BackingStoreSize = (ULONG)(SIGN_EXTEND(BSTORE_POINTER(&Thread->Context)) - Thread->BackingStoreBase); #else Thread->BackingStoreSize = 0; #endif if (StackLimit <= StackEnd && StackEnd < Thread->StackBase) { Thread->StackEnd = StackEnd; } else { Thread->StackEnd = StackLimit; } if ((ULONG)(Thread->StackBase - Thread->StackEnd) > Process->MaxStackOrStoreSize) { Process->MaxStackOrStoreSize = (ULONG)(Thread->StackBase - Thread->StackEnd); } if (Thread->BackingStoreSize > Process->MaxStackOrStoreSize) { Process->MaxStackOrStoreSize = Thread->BackingStoreSize; } Exit: if ( Succ != S_OK ) { FreeMemory ( Thread ); } return Succ; } VOID GenFreeThreadObject( IN PINTERNAL_THREAD Thread ) { if (Thread->SuspendCount != -1 && Thread->ThreadId != GetCurrentThreadId()) { ResumeThread (Thread->ThreadHandle); Thread->SuspendCount = -1; } CloseHandle (Thread->ThreadHandle); Thread->ThreadHandle = NULL; FreeMemory ( Thread ); Thread = NULL; } BOOL GenGetThreadInstructionWindow( IN PINTERNAL_PROCESS Process, IN PINTERNAL_THREAD Thread ) { PVOID MemBuf; PUCHAR InstrStart; ULONG InstrSize; SIZE_T BytesRead; BOOL Succ = FALSE; // // Store a window of the instruction stream around // the current program counter. This allows some // instruction context to be given even when images // can't be mapped. It also allows instruction // context to be given for generated code where // no image contains the necessary instructions. // InstrStart = (PUCHAR)PROGRAM_COUNTER(&Thread->Context) - INSTRUCTION_WINDOW_SIZE / 2; InstrSize = INSTRUCTION_WINDOW_SIZE; MemBuf = AllocMemory(InstrSize); if (!MemBuf) { return FALSE; } for (;;) { // If we can read the instructions through the // current program counter we'll say that's // good enough. if (ReadProcessMemory(Process->ProcessHandle, InstrStart, MemBuf, InstrSize, &BytesRead) && InstrStart + BytesRead > (PUCHAR)PROGRAM_COUNTER(&Thread->Context)) { Succ = GenAddMemoryBlock(Process, MEMBLOCK_INSTR_WINDOW, SIGN_EXTEND(InstrStart), (ULONG)BytesRead) != NULL; break; } // We couldn't read up to the program counter. // If the start address is on the previous page // move it up to the same page. if (((ULONG_PTR)InstrStart & ~(PAGE_SIZE - 1)) != (PROGRAM_COUNTER(&Thread->Context) & ~(PAGE_SIZE - 1))) { ULONG Fraction = PAGE_SIZE - (ULONG)(ULONG_PTR)InstrStart & (PAGE_SIZE - 1); InstrSize -= Fraction; InstrStart += Fraction; } else { // The start and PC were on the same page so // we just can't read memory. There may have been // a jump to a bad address or something, so this // doesn't constitute an unexpected failure. break; } } FreeMemory(MemBuf); return Succ; } PINTERNAL_MODULE GenAllocateModuleObject( IN PINTERNAL_PROCESS Process, IN PWSTR FullPathW, IN ULONG_PTR BaseOfModule, IN ULONG DumpType, IN ULONG WriteFlags ) /*++ Routine Description: Given the full-path to the module and the base of the module, create and initialize an INTERNAL_MODULE object, and return it. --*/ { BOOL Succ; PVOID MappedBase; ULONG MappedSize; PIMAGE_NT_HEADERS NtHeaders; PINTERNAL_MODULE Module; ULONG Chars; BOOL AnsiApi; ASSERT (FullPathW); ASSERT (BaseOfModule); Module = (PINTERNAL_MODULE) AllocMemory ( sizeof (INTERNAL_MODULE) ); if (Module == NULL) { return NULL; } MappedBase = GenOpenMapping ( FullPathW, &MappedSize, Module->FullPath, ARRAY_COUNT(Module->FullPath) ); if ( MappedBase == NULL ) { FreeMemory(Module); return NULL; } if (IsFlagSet(DumpType, MiniDumpFilterModulePaths)) { Module->SavePath = Module->FullPath + lstrlenW(Module->FullPath); while (Module->SavePath > Module->FullPath) { Module->SavePath--; if (*Module->SavePath == '\\' || *Module->SavePath == '/' || *Module->SavePath == ':') { Module->SavePath++; break; } } } else { Module->SavePath = Module->FullPath; } // // Cull information from the image header. // NtHeaders = ImageNtHeader ( MappedBase ); Module->BaseOfImage = SIGN_EXTEND (BaseOfModule); Module->SizeOfImage = NtHeaders->OptionalHeader.SizeOfImage; Module->CheckSum = NtHeaders->OptionalHeader.CheckSum; Module->TimeDateStamp = NtHeaders->FileHeader.TimeDateStamp; Module->WriteFlags = WriteFlags; // // Get the version information for the module. // Succ = GenGetVersionInfo ( FullPathW, &Module->VersionInfo ); if ( !Succ ) { Module->VersionInfo.dwSignature = 0; } // // Get the CV record from the debug directory. // if (IsFlagSet(Module->WriteFlags, ModuleWriteCvRecord)) { Succ = GenGetDebugRecord(MappedBase, MappedSize, NtHeaders, IMAGE_DEBUG_TYPE_CODEVIEW, REASONABLE_NB11_RECORD_SIZE, &Module->CvRecord, &Module->SizeOfCvRecord); if ( !Succ ) { Module->CvRecord = NULL; Module->SizeOfCvRecord = 0; } } // // Get the MISC record from the debug directory. // if (IsFlagSet(Module->WriteFlags, ModuleWriteMiscRecord)) { Succ = GenGetDebugRecord(MappedBase, MappedSize, NtHeaders, IMAGE_DEBUG_TYPE_MISC, REASONABLE_MISC_RECORD_SIZE, &Module->MiscRecord, &Module->SizeOfMiscRecord); if ( !Succ ) { Module->MiscRecord = NULL; Module->SizeOfMiscRecord = 0; } } UnmapViewOfFile ( MappedBase ); return Module; } VOID GenFreeModuleObject( IN PINTERNAL_MODULE Module ) { FreeMemory ( Module->CvRecord ); Module->CvRecord = NULL; FreeMemory ( Module->MiscRecord ); Module->MiscRecord = NULL; FreeMemory ( Module ); Module = NULL; } PINTERNAL_UNLOADED_MODULE GenAllocateUnloadedModuleObject( IN PWSTR Path, IN ULONG_PTR BaseOfModule, IN ULONG SizeOfModule, IN ULONG CheckSum, IN ULONG TimeDateStamp ) { PINTERNAL_UNLOADED_MODULE Module; Module = (PINTERNAL_UNLOADED_MODULE) AllocMemory ( sizeof (*Module) ); if (Module == NULL) { return NULL; } lstrcpynW (Module->Path, Path, ARRAY_COUNT(Module->Path)); Module->BaseOfImage = SIGN_EXTEND (BaseOfModule); Module->SizeOfImage = SizeOfModule; Module->CheckSum = CheckSum; Module->TimeDateStamp = TimeDateStamp; return Module; } VOID GenFreeUnloadedModuleObject( IN PINTERNAL_UNLOADED_MODULE Module ) { FreeMemory ( Module ); Module = NULL; } typedef BOOL (WINAPI* FN_GetProcessTimes)( IN HANDLE hProcess, OUT LPFILETIME lpCreationTime, OUT LPFILETIME lpExitTime, OUT LPFILETIME lpKernelTime, OUT LPFILETIME lpUserTime ); PINTERNAL_PROCESS GenAllocateProcessObject( IN HANDLE hProcess, IN ULONG ProcessId ) { PINTERNAL_PROCESS Process; FN_GetProcessTimes GetProcTimes; LPVOID Peb; Process = (PINTERNAL_PROCESS) AllocMemory ( sizeof (INTERNAL_PROCESS) ); if (!Process) { return NULL; } Process->ProcessId = ProcessId; Process->ProcessHandle = hProcess; Process->NumberOfThreads = 0; Process->NumberOfModules = 0; Process->NumberOfFunctionTables = 0; InitializeListHead (&Process->ThreadList); InitializeListHead (&Process->ModuleList); InitializeListHead (&Process->UnloadedModuleList); InitializeListHead (&Process->FunctionTableList); InitializeListHead (&Process->MemoryBlocks); GetProcTimes = (FN_GetProcessTimes) GetProcAddress(GetModuleHandle("kernel32.dll"), "GetProcessTimes"); if (GetProcTimes) { FILETIME Create, Exit, User, Kernel; if (GetProcTimes(hProcess, &Create, &Exit, &User, &Kernel)) { Process->TimesValid = TRUE; Process->CreateTime = FileTimeToTimeDate(&Create); Process->UserTime = FileTimeToSeconds(&User); Process->KernelTime = FileTimeToSeconds(&Kernel); } else { GenAccumulateStatus(MDSTATUS_CALL_FAILED); } } Peb = GenGetPebAddress(hProcess, &Process->SizeOfPeb); Process->Peb = SIGN_EXTEND(Peb); return Process; } BOOL GenFreeProcessObject( IN PINTERNAL_PROCESS Process ) { PINTERNAL_MODULE Module; PINTERNAL_UNLOADED_MODULE UnlModule; PINTERNAL_THREAD Thread; PINTERNAL_FUNCTION_TABLE Table; PVA_RANGE Range; PLIST_ENTRY Entry; Thread = NULL; Module = NULL; Entry = Process->ModuleList.Flink; while ( Entry != &Process->ModuleList ) { Module = CONTAINING_RECORD (Entry, INTERNAL_MODULE, ModulesLink); Entry = Entry->Flink; GenFreeModuleObject ( Module ); Module = NULL; } Entry = Process->UnloadedModuleList.Flink; while ( Entry != &Process->UnloadedModuleList ) { UnlModule = CONTAINING_RECORD (Entry, INTERNAL_UNLOADED_MODULE, ModulesLink); Entry = Entry->Flink; GenFreeUnloadedModuleObject ( UnlModule ); UnlModule = NULL; } Entry = Process->ThreadList.Flink; while ( Entry != &Process->ThreadList ) { Thread = CONTAINING_RECORD (Entry, INTERNAL_THREAD, ThreadsLink); Entry = Entry->Flink; if (Thread->SuspendCount != -1) { GenFreeThreadObject ( Thread ); Thread = NULL; } } Entry = Process->FunctionTableList.Flink; while ( Entry != &Process->FunctionTableList ) { Table = CONTAINING_RECORD (Entry, INTERNAL_FUNCTION_TABLE, TableLink); Entry = Entry->Flink; GenFreeFunctionTableObject ( Table ); } Entry = Process->MemoryBlocks.Flink; while (Entry != &Process->MemoryBlocks) { Range = CONTAINING_RECORD(Entry, VA_RANGE, NextLink); Entry = Entry->Flink; FreeMemory(Range); } FreeMemory ( Process ); Process = NULL; return TRUE; } struct _INTERNAL_FUNCTION_TABLE* GenAllocateFunctionTableObject( IN ULONG64 MinAddress, IN ULONG64 MaxAddress, IN ULONG64 BaseAddress, IN ULONG EntryCount, IN PDYNAMIC_FUNCTION_TABLE RawTable ) { PINTERNAL_FUNCTION_TABLE Table; Table = (PINTERNAL_FUNCTION_TABLE) AllocMemory ( sizeof (INTERNAL_FUNCTION_TABLE) ); if (Table) { Table->RawEntries = AllocMemory(sizeof(RUNTIME_FUNCTION) * EntryCount); if (Table->RawEntries) { Table->MinimumAddress = MinAddress; Table->MaximumAddress = MaxAddress; Table->BaseAddress = BaseAddress; Table->EntryCount = EntryCount; Table->RawTable = *RawTable; // RawEntries will be filled out afterwards. } else { FreeMemory(Table); Table = NULL; } } return Table; } VOID GenFreeFunctionTableObject( IN struct _INTERNAL_FUNCTION_TABLE* Table ) { if (Table->RawEntries) { FreeMemory(Table->RawEntries); } FreeMemory(Table); } BOOL GenIncludeUnwindInfoMemory( IN PINTERNAL_PROCESS Process, IN ULONG DumpType, IN struct _INTERNAL_FUNCTION_TABLE* Table ) { ULONG i; PRUNTIME_FUNCTION FuncEnt; if (DumpType & MiniDumpWithFullMemory) { // Memory will be included by default. return TRUE; } // This code only needs to scan IA64 and AMD64 tables. #if !defined(_IA64_) && !defined(_AMD64_) return TRUE; #endif FuncEnt = (PRUNTIME_FUNCTION)Table->RawEntries; for (i = 0; i < Table->EntryCount; i++) { #if defined(_IA64_) || defined(_AMD64_) SIZE_T Done; UNWIND_INFO Info; ULONG64 Start; ULONG Size; #endif #if defined(_IA64_) Start = Table->BaseAddress + FuncEnt->UnwindInfoAddress; if (!ReadProcessMemory(Process->ProcessHandle, (PVOID)Start, &Info, sizeof(Info), &Done) || Done != sizeof(Info)) { GenAccumulateStatus(MDSTATUS_UNABLE_TO_READ_MEMORY); return FALSE; } Size = sizeof(Info) + Info.DataLength * sizeof(ULONG64); #elif defined(_AMD64_) Start = Table->BaseAddress + FuncEnt->UnwindData; if (!ReadProcessMemory(Process->ProcessHandle, (PVOID)Start, &Info, sizeof(Info), &Done) || Done != sizeof(Info)) { GenAccumulateStatus(MDSTATUS_UNABLE_TO_READ_MEMORY); return FALSE; } Size = sizeof(Info) + (Info.CountOfCodes - 1) * sizeof(UNWIND_CODE); // An extra alignment code and pointer may be added on to handle // the chained info case where the chain pointer is just // beyond the end of the normal code array. if ((Info.Flags & UNW_FLAG_CHAININFO) != 0) { if ((Info.CountOfCodes & 1) != 0) { Size += sizeof(UNWIND_CODE); } Size += sizeof(ULONG64); } #endif #if defined(_IA64_) || defined(_AMD64_) if (!GenAddMemoryBlock(Process, MEMBLOCK_UNWIND_INFO, Start, Size)) { return FALSE; } #endif FuncEnt++; } return TRUE; } PVOID GenGetTebAddress( IN HANDLE Thread, OUT PULONG SizeOfTeb ) /*++ Routine Description: Get the TIB (or TEB, if you prefer) address for the thread identified by ThreadHandle. Arguments: Thread - A handle for a thread that has THRED_QUERY_CONTEXT and THREAD_QUERY_INFORMATION privileges. Return Values: Linear address of the Tib (Teb) on success. NULL on failure. --*/ { LPVOID TebAddress; ULONG Type; ULONG Major; GenGetSystemType (&Type, &Major, NULL, NULL, NULL); if ( Type == WinNt ) { TebAddress = NtxGetTebAddress (Thread, SizeOfTeb); } else if ( Type != Win9x ) { // WinCE doesn't have a TIB. TebAddress = NULL; *SizeOfTeb = 0; } else { #ifdef _X86_ BOOL Succ; ULONG Addr; LDT_ENTRY Ldt; CONTEXT Context; Context.ContextFlags = CONTEXT_SEGMENTS; Succ = GetThreadContext (Thread, &Context); if ( !Succ ) { GenAccumulateStatus(MDSTATUS_CALL_FAILED); return NULL; } Succ = GetThreadSelectorEntry (Thread, Context.SegFs, &Ldt ); if ( !Succ ) { GenAccumulateStatus(MDSTATUS_CALL_FAILED); return NULL; } Addr = (Ldt.HighWord.Bytes.BaseHi << 24) | (Ldt.HighWord.Bytes.BaseMid << 16) | (Ldt.BaseLow); TebAddress = (LPVOID) Addr; *SizeOfTeb = sizeof(NT_TIB); #else TebAddress = NULL; *SizeOfTeb = 0; #endif // _X86_ } return TebAddress; } PVOID GenGetPebAddress( IN HANDLE Process, OUT PULONG SizeOfPeb ) { LPVOID PebAddress; ULONG Type; ULONG Major; GenGetSystemType (&Type, &Major, NULL, NULL, NULL); if ( Type == WinNt ) { PebAddress = NtxGetPebAddress (Process, SizeOfPeb); } else if ( Type == WinCe ) { PebAddress = WceGetPebAddress (Process, SizeOfPeb); } else { // No process data. PebAddress = NULL; *SizeOfPeb = 0; } return PebAddress; } HRESULT GenGetThreadInfo( IN HANDLE Process, IN HANDLE Thread, OUT PULONG64 Teb, OUT PULONG SizeOfTeb, OUT PULONG64 StackBase, OUT PULONG64 StackLimit, OUT PULONG64 StoreBase, OUT PULONG64 StoreLimit ) { ULONG Type; GenGetSystemType (&Type, NULL, NULL, NULL, NULL); if ( Type == WinCe ) { return WceGetThreadInfo(Process, Thread, Teb, SizeOfTeb, StackBase, StackLimit, StoreBase, StoreLimit); } else { LPVOID TebAddress = GenGetTebAddress (Thread, SizeOfTeb); if (!TebAddress) { return E_FAIL; } *Teb = SIGN_EXTEND((LONG_PTR)TebAddress); return TibGetThreadInfo(Process, TebAddress, StackBase, StackLimit, StoreBase, StoreLimit); } } void GenRemoveMemoryBlock( IN PINTERNAL_PROCESS Process, IN PVA_RANGE Block ) { RemoveEntryList(&Block->NextLink); Process->NumberOfMemoryBlocks--; Process->SizeOfMemoryBlocks -= Block->Size; } PVA_RANGE GenAddMemoryBlock( IN PINTERNAL_PROCESS Process, IN MEMBLOCK_TYPE Type, IN ULONG64 Start, IN ULONG Size ) { ULONG64 End; PLIST_ENTRY ScanEntry; PVA_RANGE Scan; ULONG64 ScanEnd; PVA_RANGE New = NULL; SIZE_T Done; UCHAR Byte; // Do not use Size after this to avoid ULONG overflows. End = Start + Size; if (End < Start) { End = (ULONG64)-1; } if (Start == End) { // Nothing to add. return NULL; } if ((End - Start) > ULONG_MAX - Process->SizeOfMemoryBlocks) { // Overflow. GenAccumulateStatus(MDSTATUS_INTERNAL_ERROR); return NULL; } // // First trim the range down to memory that can actually // be accessed. // while (Start < End) { if (ReadProcessMemory(Process->ProcessHandle, (PVOID)(ULONG_PTR)Start, &Byte, sizeof(Byte), &Done) && Done) { break; } // Move up to the next page. Start = (Start + PAGE_SIZE) & ~(PAGE_SIZE - 1); if (!Start) { // Wrapped around. return NULL; } } if (Start >= End) { // No valid memory. return NULL; } ScanEnd = (Start + PAGE_SIZE) & ~(PAGE_SIZE - 1); for (;;) { if (ScanEnd >= End) { break; } if (!ReadProcessMemory(Process->ProcessHandle, (PVOID)(ULONG_PTR)ScanEnd, &Byte, sizeof(Byte), &Done) || !Done) { End = ScanEnd; break; } // Move up to the next page. ScanEnd = (ScanEnd + PAGE_SIZE) & ~(PAGE_SIZE - 1); if (!ScanEnd) { ScanEnd--; break; } } // // When adding memory to the list of memory to be saved // we want to avoid overlaps and also coalesce adjacent regions // so that the list has the largest possible non-adjacent // blocks. In order to accomplish this we make a pass over // the list and merge all listed blocks that overlap or abut the // incoming range with the incoming range, then remove the // merged entries from the list. After this pass we have // a region which is guaranteed not to overlap or abut anything in // the list. // ScanEntry = Process->MemoryBlocks.Flink; while (ScanEntry != &Process->MemoryBlocks) { Scan = CONTAINING_RECORD(ScanEntry, VA_RANGE, NextLink); ScanEnd = Scan->Start + Scan->Size; ScanEntry = Scan->NextLink.Flink; if (Scan->Start > End || ScanEnd < Start) { // No overlap or adjacency. continue; } // // Compute the union of the incoming range and // the scan block, then remove the scan block. // if (Scan->Start < Start) { Start = Scan->Start; } if (ScanEnd > End) { End = ScanEnd; } // We've lost the specific type. This is not a problem // right now but if specific types must be preserved // all the way through in the future it will be necessary // to avoid merging. Type = MEMBLOCK_MERGED; GenRemoveMemoryBlock(Process, Scan); if (!New) { // Save memory for reuse. New = Scan; } else { FreeMemory(Scan); } } if (!New) { New = (PVA_RANGE)AllocMemory(sizeof(*New)); if (!New) { return NULL; } } New->Start = Start; // Overflow is extremely unlikely, so don't do anything // fancy to handle it. if (End - Start > ULONG_MAX) { New->Size = ULONG_MAX; } else { New->Size = (ULONG)(End - Start); } New->Type = Type; InsertTailList(&Process->MemoryBlocks, &New->NextLink); Process->NumberOfMemoryBlocks++; Process->SizeOfMemoryBlocks += New->Size; return New; } void GenRemoveMemoryRange( IN PINTERNAL_PROCESS Process, IN ULONG64 Start, IN ULONG Size ) { ULONG64 End = Start + Size; PLIST_ENTRY ScanEntry; PVA_RANGE Scan; ULONG64 ScanEnd; Restart: ScanEntry = Process->MemoryBlocks.Flink; while (ScanEntry != &Process->MemoryBlocks) { Scan = CONTAINING_RECORD(ScanEntry, VA_RANGE, NextLink); ScanEnd = Scan->Start + Scan->Size; ScanEntry = Scan->NextLink.Flink; if (Scan->Start >= End || ScanEnd <= Start) { // No overlap. continue; } if (Scan->Start < Start) { // Trim block to non-overlapping pre-Start section. Scan->Size = (ULONG)(Start - Scan->Start); if (ScanEnd > End) { // There's also a non-overlapping section post-End. // We need to add a new block. GenAddMemoryBlock(Process, Scan->Type, End, (ULONG)(ScanEnd - End)); // The list has changed so restart. goto Restart; } } else if (ScanEnd > End) { // Trim block to non-overlapping post-End section. Scan->Start = End; Scan->Size = (ULONG)(ScanEnd - End); } else { // Scan is completely contained. GenRemoveMemoryBlock(Process, Scan); FreeMemory(Scan); } } } VOID WINAPI GenGetSystemType( OUT ULONG * Type, OPTIONAL OUT ULONG * Major, OPTIONAL OUT ULONG * Minor, OPTIONAL OUT ULONG * ServicePack, OPTIONAL OUT ULONG * BuildNumber OPTIONAL ) { OSVERSIONINFO Version; Version.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); GetVersionEx (&Version); if (Type) { if (Version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { *Type = Win9x; } else if (Version.dwPlatformId == VER_PLATFORM_WIN32_NT) { *Type = WinNt; } else if (Version.dwPlatformId == VER_PLATFORM_WIN32_CE) { *Type = WinCe; } else { *Type = Unknown; } } if (Major) { if (Version.dwPlatformId == VER_PLATFORM_WIN32_NT || Version.dwPlatformId == VER_PLATFORM_WIN32_CE) { *Major = Version.dwMajorVersion; } else { if (Version.dwMinorVersion == 0) { *Major = 95; } else { *Major = 98; } } } if (Minor) { if (Version.dwPlatformId == VER_PLATFORM_WIN32_NT || Version.dwPlatformId == VER_PLATFORM_WIN32_CE) { *Minor = Version.dwMinorVersion; } else { *Minor = 0; } } // // TODO: Derive this from known build numbers only if it's necessary // for external stuff. // if (ServicePack) { *ServicePack = 0; } if (BuildNumber) { *BuildNumber = Version.dwBuildNumber; } } BOOL GenScanAddressSpace( IN PINTERNAL_PROCESS Process, IN ULONG DumpType ) { ULONG ProtectMask = 0, TypeMask = 0; BOOL Succ; ULONG_PTR Offset; MEMORY_BASIC_INFORMATION Info; if (DumpType & MiniDumpWithPrivateReadWriteMemory) { ProtectMask |= PAGE_READWRITE; TypeMask |= MEM_PRIVATE; } if (!ProtectMask || !TypeMask) { // Nothing to scan for. return TRUE; } Succ = TRUE; Offset = 0; for (;;) { if (!VirtualQueryEx(Process->ProcessHandle, (LPVOID)Offset, &Info, sizeof(Info))) { break; } Offset = (ULONG_PTR)Info.BaseAddress + Info.RegionSize; if (Info.State == MEM_COMMIT && (Info.Protect & ProtectMask) && (Info.Type & TypeMask)) { while (Info.RegionSize > 0) { ULONG BlockSize; if (Info.RegionSize > ULONG_MAX / 2) { BlockSize = ULONG_MAX / 2; } else { BlockSize = (ULONG)Info.RegionSize; } if (!GenAddMemoryBlock(Process, MEMBLOCK_PRIVATE_RW, SIGN_EXTEND(Info.BaseAddress), BlockSize)) { Succ = FALSE; } Info.BaseAddress = (PVOID) ((ULONG_PTR)Info.BaseAddress + BlockSize); Info.RegionSize -= BlockSize; } } } return Succ; } BOOL GenGetProcessInfo( IN HANDLE hProcess, IN ULONG ProcessId, IN ULONG DumpType, IN MINIDUMP_CALLBACK_ROUTINE CallbackRoutine, IN PVOID CallbackParam, OUT PINTERNAL_PROCESS * ProcessRet ) { BOOL Succ; ULONG Type; ULONG Major; GenGetSystemType (&Type, &Major, NULL, NULL, NULL); if ( Type == WinNt && Major > 4 ) { Succ = NtxGetProcessInfo (hProcess, ProcessId, DumpType, CallbackRoutine, CallbackParam, ProcessRet); } else if ( Type == WinNt && Major == 4 ) { Succ = Nt4GetProcessInfo (hProcess, ProcessId, DumpType, CallbackRoutine, CallbackParam, ProcessRet); } else if ( Type == Win9x || Type == WinCe ) { Succ = ThGetProcessInfo (hProcess, ProcessId, DumpType, CallbackRoutine, CallbackParam, ProcessRet); } else { Succ = FALSE; } if (Succ) { // We don't consider a failure here to be a critical // failure. The dump won't contain all of the // requested information but it'll still have // the basic thread information, which could be // valuable on its own. GenScanAddressSpace(*ProcessRet, DumpType); } return Succ; } BOOL GenWriteHandleData( IN HANDLE ProcessHandle, IN HANDLE hFile, IN struct _MINIDUMP_STREAM_INFO * StreamInfo ) { BOOL Succ; ULONG Type; ULONG Major; GenGetSystemType(&Type, &Major, NULL, NULL, NULL); if ( Type == WinNt ) { Succ = NtxWriteHandleData(ProcessHandle, hFile, StreamInfo); } else { Succ = FALSE; } return Succ; } ULONG CheckSum ( IN ULONG PartialSum, IN PVOID SourceVa, IN ULONG_PTR Length ) /*++ Routine Description: Computes a checksum for the supplied virtual address and length This function comes from Dr. Dobbs Journal, May 1992 Arguments: PartialSum - The previous partial checksum SourceVa - Starting address Length - Length, in bytes, of the range Return Value: The checksum value --*/ { PUSHORT Source; Source = (PUSHORT) SourceVa; Length = Length / 2; while (Length--) { PartialSum += *Source++; PartialSum = (PartialSum >> 16) + (PartialSum & 0xFFFF); } return PartialSum; } BOOL ThGetProcessInfo( IN HANDLE hProcess, IN ULONG ProcessId, IN ULONG DumpType, IN MINIDUMP_CALLBACK_ROUTINE CallbackRoutine, IN PVOID CallbackParam, OUT PINTERNAL_PROCESS * ProcessRet ) /*++ Routine Description: Using toolhelp, obtain the process information for this process. As toolhelp provides an abstraction for retrieval, this code is "generic" and can run on any platform supporting toolhelp. Return Values: TRUE - Success. FALSE - Failure: Environment: Any platform that supports toolhelp. --*/ { BOOL Succ; BOOL MoreThreads; BOOL MoreModules; HANDLE Snapshot; MODULEENTRY32 ModuleInfo; THREADENTRY32 ThreadInfo; PINTERNAL_THREAD Thread; PINTERNAL_PROCESS Process; PINTERNAL_MODULE Module; WCHAR UnicodePath [ MAX_PATH + 10 ]; ASSERT ( hProcess ); ASSERT ( ProcessId != 0 ); ASSERT ( ProcessRet ); Process = NULL; Thread = NULL; Module = NULL; Snapshot = NULL; ModuleInfo.dwSize = sizeof (MODULEENTRY32); ThreadInfo.dwSize = sizeof (THREADENTRY32); Process = GenAllocateProcessObject ( hProcess, ProcessId ); if ( Process == NULL ) { return FALSE; } Snapshot = CreateToolhelp32Snapshot ( TH32CS_SNAPMODULE | TH32CS_SNAPTHREAD, ProcessId ); if ( Snapshot == (HANDLE) -1 ) { GenAccumulateStatus(MDSTATUS_CALL_FAILED); Succ = FALSE; goto Exit; } // // Walk thread list, suspending all threads and getting thread info. // for (MoreThreads = ProcessThread32First (Snapshot, ProcessId, &ThreadInfo ); MoreThreads; MoreThreads = ProcessThread32Next ( Snapshot, ProcessId, &ThreadInfo ) ) { HRESULT Status; ULONG WriteFlags; if (!GenExecuteIncludeThreadCallback(hProcess, ProcessId, DumpType, ThreadInfo.th32ThreadID, CallbackRoutine, CallbackParam, &WriteFlags) || IsFlagClear(WriteFlags, ThreadWriteThread)) { continue; } Status = GenAllocateThreadObject ( Process, hProcess, ThreadInfo.th32ThreadID, DumpType, WriteFlags, &Thread ); if ( FAILED(Status) ) { Succ = FALSE; goto Exit; } // If Status is S_FALSE it means that the thread // couldn't be opened and probably exited before // we got to it. Just continue on. if (Status == S_OK) { Process->NumberOfThreads++; InsertTailList (&Process->ThreadList, &Thread->ThreadsLink); } } // // Walk module list, getting module information. // for (MoreModules = Module32First ( Snapshot, &ModuleInfo ); MoreModules; MoreModules = Module32Next ( Snapshot, &ModuleInfo ) ) { ULONG WriteFlags; ASSERT ( (ULONG_PTR)ModuleInfo.modBaseAddr == (ULONG_PTR)ModuleInfo.hModule ); if (!GenExecuteIncludeModuleCallback(hProcess, ProcessId, DumpType, (LONG_PTR)ModuleInfo.modBaseAddr, CallbackRoutine, CallbackParam, &WriteFlags) || IsFlagClear(WriteFlags, ModuleWriteModule)) { continue; } MultiByteToWideChar (CP_ACP, 0, ModuleInfo.szExePath, -1, UnicodePath, ARRAY_COUNT(UnicodePath) ); Module = GenAllocateModuleObject ( Process, UnicodePath, (LONG_PTR) ModuleInfo.modBaseAddr, DumpType, WriteFlags ); if ( Module == NULL ) { Succ = FALSE; goto Exit; } Process->NumberOfModules++; InsertTailList (&Process->ModuleList, &Module->ModulesLink); } Succ = TRUE; Exit: if ( Snapshot ) { CloseHandle ( Snapshot ); Snapshot = NULL; } if ( !Succ && Process != NULL ) { GenFreeProcessObject ( Process ); Process = NULL; } *ProcessRet = Process; return Succ; }