/*++ Copyright(c) 1999-2002 Microsoft Corporation Module Name: ntx.c Abstract: Minidump user-mode crashdump NT specific functions. These routines work on NT-based operating systems from NT5 on. Author: Matthew D Hendel (math) 20-Aug-1999 --*/ #include "pch.h" #include "impl.h" PINTERNAL_MODULE NtxAllocateModuleObject( IN PINTERNAL_PROCESS Process, IN HANDLE ProcessHandle, IN ULONG_PTR BaseOfModule, IN ULONG DumpType, IN ULONG WriteFlags, IN PWSTR ModuleName OPTIONAL ) { WCHAR FullPath [ MAX_PATH + 10 ]; // // The basic LdrQueryProcessModule API that toolhelp uses // always returns ANSI strings for module paths. This // means that even if you use the wide toolhelp calls // you still lose Unicode information because the original // Unicode path was converted to ANSI and then back to Unicode. // To avoid this problem, always try and look up the true // Unicode path first. This doesn't work for 32-bit modules // in WOW64, though, so if there's a failure just use the // incoming string. // if (GetModuleFileNameExW(ProcessHandle, (HMODULE) BaseOfModule, FullPath, sizeof (FullPath))) { ModuleName = FullPath; } else if (!ModuleName) { GenAccumulateStatus(MDSTATUS_CALL_FAILED); return NULL; } // // Translate funky \??\... module name. // return GenAllocateModuleObject (Process, ModuleName, BaseOfModule, DumpType, WriteFlags); } typedef PLIST_ENTRY (*FN_RtlGetFunctionTableListHead) ( VOID ); BOOL NtxGetFunctionTables( IN HANDLE hProcess, IN PINTERNAL_PROCESS Process, IN ULONG DumpType ) { #ifdef _WIN32_WCE return FALSE; #else HMODULE NtDll; FN_RtlGetFunctionTableListHead GetHead = NULL; PLIST_ENTRY HeadAddr; LIST_ENTRY Head; PVOID Next; SIZE_T Done; DYNAMIC_FUNCTION_TABLE Table; // // On systems that support dynamic function tables // ntdll exports a function called RtlGetFunctionTableListHead // to retrieve the head of a process's function table list. // Currently this is always a global LIST_ENTRY in ntdll // and so is at the same address in all processes since ntdll // is mapped at the same address in every process. This // means we can call it in our process and get a pointer // that's valid in the process being dumped. // // We also use the presence of RGFTLH as a signal of // whether dynamic function tables are supported or not. // NtDll = GetModuleHandle("ntdll"); if (NtDll) { GetHead = (FN_RtlGetFunctionTableListHead) GetProcAddress(NtDll, "RtlGetFunctionTableListHead"); } if (!GetHead) { // Dynamic function tables are not supported. return TRUE; } HeadAddr = GetHead(); if (!ReadProcessMemory(hProcess, HeadAddr, &Head, sizeof(Head), &Done) || Done != sizeof(Head)) { GenAccumulateStatus(MDSTATUS_UNABLE_TO_READ_MEMORY); return FALSE; } Next = Head.Flink; while (Next && Next != HeadAddr) { PINTERNAL_FUNCTION_TABLE IntTable; PVOID HeapEntries; PVOID TableAddr; ULONG EntryCount; TableAddr = Next; if (!ReadProcessMemory(hProcess, TableAddr, &Table, sizeof(Table), &Done) || Done != sizeof(Table)) { GenAccumulateStatus(MDSTATUS_UNABLE_TO_READ_MEMORY); return FALSE; } #ifdef _AMD64_ Next = Table.ListEntry.Flink; #else Next = Table.Links.Flink; #endif HeapEntries = NULL; #if defined(_AMD64_) || defined(_IA64_) // // AMD64 and IA64 support a type of function table // where the data is retrieved via a callback rather // than being is a plain data table. In order to // get at the data from out-of-process the table // must have an out-of-process access DLL registered. // if (Table.Type == RF_CALLBACK) { WCHAR DllName[MAX_PATH]; HMODULE OopDll; POUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK OopCb; if (!Table.OutOfProcessCallbackDll) { // No out-of-process access is possible. continue; } if (!ReadProcessMemory(hProcess, Table.OutOfProcessCallbackDll, DllName, sizeof(DllName) - sizeof(WCHAR), &Done)) { GenAccumulateStatus(MDSTATUS_UNABLE_TO_READ_MEMORY); return FALSE; } DllName[Done / sizeof(WCHAR)] = 0; OopDll = LoadLibraryW(DllName); if (!OopDll) { GenAccumulateStatus(MDSTATUS_CALL_FAILED); return FALSE; } OopCb = (POUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK)GetProcAddress (OopDll, OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK_EXPORT_NAME); if (OopCb == NULL) { FreeLibrary(OopDll); GenAccumulateStatus(MDSTATUS_CALL_FAILED); return FALSE; } if (!NT_SUCCESS(OopCb(hProcess, TableAddr, &EntryCount, (PRUNTIME_FUNCTION*)&HeapEntries))) { FreeLibrary(OopDll); GenAccumulateStatus(MDSTATUS_CALL_FAILED); return FALSE; } FreeLibrary(OopDll); } else { EntryCount = Table.EntryCount; } #else EntryCount = Table.EntryCount; #endif IntTable = GenAllocateFunctionTableObject(Table.MinimumAddress, Table.MaximumAddress, #ifdef _ALPHA_ Table.MinimumAddress, #else Table.BaseAddress, #endif EntryCount, &Table); if (IntTable) { #if defined(_AMD64_) || defined(_IA64_) if (Table.Type == RF_CALLBACK) { memcpy(IntTable->RawEntries, HeapEntries, EntryCount * sizeof(RUNTIME_FUNCTION)); } else #endif { if (!ReadProcessMemory(hProcess, Table.FunctionTable, IntTable->RawEntries, EntryCount * sizeof(RUNTIME_FUNCTION), &Done) || Done != EntryCount * sizeof(RUNTIME_FUNCTION)) { GenFreeFunctionTableObject(IntTable); IntTable = NULL; } } } if (HeapEntries) { RtlFreeHeap(RtlProcessHeap(), 0, HeapEntries); } if (!IntTable) { return FALSE; } if (!GenIncludeUnwindInfoMemory(hProcess, DumpType, IntTable)) { return FALSE; } Process->NumberOfFunctionTables++; InsertTailList(&Process->FunctionTableList, &IntTable->TableLink); } return TRUE; #endif // _WIN32_WCE } #ifdef RTL_UNLOAD_EVENT_TRACE_NUMBER typedef PRTL_UNLOAD_EVENT_TRACE (*FN_RtlGetUnloadEventTrace) ( VOID ); #endif BOOL NtxGetUnloadedModules( IN HANDLE hProcess, IN PINTERNAL_PROCESS Process, IN ULONG DumpType ) { #if defined(_WIN32_WCE) || !defined(RTL_UNLOAD_EVENT_TRACE_NUMBER) return FALSE; #else HMODULE NtDll; FN_RtlGetUnloadEventTrace GetTrace = NULL; PRTL_UNLOAD_EVENT_TRACE TraceAddr; PRTL_UNLOAD_EVENT_TRACE TraceArray; SIZE_T Done; ULONG Entries; PRTL_UNLOAD_EVENT_TRACE Oldest; ULONG i; PINTERNAL_UNLOADED_MODULE IntModule; if (!(DumpType & MiniDumpWithUnloadedModules)) { // No unloaded module info requested. return TRUE; } // // On systems that support unload traces // ntdll exports a function called RtlGetUnloadEventTrace // to retrieve the base of an unload trace array. // Currently this is always a global in ntdll // and so is at the same address in all processes since ntdll // is mapped at the same address in every process. This // means we can call it in our process and get a pointer // that's valid in the process being dumped. // // We also use the presence of RGUET as a signal of // whether unload traces are supported or not. // NtDll = GetModuleHandle("ntdll"); if (NtDll) { GetTrace = (FN_RtlGetUnloadEventTrace) GetProcAddress(NtDll, "RtlGetUnloadEventTrace"); } if (!GetTrace) { // Unload traces are not supported. return TRUE; } TraceAddr = GetTrace(); // Currently there are always 16 entries. Entries = 16; TraceArray = (PRTL_UNLOAD_EVENT_TRACE) AllocMemory(sizeof(*TraceArray) * Entries); if (!TraceArray) { return FALSE; } if (!ReadProcessMemory(hProcess, TraceAddr, TraceArray, sizeof(*TraceArray) * Entries, &Done) || Done != sizeof(*TraceArray) * Entries) { GenAccumulateStatus(MDSTATUS_UNABLE_TO_READ_MEMORY); return FALSE; } // // Find the true number of entries in use and sort. // The sequence numbers of the trace records increase with // time and we want to have the head of the list be the // most recent record, so sort by decreasing sequence number. // We know that the array is a circular buffer, so things // are already in order except there may be a transition // of sequence after the newest record. Find that transition // and sorting becomes trivial. // Oldest = TraceArray; for (i = 0; i < Entries; i++) { if (!TraceArray[i].BaseAddress || !TraceArray[i].SizeOfImage) { // Unused entry, no need to continue. Entries = i; break; } if (TraceArray[i].Sequence < Oldest->Sequence) { Oldest = TraceArray + i; } } // // Now push the entries on from the oldest to the youngest. // for (i = 0; i < Entries; i++) { IntModule = GenAllocateUnloadedModuleObject(Oldest->ImageName, (ULONG_PTR)Oldest->BaseAddress, (ULONG)Oldest->SizeOfImage, Oldest->CheckSum, Oldest->TimeDateStamp); if (!IntModule) { return FALSE; } Process->NumberOfUnloadedModules++; InsertHeadList(&Process->UnloadedModuleList, &IntModule->ModulesLink); if (Oldest == TraceArray + (Entries - 1)) { Oldest = TraceArray; } else { Oldest++; } } return TRUE; #endif // _WIN32_WCE || !RTL_UNLOAD_EVENT_TRACE_NUMBER } BOOL NtxGetProcessInfo( 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. Return Values: TRUE - Success. FALSE - Failure: Environment: Win9x/Win2k+ only. --*/ { BOOL Succ; ULONG i; BOOL MoreThreads; HANDLE Snapshot, ModuleSnapshot = INVALID_HANDLE_VALUE; THREADENTRY32 ThreadInfo; PINTERNAL_THREAD Thread; PINTERNAL_PROCESS Process; PINTERNAL_MODULE Module; HMODULE Modules [ 512 ]; ULONG ModulesSize; ULONG NumberOfModules; ULONG BuildNumber; ASSERT ( hProcess ); ASSERT ( ProcessId != 0 ); ASSERT ( ProcessRet ); Process = NULL; Thread = NULL; Module = NULL; Snapshot = NULL; ThreadInfo.dwSize = sizeof (THREADENTRY32); Process = GenAllocateProcessObject ( hProcess, ProcessId ); if ( Process == NULL ) { return FALSE; } Snapshot = CreateToolhelp32Snapshot ( TH32CS_SNAPTHREAD, ProcessId ); if ( Snapshot == INVALID_HANDLE_VALUE ) { Succ = FALSE; GenAccumulateStatus(MDSTATUS_CALL_FAILED); 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); } } GenGetSystemType (NULL, NULL, NULL, NULL, &BuildNumber); if (BuildNumber > 2468) { // // toolhelp had been changed to perform noninvasive // module enumeration // MODULEENTRY32W ModuleEntry; BOOL ModuleFound; NumberOfModules = 0; Succ = TRUE; ModuleSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, ProcessId ); if (ModuleSnapshot == INVALID_HANDLE_VALUE) { Succ = FALSE; GenAccumulateStatus(MDSTATUS_CALL_FAILED); goto Exit; } ZeroMemory(&ModuleEntry, sizeof(ModuleEntry)); ModuleEntry.dwSize = sizeof(ModuleEntry); ModuleFound = Module32FirstW(ModuleSnapshot, &ModuleEntry); while (ModuleFound) { ULONG WriteFlags; if (GenExecuteIncludeModuleCallback(hProcess, ProcessId, DumpType, (LONG_PTR)ModuleEntry.modBaseAddr, CallbackRoutine, CallbackParam, &WriteFlags) && IsFlagSet(WriteFlags, ModuleWriteModule)) { Module = NtxAllocateModuleObject (Process, Process->ProcessHandle, (LONG_PTR)ModuleEntry.modBaseAddr, DumpType, WriteFlags, ModuleEntry.szExePath); if ( Module == NULL ) { Succ = FALSE; goto Exit; } InsertTailList (&Process->ModuleList, &Module->ModulesLink); ++NumberOfModules; } ModuleFound = Module32NextW(ModuleSnapshot, &ModuleEntry); } } else { // // Walk module list, getting module information. Use PSAPI instead of // toolhelp since it it does not exhibit the deadlock issues with // the loader lock. ( on old versions of os ) // ModulesSize = 0; Succ = EnumProcessModules ( Process->ProcessHandle, Modules, sizeof (Modules), &ModulesSize ); if ( !Succ ) { GenAccumulateStatus(MDSTATUS_CALL_FAILED); goto Exit; } NumberOfModules = ModulesSize / sizeof (HMODULE); for (i = 0; i < NumberOfModules; i++) { ULONG WriteFlags; if (!GenExecuteIncludeModuleCallback(hProcess, ProcessId, DumpType, (LONG_PTR)Modules[i], CallbackRoutine, CallbackParam, &WriteFlags) || IsFlagClear(WriteFlags, ModuleWriteModule)) { continue; } Module = NtxAllocateModuleObject ( Process, Process->ProcessHandle, (LONG_PTR) Modules [ i ], DumpType, WriteFlags, NULL ); if ( Module == NULL ) { Succ = FALSE; goto Exit; } InsertTailList (&Process->ModuleList, &Module->ModulesLink); } } Process->NumberOfModules = NumberOfModules; Succ = NtxGetFunctionTables(hProcess, Process, DumpType); // If we can't get unloaded modules that's not a critical problem. NtxGetUnloadedModules(hProcess, Process, DumpType); Exit: if ( Snapshot && (Snapshot != INVALID_HANDLE_VALUE) ) { CloseHandle ( Snapshot ); Snapshot = NULL; } if ( ModuleSnapshot && (ModuleSnapshot != INVALID_HANDLE_VALUE) ) { CloseHandle ( ModuleSnapshot ); ModuleSnapshot = NULL; } if ( !Succ && Process != NULL ) { GenFreeProcessObject ( Process ); Process = NULL; } *ProcessRet = Process; return Succ; } LPVOID NtxGetTebAddress( IN HANDLE Thread, OUT PULONG SizeOfTeb ) { #ifdef _WIN32_WCE *SizeOfTeb = 0; return NULL; #else THREAD_BASIC_INFORMATION ThreadInformation; NTSTATUS NtStatus; NtStatus = NtQueryInformationThread(Thread, ThreadBasicInformation, &ThreadInformation, sizeof(ThreadInformation), NULL); if (NT_SUCCESS(NtStatus)) { // The TEB is a little smaller than a page but // save the entire page so that adjacent TEB // pages get coalesced into a single region. // As TEBs are normally adjacent this is a common case. *SizeOfTeb = PAGE_SIZE; return ThreadInformation.TebBaseAddress; } else { *SizeOfTeb = 0; return NULL; } #endif } HRESULT TibGetThreadInfo( IN HANDLE Process, IN LPVOID TibBase, OUT PULONG64 StackBase, OUT PULONG64 StackLimit, OUT PULONG64 StoreBase, OUT PULONG64 StoreLimit ) { #ifdef _WIN32_WCE return E_NOTIMPL; #else TEB Teb; HRESULT Succ; SIZE_T BytesRead; #if defined (DUMP_BACKING_STORE) Succ = ReadProcessMemory(Process, TibBase, &Teb, sizeof (Teb), &BytesRead) ? S_OK : E_FAIL; if ( Succ != S_OK || BytesRead != sizeof (Teb) ) { return E_FAIL; } *StoreBase = SIGN_EXTEND(BSTORE_BASE(&Teb)); *StoreLimit = SIGN_EXTEND(BSTORE_LIMIT(&Teb)); #else Succ = ReadProcessMemory(Process, TibBase, &Teb, sizeof (Teb.NtTib), &BytesRead) ? S_OK : E_FAIL; if ( Succ != S_OK || BytesRead != sizeof (Teb.NtTib) ) { return E_FAIL; } *StoreBase = 0; *StoreLimit = 0; #endif *StackBase = SIGN_EXTEND((LONG_PTR)Teb.NtTib.StackBase); *StackLimit = SIGN_EXTEND((LONG_PTR)Teb.NtTib.StackLimit); return S_OK; #endif // #ifdef _WIN32_WCE } LPVOID NtxGetPebAddress( IN HANDLE Process, OUT PULONG SizeOfPeb ) { #ifdef _WIN32_WCE *SizeOfPeb = 0; return NULL; #else PROCESS_BASIC_INFORMATION Information; NTSTATUS NtStatus; NtStatus = NtQueryInformationProcess(Process, ProcessBasicInformation, &Information, sizeof(Information), NULL); if (NT_SUCCESS(NtStatus)) { *SizeOfPeb = sizeof(PEB); return Information.PebBaseAddress; } else { *SizeOfPeb = 0; return NULL; } #endif } BOOL NtxWriteHandleData( IN HANDLE ProcessHandle, IN HANDLE hFile, IN struct _MINIDUMP_STREAM_INFO * StreamInfo ) { #ifdef _WIN32_WCE return FALSE; #else NTSTATUS NtStatus; ULONG HandleCount; ULONG Hits; ULONG Handle; ULONG64 Buffer[1024 / sizeof(ULONG64)]; POBJECT_TYPE_INFORMATION TypeInfo = (POBJECT_TYPE_INFORMATION)Buffer; POBJECT_NAME_INFORMATION NameInfo = (POBJECT_NAME_INFORMATION)Buffer; OBJECT_BASIC_INFORMATION BasicInfo; HANDLE Dup; PMINIDUMP_HANDLE_DESCRIPTOR Descs, Desc; RVA Rva; ULONG32 Len; ULONG Done; MINIDUMP_HANDLE_DATA_STREAM DataStream; BOOL Succ; NtStatus = NtQueryInformationProcess(ProcessHandle, ProcessHandleCount, &HandleCount, sizeof(HandleCount), NULL); if (!NT_SUCCESS(NtStatus)) { return FALSE; } Descs = AllocMemory(HandleCount * sizeof(*Desc)); if (Descs == NULL) { return FALSE; } Hits = 0; Handle = 0; Desc = Descs; Rva = StreamInfo->RvaOfHandleData; while (Hits < HandleCount && Handle < (1 << 24)) { if (!DuplicateHandle(ProcessHandle, (HANDLE)(ULONG_PTR)Handle, GetCurrentProcess(), &Dup, 0, FALSE, DUPLICATE_SAME_ACCESS)) { Handle += 4; continue; } // Successfully got a handle, so consider this a hit. Hits++; if (!NT_SUCCESS(NtQueryObject(Dup, ObjectBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL)) || !NT_SUCCESS(NtQueryObject(Dup, ObjectTypeInformation, TypeInfo, sizeof(Buffer), NULL))) { // If we can't get the basic info and type there isn't much // point in writing anything out so skip the handle. goto CloseDup; } Len = TypeInfo->TypeName.Length; TypeInfo->TypeName.Buffer[Len / sizeof(WCHAR)] = 0; Desc->TypeNameRva = Rva; if (!WriteFile(hFile, &Len, sizeof(Len), &Done, NULL) || Done != sizeof(Len)) { goto ExitCloseDup; } Len += sizeof(WCHAR); if (!WriteFile(hFile, TypeInfo->TypeName.Buffer, Len, &Done, NULL) || Done != Len) { goto ExitCloseDup; } Rva += Len + sizeof(Len); // Don't get the name of file objects as it // can cause deadlocks. If we fail getting the // name just leave it out and don't consider it fatal. if (lstrcmpW(TypeInfo->TypeName.Buffer, L"File") && NT_SUCCESS(NtQueryObject(Dup, ObjectNameInformation, NameInfo, sizeof(Buffer), NULL)) && NameInfo->Name.Buffer != NULL) { Len = NameInfo->Name.Length; NameInfo->Name.Buffer[Len / sizeof(WCHAR)] = 0; Desc->ObjectNameRva = Rva; if (!WriteFile(hFile, &Len, sizeof(Len), &Done, NULL) || Done != sizeof(Len)) { goto ExitCloseDup; } Len += sizeof(WCHAR); if (!WriteFile(hFile, NameInfo->Name.Buffer, Len, &Done, NULL) || Done != Len) { goto ExitCloseDup; } Rva += Len + sizeof(Len); } else { Desc->ObjectNameRva = 0; } Desc->Handle = Handle; Desc->Attributes = BasicInfo.Attributes; Desc->GrantedAccess = BasicInfo.GrantedAccess; Desc->HandleCount = BasicInfo.HandleCount; Desc->PointerCount = BasicInfo.PointerCount; Desc++; CloseDup: CloseHandle(Dup); Handle += 4; } DataStream.SizeOfHeader = sizeof(DataStream); DataStream.SizeOfDescriptor = sizeof(*Descs); DataStream.NumberOfDescriptors = (ULONG)(Desc - Descs); DataStream.Reserved = 0; StreamInfo->RvaOfHandleData = Rva; StreamInfo->SizeOfHandleData = sizeof(DataStream) + DataStream.NumberOfDescriptors * sizeof(*Descs); Succ = WriteFile(hFile, &DataStream, sizeof(DataStream), &Done, NULL) && Done == sizeof(DataStream) && WriteFile(hFile, Descs, DataStream.NumberOfDescriptors * sizeof(*Descs), &Done, NULL) && Done == DataStream.NumberOfDescriptors * sizeof(*Descs); FreeMemory(Descs); return Succ; ExitCloseDup: CloseHandle(Dup); FreeMemory(Descs); return FALSE; #endif // #ifdef _WIN32_WCE }