//---------------------------------------------------------------------------- // // IDebugDataSpaces implementations. // // Copyright (C) Microsoft Corporation, 1999-2002. // //---------------------------------------------------------------------------- #include "ntsdp.hpp" //---------------------------------------------------------------------------- // // TargetInfo data space methods. // //---------------------------------------------------------------------------- void TargetInfo::NearestDifferentlyValidOffsets(ULONG64 Offset, PULONG64 NextOffset, PULONG64 NextPage) { // // In the default case we assume that address validity // is controlled on a per-page basis so the next possibly // valid page and offset are both the offset of the next // page. // ULONG64 Page = NEXT_PAGE(m_Machine, Offset); if (NextOffset != NULL) { *NextOffset = Page; } if (NextPage != NULL) { *NextPage = Page; } } HRESULT TargetInfo::ReadVirtualUncached( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { return ReadVirtual(Process, Offset, Buffer, BufferSize, BytesRead); } HRESULT TargetInfo::WriteVirtualUncached( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { return WriteVirtual(Process, Offset, Buffer, BufferSize, BytesWritten); } // #define DBG_SEARCH HRESULT TargetInfo::SearchVirtual( IN ProcessInfo* Process, IN ULONG64 Offset, IN ULONG64 Length, IN PVOID Pattern, IN ULONG PatternSize, IN ULONG PatternGranularity, OUT PULONG64 MatchOffset ) { HRESULT Status; ULONG64 SearchEnd; UCHAR Buffer[4096]; PUCHAR Buf, Pat, BufEnd, PatEnd; ULONG ReadLen; ULONG64 BufOffset; ULONG64 PatOffset; ULONG64 StartOffset; SearchEnd = Offset + Length; Buf = Buffer; BufEnd = Buffer; Pat = (PUCHAR)Pattern; PatEnd = Pat + PatternSize; ReadLen = Length < sizeof(Buffer) ? (ULONG)Length : sizeof(Buffer); BufOffset = Offset; PatOffset = Offset; StartOffset = Offset; #ifdef DBG_SEARCH g_NtDllCalls.DbgPrint("Search %d bytes from %I64X to %I64X, gran %X\n", PatternSize, Offset, SearchEnd - 1, Granularity); #endif for (;;) { #ifdef DBG_SEARCH_VERBOSE g_NtDllCalls.DbgPrint(" %I64X: matched %d\n", Offset + (Buf - Buffer), (ULONG)(Pat - (PUCHAR)Pattern)); #endif if (Pat == PatEnd) { // Made it to the end of the pattern so there's // a match. *MatchOffset = PatOffset; Status = S_OK; break; } if (Buf >= BufEnd) { ULONG Read; // Ran out of buffered memory so get some more. for (;;) { if (CheckUserInterrupt()) { WarnOut("-- Memory search interrupted at %s\n", FormatAddr64(Offset)); Status = HRESULT_FROM_NT(STATUS_CONTROL_C_EXIT); goto Exit; } if (Offset >= SearchEnd) { // Return a result code that's specific and // consistent with the kernel version. Status = HRESULT_FROM_NT(STATUS_NO_MORE_ENTRIES); goto Exit; } Status = ReadVirtual(Process, Offset, Buffer, ReadLen, &Read); #ifdef DBG_SEARCH g_NtDllCalls.DbgPrint(" Read %X bytes at %I64X, ret %X:%X\n", ReadLen, Offset, Status, Read); #endif if (Status != S_OK) { // Skip to the start of the next page. NearestDifferentlyValidOffsets(Offset, NULL, &Offset); // Restart search due to the address discontinuity. Pat = (PUCHAR)Pattern; PatOffset = Offset; } else { break; } } Buf = Buffer; BufEnd = Buffer + Read; BufOffset = Offset; Offset += Read; } // If this is the first byte of the pattern it // must match on a granularity boundary. if (*Buf++ == *Pat && (Pat != (PUCHAR)Pattern || (((PatOffset - StartOffset) % PatternGranularity) == 0))) { Pat++; } else { Buf -= Pat - (PUCHAR)Pattern; Pat = (PUCHAR)Pattern; PatOffset = BufOffset + (Buf - Buffer); } } Exit: return Status; } ULONG HammingDistance(ULONG64 Left, ULONG64 Right, ULONG WordLength) { ULONG64 Value; ULONG Index; ULONG Distance; Value = Left ^ Right; Distance = 0; for (Index = 0; Index < 8 * WordLength; Index++) { if ((Value & 1)) { Distance++; } Value >>= 1; } return Distance; } HRESULT TargetInfo::PointerSearchPhysical( IN ULONG64 Offset, IN ULONG64 Length, IN ULONG64 PointerMin, IN ULONG64 PointerMax, IN ULONG Flags, OUT PULONG64 MatchOffsets, IN ULONG MatchOffsetsSize, OUT PULONG MatchOffsetsCount ) { HRESULT Status; ULONG ReadSize; if (Flags & PTR_SEARCH_PHYS_PTE) { ReadSize = m_TypeInfo.SizePte; } else { if (m_Machine->m_Ptr64) { ReadSize = 8; } else { ReadSize = 4; PointerMin &= 0xffffffff; PointerMax &= 0xffffffff; } } // Make sure things are aligned properly. if ((Offset & (ReadSize - 1)) || (Length & (ReadSize - 1))) { return E_INVALIDARG; } ULONG Hits; Hits = 0; Status = S_OK; while (Length > 0) { ULONG64 Data; BOOL Hit; if (g_EngStatus & ENG_STATUS_USER_INTERRUPT) { Status = HRESULT_FROM_NT(STATUS_CONTROL_C_EXIT); // Leave the interrupt flag on so that !search // interrupts itself. } Data = 0; if ((Status = ReadAllPhysical(Offset, &Data, ReadSize)) != S_OK) { return Status; } Hit = FALSE; if (Flags & PTR_SEARCH_PHYS_PTE) { ULONG64 Pfn; ULONG PteFlags; m_Machine->DecodePte(Data, &Pfn, &PteFlags); if (Pfn == PointerMin) { Hit = TRUE; } } else { if ((Data >= PointerMin && Data <= PointerMax) || HammingDistance(Data, PointerMin, ReadSize) == 1) { Hit = TRUE; } } if (Hit) { if (Hits < MatchOffsetsSize) { MatchOffsets[Hits] = Offset; } Hits++; if (!(Flags & PTR_SEARCH_PHYS_ALL_HITS)) { ULONG64 ToNextPage; // // The caller has asked for just the first // hit per page, so we can skip to the next page. // ToNextPage = NEXT_PAGE(m_Machine, Offset); if (ToNextPage == 0) { // Wrapped around, so we're done. break; } ToNextPage -= Offset; if (ToNextPage > Length) { ToNextPage = Length; } Offset += ToNextPage; Length -= ToNextPage; continue; } } Offset += ReadSize; Length -= ReadSize; } if (MatchOffsetsCount) { *MatchOffsetsCount = Hits; } return Status; } HRESULT TargetInfo::ReadPhysicalUncached( THIS_ IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, IN ULONG Flags, OUT PULONG BytesRead ) { return ReadPhysical(Offset, Buffer, BufferSize, Flags, BytesRead); } HRESULT TargetInfo::WritePhysicalUncached( THIS_ IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, IN ULONG Flags, OUT PULONG BytesWritten ) { return WritePhysical(Offset, Buffer, BufferSize, Flags, BytesWritten); } HRESULT TargetInfo::ReadHandleData( IN ProcessInfo* Process, IN ULONG64 Handle, IN ULONG DataType, OUT OPTIONAL PVOID Buffer, IN ULONG BufferSize, OUT OPTIONAL PULONG DataSize ) { // Base implementation which silently fails for modes // where there is no way to retrieve handle data. return E_UNEXPECTED; } HRESULT TargetInfo::FillVirtual( THIS_ IN ProcessInfo* Process, IN ULONG64 Start, IN ULONG Size, IN PVOID Pattern, IN ULONG PatternSize, OUT PULONG Filled ) { HRESULT Status = S_OK; PUCHAR Pat = (PUCHAR)Pattern; PUCHAR PatEnd = Pat + PatternSize; *Filled = 0; while (Size-- > 0) { ULONG Done; if (CheckUserInterrupt()) { dprintf("User interrupt during fill - exiting.\n"); Status = HRESULT_FROM_NT(STATUS_CONTROL_C_EXIT); *Filled = 0; break; } if ((Status = WriteVirtual(Process, Start, Pat, 1, &Done)) != S_OK) { break; } if (Done != 1) { Status = HRESULT_FROM_WIN32(ERROR_WRITE_FAULT); break; } Start++; if (++Pat == PatEnd) { Pat = (PUCHAR)Pattern; } (*Filled)++; } // If nothing was filled return an error, otherwise // consider it a success. return *Filled > 0 ? S_OK : Status; } HRESULT TargetInfo::FillPhysical( THIS_ IN ULONG64 Start, IN ULONG Size, IN PVOID Pattern, IN ULONG PatternSize, OUT PULONG Filled ) { HRESULT Status = S_OK; PUCHAR Pat = (PUCHAR)Pattern; PUCHAR PatEnd = Pat + PatternSize; *Filled = 0; while (Size-- > 0) { ULONG Done; if (CheckUserInterrupt()) { dprintf("User interrupt during fill - exiting.\n"); Status = HRESULT_FROM_NT(STATUS_CONTROL_C_EXIT); *Filled = 0; break; } if ((Status = WritePhysical(Start, Pat, 1, PHYS_FLAG_DEFAULT, &Done)) != S_OK) { break; } if (Done != 1) { Status = HRESULT_FROM_WIN32(ERROR_WRITE_FAULT); break; } Start++; if (++Pat == PatEnd) { Pat = (PUCHAR)Pattern; } (*Filled)++; } // If nothing was filled return an error, otherwise // consider it a success. return *Filled > 0 ? S_OK : Status; } HRESULT TargetInfo::GetProcessorId(ULONG Processor, PDEBUG_PROCESSOR_IDENTIFICATION_ALL Id) { // Base implementation which silently fails for modes // where the ID cannot be retrieved. return E_UNEXPECTED; } HRESULT TargetInfo::GetProcessorSpeed(ULONG Processor, PULONG Speed) { // Base implementation which silently fails for modes // where the speed cannot be retrieved. return E_UNEXPECTED; } HRESULT TargetInfo::GetGenericProcessorFeatures( ULONG Processor, PULONG64 Features, ULONG FeaturesSize, PULONG Used ) { // Base implementation which silently fails for modes // where the information cannot be retrieved. return E_UNEXPECTED; } HRESULT TargetInfo::GetSpecificProcessorFeatures( ULONG Processor, PULONG64 Features, ULONG FeaturesSize, PULONG Used ) { // Base implementation which silently fails for modes // where the information cannot be retrieved. return E_UNEXPECTED; } HRESULT TargetInfo::GetTaggedBaseOffset(PULONG64 Offset) { // Base implementation silently fails for // targets with no tagged data. return E_NOINTERFACE; } HRESULT TargetInfo::ReadTagged(ULONG64 Offset, PVOID Buffer, ULONG BufferSize) { // Base implementation silently fails for // targets with no tagged data. return E_NOINTERFACE; } HRESULT TargetInfo::ReadPageFile(ULONG PfIndex, ULONG64 PfOffset, PVOID Buffer, ULONG Size) { // Default implementation for targets which do not // support reading the page file. return HR_PAGE_NOT_AVAILABLE; } HRESULT TargetInfo::GetUnloadedModuleListHead(ProcessInfo* Process, PULONG64 Head) { // Get the address of the dynamic function table list head which is the // the same for all processes. This only has to be done once. if (Process->m_RtlUnloadList) { *Head = Process->m_RtlUnloadList; return S_OK; } GetOffsetFromSym(Process, "ntdll!RtlpUnloadEventTrace", &Process->m_RtlUnloadList, NULL); if (!Process->m_RtlUnloadList) { // No error message here as it's a common case when // symbols are bad. return E_NOINTERFACE; } *Head = Process->m_RtlUnloadList; return S_OK; } HRESULT TargetInfo::GetFunctionTableListHead(ProcessInfo* Process, PULONG64 Head) { // Get the address of the dynamic function table list head which is the // the same for all processes. This only has to be done once. if (Process->m_DynFuncTableList) { *Head = Process->m_DynFuncTableList; return S_OK; } GetOffsetFromSym(Process, "ntdll!RtlpDynamicFunctionTable", &Process->m_DynFuncTableList, NULL); if (!Process->m_DynFuncTableList) { // No error message here as it's a common case when // symbols are bad. return E_NOINTERFACE; } *Head = Process->m_DynFuncTableList; return S_OK; } // These procedures support dynamic function table entries for user-mode // run-time code. Dynamic function tables are stored in a linked list // inside ntdll. The address of the linked list head is returned by // RtlGetFunctionTableListHead. Since dynamic function tables are // only supported in user-mode the address of the list head will be // the same in all processes. Dynamic function tables are very rare, // so in most cases this the list will be unitialized and this routine // will return NULL. dbghelp only calls this when it // is unable to find a function entry in any of the images. PVOID TargetInfo::FindDynamicFunctionEntry(ProcessInfo* Process, ULONG64 Address) { ULONG64 ListHeadAddr; LIST_ENTRY64 DynamicFunctionTableHead; ULONG64 Entry; if (GetFunctionTableListHead(Process, &ListHeadAddr) != S_OK) { return NULL; } // Read the dynamic function table list head if (ReadListEntry(Process, m_Machine, ListHeadAddr, &DynamicFunctionTableHead) != S_OK) { // This failure happens almost all the time in minidumps // because the function table list symbol can be resolved // but the memory isn't part of the minidump. if (!IS_USER_MINI_DUMP(this)) { ErrOut("Unable to read dynamic function table list head\n"); } return NULL; } Entry = DynamicFunctionTableHead.Flink; // The list head is initialized the first time it's used so check // for an uninitialized pointers. This is the most common result. if (Entry == 0) { return NULL; } // Loop through the dynamic function table list reading the headers. // If the range of a dynamic function table contains Address then // search the function table. Dynamic function table ranges are not // mututally exclusive like those in images so an address may be // in more than one range. However, there can be only one dynamic function // entry that contains the address (if there are any at all). while (Entry != ListHeadAddr) { ULONG64 Table, MinAddress, MaxAddress, BaseAddress, TableData; ULONG TableSize; WCHAR OutOfProcessDll[MAX_PATH]; CROSS_PLATFORM_DYNAMIC_FUNCTION_TABLE RawTable; PVOID FunctionTable; PVOID FunctionEntry; Table = Entry; if (m_Machine-> ReadDynamicFunctionTable(Process, Table, &Entry, &MinAddress, &MaxAddress, &BaseAddress, &TableData, &TableSize, OutOfProcessDll, &RawTable) != S_OK) { ErrOut("Unable to read dynamic function table entry\n"); continue; } if (Address >= MinAddress && Address < MaxAddress && (OutOfProcessDll[0] || (TableData && TableSize > 0))) { if (OutOfProcessDll[0]) { if (ReadOutOfProcessDynamicFunctionTable (Process, OutOfProcessDll, Table, &TableSize, &FunctionTable) != S_OK) { ErrOut("Unable to read dynamic function table entries\n"); continue; } } else { FunctionTable = malloc(TableSize); if (FunctionTable == NULL) { ErrOut("Unable to allocate memory for " "dynamic function table\n"); continue; } // Read the dynamic function table if (ReadAllVirtual(Process, TableData, FunctionTable, TableSize) != S_OK) { ErrOut("Unable to read dynamic function table entries\n"); free(FunctionTable); continue; } } FunctionEntry = m_Machine-> FindDynamicFunctionEntry(&RawTable, Address, FunctionTable, TableSize); free(FunctionTable); if (FunctionEntry) { return FunctionEntry; } } } return NULL; } ULONG64 TargetInfo::GetDynamicFunctionTableBase(ProcessInfo* Process, ULONG64 Address) { LIST_ENTRY64 ListHead; ULONG64 Entry; // If the process dynamic function table list head hasn't // been looked up yet that means that no dynamic function // table entry could be in use yet, so there's no need to look. if (!Process->m_DynFuncTableList) { return 0; } if (ReadListEntry(Process, m_Machine, Process->m_DynFuncTableList, &ListHead) != S_OK) { return 0; } Entry = ListHead.Flink; // The list head is initialized the first time it's used so check // for an uninitialized pointers. This is the most common result. if (Entry == 0) { return 0; } // Loop through the dynamic function table list reading the headers. // If the range of a dynamic function table contains Address then // return the function table's base. while (Entry != Process->m_DynFuncTableList) { ULONG64 MinAddress, MaxAddress, BaseAddress, TableData; ULONG TableSize; WCHAR OutOfProcessDll[MAX_PATH]; CROSS_PLATFORM_DYNAMIC_FUNCTION_TABLE RawTable; if (m_Machine-> ReadDynamicFunctionTable(Process, Entry, &Entry, &MinAddress, &MaxAddress, &BaseAddress, &TableData, &TableSize, OutOfProcessDll, &RawTable) == S_OK && Address >= MinAddress && Address < MaxAddress) { return BaseAddress; } } return 0; } HRESULT TargetInfo::ReadOutOfProcessDynamicFunctionTable(ProcessInfo* Process, PWSTR Dll, ULONG64 Table, PULONG TableSize, PVOID* TableData) { // Empty base implementation to avoid error messages // that would be produced by an UNEXPECTED_HR implementation. return E_UNEXPECTED; } PVOID CALLBACK TargetInfo::DynamicFunctionTableCallback(HANDLE ProcessHandle, ULONG64 Address, ULONG64 Context) { ProcessInfo* Process = (ProcessInfo*)Context; DBG_ASSERT(ProcessHandle == OS_HANDLE(Process->m_SysHandle)); return Process->m_Target-> FindDynamicFunctionEntry(Process, Address); } HRESULT TargetInfo::EnumFunctionTables(IN ProcessInfo* Process, IN OUT PULONG64 Start, IN OUT PULONG64 Handle, OUT PULONG64 MinAddress, OUT PULONG64 MaxAddress, OUT PULONG64 BaseAddress, OUT PULONG EntryCount, OUT PCROSS_PLATFORM_DYNAMIC_FUNCTION_TABLE RawTable, OUT PVOID* RawEntries) { HRESULT Status; if (*Start == 0) { LIST_ENTRY64 DynamicFunctionTableHead; if ((Status = GetFunctionTableListHead(Process, Start)) != S_OK) { return Status; } if ((Status = ReadListEntry(Process, m_Machine, *Start, &DynamicFunctionTableHead)) != S_OK) { return Status; } *Handle = DynamicFunctionTableHead.Flink; } if (!*Handle || *Handle == *Start) { return S_FALSE; } ULONG64 Table, TableData; ULONG TableSize; WCHAR OutOfProcessDll[MAX_PATH]; Table = *Handle; if ((Status = m_Machine-> ReadDynamicFunctionTable(Process, Table, Handle, MinAddress, MaxAddress, BaseAddress, &TableData, &TableSize, OutOfProcessDll, RawTable)) != S_OK) { return Status; } if (OutOfProcessDll[0]) { if ((Status = ReadOutOfProcessDynamicFunctionTable (Process, OutOfProcessDll, Table, &TableSize, RawEntries)) != S_OK) { return Status; } } else { *RawEntries = malloc(TableSize); if (!*RawEntries) { return E_OUTOFMEMORY; } // Read the dynamic function table if ((Status = ReadAllVirtual(Process, TableData, *RawEntries, TableSize)) != S_OK) { free(*RawEntries); return Status; } } *EntryCount = TableSize / m_TypeInfo.SizeRuntimeFunction; return S_OK; } HRESULT TargetInfo::QueryAddressInformation(ProcessInfo* Process, ULONG64 Address, ULONG InSpace, PULONG OutSpace, PULONG OutFlags) { // Default implementation which just returns the // least restrictive settings. *OutSpace = (Process && IS_KERNEL_TARGET(this)) ? DBGKD_QUERY_MEMORY_KERNEL : DBGKD_QUERY_MEMORY_PROCESS; *OutFlags = DBGKD_QUERY_MEMORY_READ | DBGKD_QUERY_MEMORY_WRITE | DBGKD_QUERY_MEMORY_EXECUTE; return S_OK; } HRESULT TargetInfo::ReadPointer( ProcessInfo* Process, MachineInfo* Machine, ULONG64 Address, PULONG64 Pointer64 ) { HRESULT Status; ULONG Result; ULONG SizeToRead; ULONG Pointer32; ULONG64 Data; if (Machine->m_Ptr64) { SizeToRead = sizeof(ULONG64); Status = ReadVirtual(Process, Address, &Data, SizeToRead, &Result); } else { SizeToRead = sizeof(ULONG32); Status = ReadVirtual(Process, Address, &Pointer32, SizeToRead, &Result); Data = EXTEND64(Pointer32); } if (Status != S_OK) { return Status; } if (Result != SizeToRead) { return E_FAIL; } *Pointer64 = Data; return S_OK; } HRESULT TargetInfo::WritePointer( ProcessInfo* Process, MachineInfo* Machine, ULONG64 Address, ULONG64 Pointer64 ) { HRESULT Status; ULONG Result; ULONG SizeToWrite; ULONG Pointer32; if (Machine->m_Ptr64) { SizeToWrite = sizeof(ULONG64); Status = WriteVirtual(Process, Address, &Pointer64, SizeToWrite, &Result); } else { SizeToWrite = sizeof(ULONG32); Pointer32 = (ULONG)Pointer64; Status = WriteVirtual(Process, Address, &Pointer32, SizeToWrite, &Result); } if (Status != S_OK) { return Status; } if (Result != SizeToWrite) { return HRESULT_FROM_WIN32(ERROR_WRITE_FAULT); } return S_OK; } HRESULT TargetInfo::ReadListEntry( ProcessInfo* Process, MachineInfo* Machine, ULONG64 Address, PLIST_ENTRY64 List64 ) { HRESULT Status; ULONG Result; ULONG SizeToRead; LIST_ENTRY32 List32; if (Machine->m_Ptr64) { SizeToRead = sizeof(LIST_ENTRY64); Status = ReadVirtual(Process, Address, List64, SizeToRead, &Result); } else { SizeToRead = sizeof(LIST_ENTRY32); Status = ReadVirtual(Process, Address, &List32, SizeToRead, &Result); } if (Status != S_OK) { return Status; } if (Result != SizeToRead) { return E_FAIL; } if (!Machine->m_Ptr64) { List64->Flink = EXTEND64(List32.Flink); List64->Blink = EXTEND64(List32.Blink); } return S_OK; } void ConvertLoaderEntry32To64( PKLDR_DATA_TABLE_ENTRY32 b32, PKLDR_DATA_TABLE_ENTRY64 b64 ) { #define COPYSE2(p64,s32,f) p64->f = (ULONG64)(LONG64)(LONG)s32->f COPYSE2(b64,b32,InLoadOrderLinks.Flink); COPYSE2(b64,b32,InLoadOrderLinks.Blink); COPYSE2(b64,b32,__Undefined1); COPYSE2(b64,b32,__Undefined2); COPYSE2(b64,b32,__Undefined3); COPYSE2(b64,b32,NonPagedDebugInfo); COPYSE2(b64,b32,DllBase); COPYSE2(b64,b32,EntryPoint); b64->SizeOfImage = b32->SizeOfImage; b64->FullDllName.Length = b32->FullDllName.Length; b64->FullDllName.MaximumLength = b32->FullDllName.MaximumLength; COPYSE2(b64,b32,FullDllName.Buffer); b64->BaseDllName.Length = b32->BaseDllName.Length; b64->BaseDllName.MaximumLength = b32->BaseDllName.MaximumLength; COPYSE2(b64,b32,BaseDllName.Buffer); b64->Flags = b32->Flags; b64->LoadCount = b32->LoadCount; b64->__Undefined5 = b32->__Undefined5; COPYSE2(b64,b32,__Undefined6); b64->CheckSum = b32->CheckSum; b64->TimeDateStamp = b32->TimeDateStamp; #undef COPYSE2 return; } HRESULT TargetInfo::ReadLoaderEntry( ProcessInfo* Process, MachineInfo* Machine, ULONG64 Address, PKLDR_DATA_TABLE_ENTRY64 Entry ) { HRESULT Status; ULONG Result; ULONG SizeToRead; KLDR_DATA_TABLE_ENTRY32 Ent32; if (Machine->m_Ptr64) { SizeToRead = sizeof(KLDR_DATA_TABLE_ENTRY64); Status = ReadVirtual(Process, Address, Entry, SizeToRead, &Result); } else { SizeToRead = sizeof(KLDR_DATA_TABLE_ENTRY32); Status = ReadVirtual(Process, Address, &Ent32, SizeToRead, &Result); ConvertLoaderEntry32To64(&Ent32, Entry); } if (Status != S_OK) { return Status; } if (Result != SizeToRead) { return E_FAIL; } return S_OK; } HRESULT TargetInfo::ReadUnicodeString(ProcessInfo* Process, MachineInfo* Machine, ULONG64 Address, PUNICODE_STRING64 String) { HRESULT Status; ULONG Result; ULONG SizeToRead; UNICODE_STRING32 Str32; if (Machine->m_Ptr64) { SizeToRead = sizeof(UNICODE_STRING64); Status = ReadVirtual(Process, Address, String, SizeToRead, &Result); } else { SizeToRead = sizeof(UNICODE_STRING32); Status = ReadVirtual(Process, Address, &Str32, SizeToRead, &Result); String->Length = Str32.Length; String->MaximumLength = Str32.MaximumLength; String->Buffer = EXTEND64(Str32.Buffer); } if (Status != S_OK) { return Status; } if (Result != SizeToRead) { return E_FAIL; } return S_OK; } // VS_VERSIONINFO has a variable format but in the case we // care about it's fixed. struct PARTIAL_VERSIONINFO { WORD wLength; WORD wValueLength; WORD wType; WCHAR szKey[17]; VS_FIXEDFILEINFO Value; }; #define VER2_SIG ((ULONG)'X2EF') HRESULT TargetInfo::ReadImageVersionInfo(ProcessInfo* Process, ULONG64 ImageBase, PCSTR Item, PVOID Buffer, ULONG BufferSize, PULONG VerInfoSize, PIMAGE_DATA_DIRECTORY ResDataDir) { if (ResDataDir->VirtualAddress == 0 || ResDataDir->Size < sizeof(IMAGE_RESOURCE_DIRECTORY)) { return E_NOINTERFACE; } HRESULT Status; IMAGE_RESOURCE_DIRECTORY ResDir; ULONG64 Offset, DirOffset; Offset = ImageBase + ResDataDir->VirtualAddress; if ((Status = ReadAllVirtual(Process, Offset, &ResDir, sizeof(ResDir))) != S_OK) { return Status; } // // Search for the resource directory entry named by VS_FILE_INFO. // IMAGE_RESOURCE_DIRECTORY_ENTRY DirEnt; ULONG i; DirOffset = Offset; Offset += sizeof(ResDir) + ((ULONG64)ResDir.NumberOfNamedEntries * sizeof(DirEnt)); for (i = 0; i < (ULONG)ResDir.NumberOfIdEntries; i++) { if ((Status = ReadAllVirtual(Process, Offset, &DirEnt, sizeof(DirEnt))) != S_OK) { return Status; } if (!DirEnt.NameIsString && MAKEINTRESOURCE(DirEnt.Id) == VS_FILE_INFO) { break; } Offset += sizeof(DirEnt); } if (i >= (ULONG)ResDir.NumberOfIdEntries || !DirEnt.DataIsDirectory) { return E_NOINTERFACE; } Offset = DirOffset + DirEnt.OffsetToDirectory; if ((Status = ReadAllVirtual(Process, Offset, &ResDir, sizeof(ResDir))) != S_OK) { return Status; } // // Search for the resource directory entry named by VS_VERSION_INFO. // Offset += sizeof(ResDir) + ((ULONG64)ResDir.NumberOfNamedEntries * sizeof(DirEnt)); for (i = 0; i < (ULONG)ResDir.NumberOfIdEntries; i++) { if ((Status = ReadAllVirtual(Process, Offset, &DirEnt, sizeof(DirEnt))) != S_OK) { return Status; } if (DirEnt.Name == VS_VERSION_INFO) { break; } Offset += sizeof(DirEnt); } if (i >= (ULONG)ResDir.NumberOfIdEntries || !DirEnt.DataIsDirectory) { return E_NOINTERFACE; } Offset = DirOffset + DirEnt.OffsetToDirectory; if ((Status = ReadAllVirtual(Process, Offset, &ResDir, sizeof(ResDir))) != S_OK) { return Status; } // // We now have the VS_VERSION_INFO directory. Just take // the first entry as we don't care about languages. // Offset += sizeof(ResDir); if ((Status = ReadAllVirtual(Process, Offset, &DirEnt, sizeof(DirEnt))) != S_OK) { return Status; } if (DirEnt.DataIsDirectory) { return E_NOINTERFACE; } IMAGE_RESOURCE_DATA_ENTRY DataEnt; Offset = DirOffset + DirEnt.OffsetToData; if ((Status = ReadAllVirtual(Process, Offset, &DataEnt, sizeof(DataEnt))) != S_OK) { return Status; } if (DataEnt.Size < sizeof(PARTIAL_VERSIONINFO)) { return E_NOINTERFACE; } PARTIAL_VERSIONINFO RawInfo; Offset = ImageBase + DataEnt.OffsetToData; if ((Status = ReadAllVirtual(Process, Offset, &RawInfo, sizeof(RawInfo))) != S_OK) { return Status; } if (RawInfo.wLength < sizeof(RawInfo) || wcscmp(RawInfo.szKey, L"VS_VERSION_INFO") != 0) { return E_NOINTERFACE; } // // VerQueryValueA needs extra data space for ANSI translations // of version strings. VQVA assumes that this space is available // at the end of the data block passed in. GetFileVersionInformationSize // makes this work by returning a size that's big enough // for the actual data plus space for ANSI translations. We // need to do the same thing here so that we also provide // the necessary translation area. // ULONG DataSize = (RawInfo.wLength + 3) & ~3; PVOID VerData = malloc(DataSize * 2 + sizeof(ULONG)); if (VerData == NULL) { return E_OUTOFMEMORY; } if ((Status = ReadAllVirtual(Process, Offset, VerData, RawInfo.wLength)) == S_OK) { // Stamp the buffer with the signature that indicates // a full-size translation buffer is available after // the raw data. *(PULONG)((PUCHAR)VerData + DataSize) = VER2_SIG; Status = QueryVersionDataBuffer(VerData, Item, Buffer, BufferSize, VerInfoSize); } free(VerData); return Status; } HRESULT TargetInfo::ReadImageNtHeaders(ProcessInfo* Process, ULONG64 ImageBase, PIMAGE_NT_HEADERS64 Headers) { HRESULT Status; IMAGE_DOS_HEADER DosHdr; IMAGE_NT_HEADERS32 Hdr32; if ((Status = ReadAllVirtual(Process, ImageBase, &DosHdr, sizeof(DosHdr))) != S_OK) { return Status; } if (DosHdr.e_magic != IMAGE_DOS_SIGNATURE) { return E_INVALIDARG; } // Only read HEADERS32 worth of data first in case // that's all the memory that's available. if ((Status = ReadAllVirtual(Process, ImageBase + DosHdr.e_lfanew, &Hdr32, sizeof(Hdr32))) != S_OK) { return Status; } if (Hdr32.Signature != IMAGE_NT_SIGNATURE) { return E_INVALIDARG; } switch(Hdr32.OptionalHeader.Magic) { case IMAGE_NT_OPTIONAL_HDR32_MAGIC: ImageNtHdr32To64(&Hdr32, Headers); Status = S_OK; break; case IMAGE_NT_OPTIONAL_HDR64_MAGIC: // Read the remainder of the header. memcpy(Headers, &Hdr32, sizeof(Hdr32)); Status = ReadAllVirtual(Process, ImageBase + DosHdr.e_lfanew + sizeof(Hdr32), (PUCHAR)Headers + sizeof(Hdr32), sizeof(*Headers) - sizeof(Hdr32)); break; default: Status = E_INVALIDARG; break; } return Status; } HRESULT TargetInfo::ReadDirectoryTableBase(PULONG64 DirBase) { HRESULT Status; ULONG64 CurProc; // Retrieve the current EPROCESS's DirectoryTableBase[0] value. Status = GetProcessInfoDataOffset(m_RegContextThread, 0, 0, &CurProc); if (Status != S_OK) { return Status; } CurProc += m_KdDebuggerData.OffsetEprocessDirectoryTableBase; return ReadPointer(m_ProcessHead, m_Machine, CurProc, DirBase); } HRESULT TargetInfo::ReadSharedUserTimeDateN(PULONG64 TimeDate) { return ReadAllVirtual(m_ProcessHead, m_TypeInfo.SharedUserDataOffset + FIELD_OFFSET(KUSER_SHARED_DATA, SystemTime), TimeDate, sizeof(*TimeDate)); } HRESULT TargetInfo::ReadSharedUserUpTimeN(PULONG64 UpTime) { return ReadAllVirtual(m_ProcessHead, m_TypeInfo.SharedUserDataOffset + FIELD_OFFSET(KUSER_SHARED_DATA, InterruptTime), UpTime, sizeof(*UpTime)); } HRESULT TargetInfo::ReadSharedUserProductInfo(PULONG ProductType, PULONG SuiteMask) { HRESULT Status; BOOLEAN IsValid; if ((Status = ReadAllVirtual(m_ProcessHead, m_TypeInfo.SharedUserDataOffset + FIELD_OFFSET(KUSER_SHARED_DATA, ProductTypeIsValid), &IsValid, sizeof(IsValid))) != S_OK) { return Status; } if (!IsValid) { return E_NOTIMPL; } if ((Status = ReadAllVirtual(m_ProcessHead, m_TypeInfo.SharedUserDataOffset + FIELD_OFFSET(KUSER_SHARED_DATA, NtProductType), ProductType, sizeof(*ProductType))) != S_OK) { return Status; } return ReadAllVirtual(m_ProcessHead, m_TypeInfo.SharedUserDataOffset + FIELD_OFFSET(KUSER_SHARED_DATA, SuiteMask), SuiteMask, sizeof(*SuiteMask)); } //---------------------------------------------------------------------------- // // LiveKernelTargetInfo data space methods. // //---------------------------------------------------------------------------- HRESULT LiveKernelTargetInfo::GetProcessorId (ULONG Processor, PDEBUG_PROCESSOR_IDENTIFICATION_ALL Id) { return m_Machine->ReadKernelProcessorId(Processor, Id); } HRESULT LiveKernelTargetInfo::GetProcessorSpeed (ULONG Processor, PULONG Speed) { HRESULT Status; ULONG64 Prcb; if ((Status = GetProcessorSystemDataOffset(Processor, DEBUG_DATA_KPRCB_OFFSET, &Prcb)) != S_OK) { return Status; } if (!m_KdDebuggerData.OffsetPrcbMhz) { return E_NOTIMPL; } return ReadAllVirtual(m_ProcessHead, Prcb + m_KdDebuggerData.OffsetPrcbMhz, Speed, sizeof(ULONG)); } //---------------------------------------------------------------------------- // // ConnLiveKernelTargetInfo data space methods. // //---------------------------------------------------------------------------- HRESULT ConnLiveKernelTargetInfo::ReadVirtual( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { if (Process) { return Process->m_VirtualCache. Read(Offset, Buffer, BufferSize, BytesRead); } else { return ReadVirtualUncached(Process, Offset, Buffer, BufferSize, BytesRead); } } HRESULT ConnLiveKernelTargetInfo::WriteVirtual( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { HRESULT Status; if (Process) { Status = Process->m_VirtualCache. Write(Offset, Buffer, BufferSize, BytesWritten); } else { Status = WriteVirtualUncached(Process, Offset, Buffer, BufferSize, BytesWritten); } if (Status == S_OK) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_VIRTUAL); } return Status; } HRESULT ConnLiveKernelTargetInfo::SearchVirtual( IN ProcessInfo* Process, IN ULONG64 Offset, IN ULONG64 Length, IN PVOID Pattern, IN ULONG PatternSize, IN ULONG PatternGranularity, OUT PULONG64 MatchOffset ) { // In NT 4.0, the search API is not supported at the kernel protocol // level. Fall back to the default ReadMemory \ search. // HRESULT Status; if (m_SystemVersion <= NT_SVER_NT4 || PatternGranularity != 1) { Status = TargetInfo::SearchVirtual(Process, Offset, Length, (PUCHAR)Pattern, PatternSize, PatternGranularity, MatchOffset); } else { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; PDBGKD_SEARCH_MEMORY a = &m.u.SearchMemory; ULONG rc; NTSTATUS st; KdOut("Search called %s, length %I64x\n", FormatAddr64(Offset), Length); *MatchOffset = 0; a->SearchAddress = Offset; a->SearchLength = Length; a->PatternLength = PatternSize; m.ApiNumber = DbgKdSearchMemoryApi; m.ReturnStatus = STATUS_PENDING; // // Send the message and data to write and then wait for reply // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, Pattern, (USHORT)PatternSize); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET || Reply->ApiNumber != DbgKdSearchMemoryApi); st = Reply->ReturnStatus; if (NT_SUCCESS(st)) { if (m_Machine->m_Ptr64) { *MatchOffset = Reply->u.SearchMemory.FoundAddress; } else { *MatchOffset = EXTEND64(Reply->u.SearchMemory.FoundAddress); } } KdOut("DbgKdSearchMemory %08lx\n", st); Status = CONV_NT_STATUS(st); } return Status; } HRESULT ConnLiveKernelTargetInfo::ReadVirtualUncached( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { BOOL TranslateVirt = Process && Process->m_VirtualCache.m_ForceDecodePTEs; ULONG Done; ULONG Left; HRESULT Status = S_OK; if (!m_Machine->m_Ptr64 && EXTEND64((ULONG)Offset) != Offset) { ErrOut("ReadVirtual: %08x not properly sign extended\n", (ULONG)Offset); } Restart: Done = 0; Left = BufferSize; while (Left) { ULONG64 ReqOffs; ULONG Req; // Exit if the user is tired of waiting. if (g_EngStatus & ENG_STATUS_USER_INTERRUPT) { Done = 0; Status = HRESULT_FROM_NT(STATUS_CONTROL_C_EXIT); break; } ReqOffs = Offset + Done; Req = Left; if (Req > MAX_MANIP_TRANSFER) { Req = MAX_MANIP_TRANSFER; } // Split all requests at page boundaries. This // handles different translations per page in the // case where the debugger is translating and also // avoids failures in partial success cases where // the lead page is valid but followers are not. if (PAGE_ALIGN(m_Machine, ReqOffs) != PAGE_ALIGN(m_Machine, ReqOffs + Req - 1)) { Req = (ULONG) ((ReqOffs | (m_Machine->m_PageSize - 1)) - ReqOffs + 1); } if (TranslateVirt) { Status = KdReadVirtualTranslated(ReqOffs, (PUCHAR)Buffer + Done, Req, &Req); } else { NTSTATUS NtStatus = KdReadVirtual(ReqOffs, (PUCHAR)Buffer + Done, Req, &Req); Status = CONV_NT_STATUS(NtStatus); } if (Status != S_OK) { // If the target machine failed the write it // may be because there's a page in transition // which it didn't want to access. Try again // with the debugger doing the translations so // that it can override certain protections. if (Status != HRESULT_FROM_NT(STATUS_CONTROL_C_EXIT) && !TranslateVirt && Process && Process->m_VirtualCache.m_DecodePTEs) { TranslateVirt = TRUE; goto Restart; } break; } else { Left -= Req; Done += Req; } } *BytesRead = Done; return Done > 0 ? S_OK : Status; } HRESULT ConnLiveKernelTargetInfo::WriteVirtualUncached( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { BOOL TranslateVirt = Process && Process->m_VirtualCache.m_ForceDecodePTEs; ULONG Done; ULONG Left; HRESULT Status = S_OK; if (!m_Machine->m_Ptr64 && EXTEND64((ULONG)Offset) != Offset) { ErrOut("WriteVirtual: %08x not properly sign extended\n", (ULONG)Offset); } // Restrict notifications to a single notify at the end. g_EngNotify++; Restart: Done = 0; Left = BufferSize; while (Left) { ULONG64 ReqOffs; ULONG Req; ReqOffs = Offset + Done; Req = Left; if (Req > MAX_MANIP_TRANSFER) { Req = MAX_MANIP_TRANSFER; } // Split all requests at page boundaries. This // handles different translations per page in the // case where the debugger is translating and also // avoids failures in partial success cases where // the lead page is valid but followers are not. if (PAGE_ALIGN(m_Machine, ReqOffs) != PAGE_ALIGN(m_Machine, ReqOffs + Req - 1)) { Req = (ULONG) ((ReqOffs | (m_Machine->m_PageSize - 1)) - ReqOffs + 1); } if (TranslateVirt) { Status = KdWriteVirtualTranslated(ReqOffs, (PUCHAR)Buffer + Done, Req, &Req); } else { NTSTATUS NtStatus = KdWriteVirtual(ReqOffs, (PUCHAR)Buffer + Done, Req, &Req); Status = CONV_NT_STATUS(NtStatus); } if (Status != S_OK) { // If the target machine failed the write it // may be because there's a page in transition // which it didn't want to access. Try again // with the debugger doing the translations so // that it can override certain protections. if (Status != HRESULT_FROM_NT(STATUS_CONTROL_C_EXIT) && !TranslateVirt && Process && Process->m_VirtualCache.m_DecodePTEs) { TranslateVirt = TRUE; goto Restart; } break; } else { Left -= Req; Done += Req; } } *BytesWritten = Done; Status = Done > 0 ? S_OK : Status; g_EngNotify--; if (Status == S_OK) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_VIRTUAL); } return Status; } NTSTATUS ConnLiveKernelTargetInfo::KdReadVirtual( IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT OPTIONAL PULONG BytesRead ) { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; ULONG rc; // // Initialize state manipulate message to read virtual memory. // m.ApiNumber = DbgKdReadVirtualMemoryApi; m.u.ReadMemory.TargetBaseAddress = Offset; m.u.ReadMemory.TransferCount = BufferSize; // // Send the read virtual message to the target system and wait for // a reply. // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, NULL, 0); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while ((rc != DBGKD_WAIT_PACKET) || (Reply->ApiNumber != DbgKdReadVirtualMemoryApi)); if (NT_SUCCESS(Reply->ReturnStatus)) { if (Reply->u.ReadMemory.ActualBytesRead == 0) { Reply->ReturnStatus = STATUS_UNSUCCESSFUL; } else { if (Reply->u.ReadMemory.ActualBytesRead > BufferSize) { ErrOut("KdReadVirtual: Asked for %d, got %d\n", BufferSize, Reply->u.ReadMemory.ActualBytesRead); Reply->u.ReadMemory.ActualBytesRead = BufferSize; } memcpy(Buffer, Reply + 1, Reply->u.ReadMemory.ActualBytesRead); } } *BytesRead = Reply->u.ReadMemory.ActualBytesRead; KdOut("KdReadVirtual(%s, %x) returns %08lx, %x\n", FormatAddr64(Offset), BufferSize, Reply->ReturnStatus, *BytesRead); return Reply->ReturnStatus; } NTSTATUS ConnLiveKernelTargetInfo::KdWriteVirtual( IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT OPTIONAL PULONG BytesWritten ) { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; ULONG rc; // // Initialize state manipulate message to write virtual memory. // m.ApiNumber = DbgKdWriteVirtualMemoryApi; m.u.WriteMemory.TargetBaseAddress = Offset; m.u.WriteMemory.TransferCount = BufferSize; // // Send the write message and data to the target system and wait // for a reply. // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, Buffer, (USHORT)BufferSize); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while ((rc != DBGKD_WAIT_PACKET) || (Reply->ApiNumber != DbgKdWriteVirtualMemoryApi)); KdOut("DbgKdWriteVirtualMemory returns %08lx\n", Reply->ReturnStatus); *BytesWritten = Reply->u.WriteMemory.ActualBytesWritten; KdOut("KdWriteVirtual(%s, %x) returns %08lx, %x\n", FormatAddr64(Offset), BufferSize, Reply->ReturnStatus, *BytesWritten); return Reply->ReturnStatus; } HRESULT ConnLiveKernelTargetInfo::KdReadVirtualTranslated( IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT OPTIONAL PULONG BytesRead ) { // // Virtual addresses are not necessarily contiguous // after translation to physical addresses so do // not allow transfers that cross a page boundary. // Higher-level code is responsible for splitting // up requests. // DBG_ASSERT(PAGE_ALIGN(m_Machine, Offset) == PAGE_ALIGN(m_Machine, Offset + BufferSize - 1)); ULONG64 TargetPhysicalAddress; ULONG Levels; ULONG PfIndex; HRESULT Status; BOOL ChangedSuspend; // Allow the page table physical accesses to be cached. ChangedSuspend = m_PhysicalCache.ChangeSuspend(TRUE); Status = m_Machine-> GetVirtualTranslationPhysicalOffsets(m_ProcessHead->m_CurrentThread, Offset, NULL, 0, &Levels, &PfIndex, &TargetPhysicalAddress); if (ChangedSuspend) { m_PhysicalCache.ChangeSuspend(FALSE); } if (Status == S_OK) { Status = ReadPhysicalUncached(TargetPhysicalAddress, Buffer, BufferSize, PHYS_FLAG_DEFAULT, BytesRead); } else if (Status == HR_PAGE_IN_PAGE_FILE || Status == HR_PAGE_NOT_AVAILABLE) { // Translate specific errors into generic memory // failure errors. Status = HRESULT_FROM_NT(STATUS_UNSUCCESSFUL); } return Status; } HRESULT ConnLiveKernelTargetInfo::KdWriteVirtualTranslated( IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT OPTIONAL PULONG BytesWritten ) { // // Virtual addresses are not necessarily contiguous // after translation to physical addresses so do // not allow transfers that cross a page boundary. // Higher-level code is responsible for splitting // up requests. // DBG_ASSERT(PAGE_ALIGN(m_Machine, Offset) == PAGE_ALIGN(m_Machine, Offset + BufferSize - 1)); ULONG64 TargetPhysicalAddress; ULONG Levels; ULONG PfIndex; HRESULT Status; BOOL ChangedSuspend; // Allow the page table physical accesses to be cached. ChangedSuspend = m_PhysicalCache.ChangeSuspend(TRUE); Status = m_Machine-> GetVirtualTranslationPhysicalOffsets(m_ProcessHead->m_CurrentThread, Offset, NULL, 0, &Levels, &PfIndex, &TargetPhysicalAddress); if (ChangedSuspend) { m_PhysicalCache.ChangeSuspend(FALSE); } if (Status == S_OK) { Status = WritePhysicalUncached(TargetPhysicalAddress, Buffer, BufferSize, PHYS_FLAG_DEFAULT, BytesWritten); } else if (Status == HR_PAGE_IN_PAGE_FILE || Status == HR_PAGE_NOT_AVAILABLE) { // Translate specific errors into generic memory // failure errors. Status = HRESULT_FROM_NT(STATUS_UNSUCCESSFUL); } return Status; } HRESULT ConnLiveKernelTargetInfo::ReadPhysical( THIS_ IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, IN ULONG Flags, OUT PULONG BytesRead ) { if (!m_PhysicalCache.m_Suspend) { if (Flags != PHYS_FLAG_DEFAULT) { return E_NOTIMPL; } return m_PhysicalCache. Read(Offset, Buffer, BufferSize, BytesRead); } else { return ReadPhysicalUncached(Offset, Buffer, BufferSize, Flags, BytesRead); } } HRESULT ConnLiveKernelTargetInfo::WritePhysical( THIS_ IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, IN ULONG Flags, OUT PULONG BytesWritten ) { if (!m_PhysicalCache.m_Suspend) { if (Flags != PHYS_FLAG_DEFAULT) { return E_NOTIMPL; } return m_PhysicalCache. Write(Offset, Buffer, BufferSize, BytesWritten); } else { return WritePhysicalUncached(Offset, Buffer, BufferSize, Flags, BytesWritten); } } HRESULT ConnLiveKernelTargetInfo::PointerSearchPhysical( IN ULONG64 Offset, IN ULONG64 Length, IN ULONG64 PointerMin, IN ULONG64 PointerMax, IN ULONG Flags, OUT PULONG64 MatchOffsets, IN ULONG MatchOffsetsSize, OUT PULONG MatchOffsetsCount ) { const ULONG SEARCH_SYMBOL_CHECK = 0xABCDDCBA; HRESULT Status; // The kernel stubs only handle page-based searches, // so if the search isn't page aligned fall back on // the base implementation. // The kernel also will only return a single hit if // the search range is greater than a page. if ((Offset & ((1 << m_Machine->m_PageShift) - 1)) || (Length & ((1 << m_Machine->m_PageShift) - 1)) || ((Flags & PTR_SEARCH_PHYS_ALL_HITS) && Length != m_Machine->m_PageSize)) { return TargetInfo::PointerSearchPhysical(Offset, Length, PointerMin, PointerMax, Flags, MatchOffsets, MatchOffsetsSize, MatchOffsetsCount); } if (!m_KdpSearchInProgress) { if (!GetOffsetFromSym(m_ProcessHead, "nt!KdpSearchPageHits", &m_KdpSearchPageHits, NULL) || !GetOffsetFromSym(m_ProcessHead, "nt!KdpSearchPageHitOffsets", &m_KdpSearchPageHitOffsets, NULL) || !GetOffsetFromSym(m_ProcessHead, "nt!KdpSearchPageHitIndex", &m_KdpSearchPageHitIndex, NULL) || !GetOffsetFromSym(m_ProcessHead, "nt!KdpSearchInProgress", &m_KdpSearchInProgress, NULL) || !GetOffsetFromSym(m_ProcessHead, "nt!KdpSearchStartPageFrame", &m_KdpSearchStartPageFrame, NULL) || !GetOffsetFromSym(m_ProcessHead, "nt!KdpSearchEndPageFrame", &m_KdpSearchEndPageFrame, NULL) || !GetOffsetFromSym(m_ProcessHead, "nt!KdpSearchAddressRangeStart", &m_KdpSearchAddressRangeStart, NULL) || !GetOffsetFromSym(m_ProcessHead, "nt!KdpSearchAddressRangeEnd", &m_KdpSearchAddressRangeEnd, NULL) || !GetOffsetFromSym(m_ProcessHead, "nt!KdpSearchPfnValue", &m_KdpSearchPfnValue, NULL)) { m_KdpSearchInProgress = 0; ErrOut("Search error: Unable to resolve symbols for kernel\n"); return E_INVALIDARG; } if (!(Flags & PTR_SEARCH_NO_SYMBOL_CHECK)) { ULONG Check; if (!GetOffsetFromSym(m_ProcessHead, "nt!KdpSearchCheckPoint", &m_KdpSearchCheckPoint, NULL) || ReadAllVirtual(m_ProcessHead, m_KdpSearchCheckPoint, &Check, sizeof(Check)) != S_OK || Check != SEARCH_SYMBOL_CHECK) { m_KdpSearchInProgress = 0; ErrOut("Search error: Incorrect symbols for kernel\n"); return E_INVALIDARG; } } } ULONG Ul; // // Perform some sanity checks on the values. // if (ReadAllVirtual(m_ProcessHead, m_KdpSearchInProgress, &Ul, sizeof(Ul)) != S_OK || Ul != 0) { ErrOut("Search error: " "Inconsistent value for nt!KdpSearchInProgress \n"); return E_INVALIDARG; } // // Set up the search engine // if ((Status = WritePointer(m_ProcessHead, m_Machine, m_KdpSearchAddressRangeStart, PointerMin)) != S_OK || (Status = WritePointer(m_ProcessHead, m_Machine, m_KdpSearchAddressRangeEnd, PointerMax)) != S_OK || (Status = WritePointer(m_ProcessHead, m_Machine, m_KdpSearchStartPageFrame, Offset >> m_Machine-> m_PageShift)) != S_OK || (Status = WritePointer(m_ProcessHead, m_Machine, m_KdpSearchEndPageFrame, (Offset + Length - 1) >> m_Machine->m_PageShift)) != S_OK) { return Status; } Ul = 0; if ((Status = WriteAllVirtual(m_ProcessHead, m_KdpSearchPageHitIndex, &Ul, sizeof(Ul))) != S_OK || (Status = WriteAllVirtual(m_ProcessHead, m_KdpSearchPageHits, &Ul, sizeof(Ul))) != S_OK || (Status = WriteAllVirtual(m_ProcessHead, m_KdpSearchPageHitOffsets, &Ul, sizeof(Ul))) != S_OK) { return Status; } Ul = (Flags & PTR_SEARCH_PHYS_PTE) ? 1 : 0; if ((Status = WriteAllVirtual(m_ProcessHead, m_KdpSearchPfnValue, &Ul, sizeof(Ul))) != S_OK) { return Status; } Ul = 1; if ((Status = WriteAllVirtual(m_ProcessHead, m_KdpSearchInProgress, &Ul, sizeof(Ul))) != S_OK) { goto Exit; } // The kernel check-low-memory code checks the variables // set above and automatically changes its behavior to // the given search rather than the standard low-memory check. if (FAILED(Status = CheckLowMemory())) { goto Exit; } ULONG Hits; ULONG i; if ((Status = ReadAllVirtual(m_ProcessHead, m_KdpSearchPageHitIndex, &Hits, sizeof(Hits))) != S_OK) { goto Exit; } if (MatchOffsetsCount) { *MatchOffsetsCount = Hits; } if (Hits > MatchOffsetsSize) { Hits = MatchOffsetsSize; Status = S_FALSE; } for (i = 0; i < Hits; i++) { ULONG64 Pfn; ULONG Offs; HRESULT ReadStatus; Pfn = 0; if ((ReadStatus = ReadAllVirtual (m_ProcessHead, m_KdpSearchPageHits + i * m_TypeInfo.SizePageFrameNumber, &Pfn, m_TypeInfo.SizePageFrameNumber)) != S_OK || (ReadStatus = ReadAllVirtual (m_ProcessHead, m_KdpSearchPageHitOffsets + i * sizeof(ULONG), &Offs, sizeof(Offs))) != S_OK) { Status = ReadStatus; goto Exit; } MatchOffsets[i] = (Pfn << m_Machine->m_PageShift) + (Offs & ((1 << m_Machine->m_PageShift) - 1)); } Exit: Ul = 0; // Can't do much on failures. WriteAllVirtual(m_ProcessHead, m_KdpSearchPfnValue, &Ul, sizeof(Ul)); WriteAllVirtual(m_ProcessHead, m_KdpSearchInProgress, &Ul, sizeof(Ul)); return Status; } HRESULT ConnLiveKernelTargetInfo::ReadPhysicalUncached( THIS_ IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, IN ULONG Flags, OUT PULONG BytesRead ) { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; PDBGKD_READ_MEMORY64 a; NTSTATUS st; ULONG rc; ULONG cb, cb2; ULONG OrigBufferSize = BufferSize; cb2 = 0; if (ARGUMENT_PRESENT(BytesRead)) { *BytesRead = 0; } readmore: cb = BufferSize; if (cb > MAX_MANIP_TRANSFER) { cb = MAX_MANIP_TRANSFER; } // // Format state manipulate message // m.ApiNumber = DbgKdReadPhysicalMemoryApi; m.ReturnStatus = STATUS_PENDING; a = &m.u.ReadMemory; a->TargetBaseAddress = Offset + cb2; a->TransferCount = cb; // The ActualBytes fields have been overloaded to // allow passing in flags for the request. Previous // debuggers passed zero so that should always be // the default. a->ActualBytesRead = Flags; // // Send the message and then wait for reply // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, NULL, 0); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET || Reply->ApiNumber != DbgKdReadPhysicalMemoryApi); st = Reply->ReturnStatus; a = &Reply->u.ReadMemory; DBG_ASSERT(a->ActualBytesRead <= cb); // // Return actual bytes read, and then transfer the bytes // if (ARGUMENT_PRESENT(BytesRead)) { *BytesRead += a->ActualBytesRead; } // // Since read response data follows message, Reply+1 should point // at the data // if (NT_SUCCESS(st)) { memcpy((PCHAR)((ULONG_PTR)Buffer + cb2), Reply + 1, (int)a->ActualBytesRead); BufferSize -= a->ActualBytesRead; cb2 += a->ActualBytesRead; if (BufferSize) { goto readmore; } } KdOut("KdReadPhysical(%s, %x) returns %08lx, %x\n", FormatAddr64(Offset), OrigBufferSize, st, cb2); return CONV_NT_STATUS(st); } HRESULT ConnLiveKernelTargetInfo::WritePhysicalUncached( THIS_ IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, IN ULONG Flags, OUT PULONG BytesWritten ) { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; PDBGKD_WRITE_MEMORY64 a; NTSTATUS st; ULONG rc; ULONG cb, cb2; ULONG OrigBufferSize = BufferSize; InvalidateMemoryCaches(TRUE); cb2 = 0; if (ARGUMENT_PRESENT(BytesWritten)) { *BytesWritten = 0; } writemore: cb = BufferSize; if (cb > MAX_MANIP_TRANSFER) { cb = MAX_MANIP_TRANSFER; } // // Format state manipulate message // m.ApiNumber = DbgKdWritePhysicalMemoryApi; m.ReturnStatus = STATUS_PENDING; a = &m.u.WriteMemory; a->TargetBaseAddress = Offset + cb2; a->TransferCount = cb; // The ActualBytes fields have been overloaded to // allow passing in flags for the request. Previous // debuggers passed zero so that should always be // the default. a->ActualBytesWritten = Flags; // // Send the message and data to write and then wait for reply // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, (PVOID)((ULONG_PTR)Buffer + cb2), (USHORT)cb); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET || Reply->ApiNumber != DbgKdWritePhysicalMemoryApi); st = Reply->ReturnStatus; a = &Reply->u.WriteMemory; DBG_ASSERT(a->ActualBytesWritten <= cb); // // Return actual bytes written // if (ARGUMENT_PRESENT(BytesWritten)) { *BytesWritten += a->ActualBytesWritten; } if (NT_SUCCESS(st)) { BufferSize -= a->ActualBytesWritten; cb2 += a->ActualBytesWritten; if (BufferSize) { goto writemore; } } KdOut("KdWritePhysical(%s, %x) returns %08lx, %x\n", FormatAddr64(Offset), OrigBufferSize, st, cb2); if (NT_SUCCESS(st)) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_PHYSICAL); } return CONV_NT_STATUS(st); } HRESULT ConnLiveKernelTargetInfo::ReadControl( THIS_ IN ULONG Processor, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; PDBGKD_READ_MEMORY64 a = &m.u.ReadMemory; NTSTATUS st = STATUS_UNSUCCESSFUL; ULONG rc; if (BufferSize > MAX_MANIP_TRANSFER) { return E_INVALIDARG; } // // Format state manipulate message // m.ApiNumber = DbgKdReadControlSpaceApi; m.ReturnStatus = STATUS_PENDING; m.Processor = (SHORT)Processor; a->TargetBaseAddress = Offset; a->TransferCount = BufferSize; a->ActualBytesRead = 0L; // // Send the message and then wait for reply // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, NULL, 0); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET || Reply->ApiNumber != DbgKdReadControlSpaceApi); st = Reply->ReturnStatus; // // Reset message address to reply. // a = &Reply->u.ReadMemory; DBG_ASSERT(a->ActualBytesRead <= BufferSize); // // Return actual bytes read, and then transfer the bytes // if (ARGUMENT_PRESENT(BytesRead)) { *BytesRead = a->ActualBytesRead; } // // Since read response data follows message, Reply+1 should point // at the data // memcpy(Buffer, Reply + 1, (int)a->ActualBytesRead); KdOut("DbgKdReadControlSpace returns %08lx\n", st); return CONV_NT_STATUS(st); } HRESULT ConnLiveKernelTargetInfo::WriteControl( THIS_ IN ULONG Processor, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; PDBGKD_WRITE_MEMORY64 a = &m.u.WriteMemory; NTSTATUS st; ULONG rc; if (BufferSize > MAX_MANIP_TRANSFER) { return E_INVALIDARG; } // // Format state manipulate message // m.ApiNumber = DbgKdWriteControlSpaceApi; m.ReturnStatus = STATUS_PENDING; m.Processor = (USHORT)Processor; a->TargetBaseAddress = Offset; a->TransferCount = BufferSize; a->ActualBytesWritten = 0L; // // Send the message and data to write and then wait for reply // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, Buffer, (USHORT)BufferSize); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET || Reply->ApiNumber != DbgKdWriteControlSpaceApi); st = Reply->ReturnStatus; a = &Reply->u.WriteMemory; DBG_ASSERT(a->ActualBytesWritten <= BufferSize); // // Return actual bytes written // *BytesWritten = a->ActualBytesWritten; KdOut("DbgWriteControlSpace returns %08lx\n", st); if (NT_SUCCESS(st)) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_CONTROL); } return CONV_NT_STATUS(st); } HRESULT ConnLiveKernelTargetInfo::ReadIo( THIS_ IN ULONG InterfaceType, IN ULONG BusNumber, IN ULONG AddressSpace, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; NTSTATUS st; ULONG rc; ULONG DataValue; switch(BufferSize) { case 1: case 2: case 4: break; default: return E_INVALIDARG; } if (!(AddressSpace == 0 || AddressSpace == 1)) { return E_INVALIDARG; } // Convert trivially extended I/O requests down into simple // requests as not all platform support extended requests. if (InterfaceType == Isa && BusNumber == 0 && AddressSpace == 1) { PDBGKD_READ_WRITE_IO64 a = &m.u.ReadWriteIo; // // Format state manipulate message // m.ApiNumber = DbgKdReadIoSpaceApi; m.ReturnStatus = STATUS_PENDING; a->DataSize = BufferSize; a->IoAddress = Offset; // // Send the message and then wait for reply // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, NULL, 0); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET || Reply->ApiNumber != DbgKdReadIoSpaceApi); st = Reply->ReturnStatus; DataValue = Reply->u.ReadWriteIo.DataValue; KdOut("DbgKdReadIoSpace returns %08lx\n", st); } else { PDBGKD_READ_WRITE_IO_EXTENDED64 a = &m.u.ReadWriteIoExtended; // // Format state manipulate message // m.ApiNumber = DbgKdReadIoSpaceExtendedApi; m.ReturnStatus = STATUS_PENDING; a->DataSize = BufferSize; a->IoAddress = Offset; a->InterfaceType = InterfaceType; a->BusNumber = BusNumber; a->AddressSpace = AddressSpace; // // Send the message and then wait for reply // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, NULL, 0); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET || Reply->ApiNumber != DbgKdReadIoSpaceExtendedApi); st = Reply->ReturnStatus; DataValue = Reply->u.ReadWriteIoExtended.DataValue; KdOut("DbgKdReadIoSpaceExtended returns %08lx\n", st); } if (NT_SUCCESS(st)) { switch(BufferSize) { case 1: *(PUCHAR)Buffer = (UCHAR)DataValue; break; case 2: *(PUSHORT)Buffer = (USHORT)DataValue; break; case 4: *(PULONG)Buffer = DataValue; break; } // I/O access currently can't successfully return anything // other than the requested size. if (BytesRead != NULL) { *BytesRead = BufferSize; } return S_OK; } else { return HRESULT_FROM_NT(st); } } HRESULT ConnLiveKernelTargetInfo::WriteIo( THIS_ IN ULONG InterfaceType, IN ULONG BusNumber, IN ULONG AddressSpace, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; NTSTATUS st; ULONG rc; ULONG DataValue; switch(BufferSize) { case 1: DataValue = *(PUCHAR)Buffer; break; case 2: DataValue = *(PUSHORT)Buffer; break; case 4: DataValue = *(PULONG)Buffer; break; default: return E_INVALIDARG; } if (!(AddressSpace == 0 || AddressSpace == 1)) { return E_INVALIDARG; } // Convert trivially extended I/O requests down into simple // requests as not all platform support extended requests. if (InterfaceType == Isa && BusNumber == 0 && AddressSpace == 1) { PDBGKD_READ_WRITE_IO64 a = &m.u.ReadWriteIo; // // Format state manipulate message // m.ApiNumber = DbgKdWriteIoSpaceApi; m.ReturnStatus = STATUS_PENDING; a->DataSize = BufferSize; a->IoAddress = Offset; a->DataValue = DataValue; // // Send the message and then wait for reply // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, NULL, 0); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET || Reply->ApiNumber != DbgKdWriteIoSpaceApi); st = Reply->ReturnStatus; KdOut("DbgKdWriteIoSpace returns %08lx\n", st); } else { PDBGKD_READ_WRITE_IO_EXTENDED64 a = &m.u.ReadWriteIoExtended; // // Format state manipulate message // m.ApiNumber = DbgKdWriteIoSpaceExtendedApi; m.ReturnStatus = STATUS_PENDING; a->DataSize = BufferSize; a->IoAddress = Offset; a->DataValue = DataValue; a->InterfaceType = InterfaceType; a->BusNumber = BusNumber; a->AddressSpace = AddressSpace; // // Send the message and then wait for reply // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, NULL, 0); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET || Reply->ApiNumber != DbgKdWriteIoSpaceExtendedApi); st = Reply->ReturnStatus; KdOut("DbgKdWriteIoSpaceExtended returns %08lx\n", st); } if (NT_SUCCESS(st)) { // I/O access currently can't successfully return anything // other than the requested size. if (BytesWritten != NULL) { *BytesWritten = BufferSize; } NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_IO); return S_OK; } else { return HRESULT_FROM_NT(st); } } HRESULT ConnLiveKernelTargetInfo::ReadMsr( THIS_ IN ULONG Msr, OUT PULONG64 Value ) { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; PDBGKD_READ_WRITE_MSR a = &m.u.ReadWriteMsr; LARGE_INTEGER li; NTSTATUS st; ULONG rc; // // Format state manipulate message // m.ApiNumber = DbgKdReadMachineSpecificRegister; m.ReturnStatus = STATUS_PENDING; a->Msr = Msr; // // Send the message and then wait for reply // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, NULL, 0); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET || Reply->ApiNumber != DbgKdReadMachineSpecificRegister); st = Reply->ReturnStatus; a = &Reply->u.ReadWriteMsr; li.LowPart = a->DataValueLow; li.HighPart = a->DataValueHigh; *Value = li.QuadPart; KdOut("DbgKdReadMsr returns %08lx\n", st); return CONV_NT_STATUS(st); } HRESULT ConnLiveKernelTargetInfo::WriteMsr( THIS_ IN ULONG Msr, IN ULONG64 Value ) { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; PDBGKD_READ_WRITE_MSR a = &m.u.ReadWriteMsr; LARGE_INTEGER li; NTSTATUS st; ULONG rc; li.QuadPart = Value; // // Format state manipulate message // m.ApiNumber = DbgKdWriteMachineSpecificRegister; m.ReturnStatus = STATUS_PENDING; // Quiet PREfix warnings. m.Processor = 0; m.ProcessorLevel = 0; a->Msr = Msr; a->DataValueLow = li.LowPart; a->DataValueHigh = li.HighPart; // // Send the message and then wait for reply // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, NULL, 0); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET || Reply->ApiNumber != DbgKdWriteMachineSpecificRegister); st = Reply->ReturnStatus; KdOut("DbgKdWriteMsr returns %08lx\n", st); if (NT_SUCCESS(st)) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_MSR); } return CONV_NT_STATUS(st); } HRESULT ConnLiveKernelTargetInfo::ReadBusData( THIS_ IN ULONG BusDataType, IN ULONG BusNumber, IN ULONG SlotNumber, IN ULONG Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; PDBGKD_GET_SET_BUS_DATA a = &m.u.GetSetBusData; NTSTATUS st; ULONG rc; // // Check the buffer size. // if (BufferSize > MAX_MANIP_TRANSFER) { return E_INVALIDARG; } // // Format state manipulate message // m.ApiNumber = DbgKdGetBusDataApi; m.ReturnStatus = STATUS_PENDING; a->BusDataType = BusDataType; a->BusNumber = BusNumber; a->SlotNumber = SlotNumber; a->Offset = Offset; a->Length = BufferSize; // // Send the message and then wait for reply // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, NULL, 0); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET || Reply->ApiNumber != DbgKdGetBusDataApi); st = Reply->ReturnStatus; if (NT_SUCCESS(st)) { a = &Reply->u.GetSetBusData; memcpy(Buffer, Reply + 1, a->Length); if (BytesRead != NULL) { *BytesRead = a->Length; } } KdOut("DbgKdGetBusData returns %08lx\n", st); return CONV_NT_STATUS(st); } HRESULT ConnLiveKernelTargetInfo::WriteBusData( THIS_ IN ULONG BusDataType, IN ULONG BusNumber, IN ULONG SlotNumber, IN ULONG Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; PDBGKD_GET_SET_BUS_DATA a = &m.u.GetSetBusData; NTSTATUS st; ULONG rc; // // Check the buffer size. // if (BufferSize > MAX_MANIP_TRANSFER) { return E_INVALIDARG; } // // Format state manipulate message // m.ApiNumber = DbgKdSetBusDataApi; m.ReturnStatus = STATUS_PENDING; a->BusDataType = BusDataType; a->BusNumber = BusNumber; a->SlotNumber = SlotNumber; a->Offset = Offset; a->Length = BufferSize; // // Send the message and then wait for reply // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, Buffer, (USHORT)BufferSize); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET || Reply->ApiNumber != DbgKdSetBusDataApi); st = Reply->ReturnStatus; if (NT_SUCCESS(st) && BytesWritten != NULL) { a = &Reply->u.GetSetBusData; *BytesWritten = a->Length; } KdOut("DbgKdSetBusData returns %08lx\n", st); if (NT_SUCCESS(st)) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_BUS_DATA); } return CONV_NT_STATUS(st); } HRESULT ConnLiveKernelTargetInfo::CheckLowMemory(void) { DBGKD_MANIPULATE_STATE64 m; PDBGKD_MANIPULATE_STATE64 Reply; ULONG rc; // // Format state manipulate message // ZeroMemory(&m, sizeof(m)); m.ApiNumber = DbgKdCheckLowMemoryApi; m.ReturnStatus = STATUS_PENDING; // // We wait for an answer from the kernel side. // do { m_Transport->WritePacket(&m, sizeof(m), PACKET_TYPE_KD_STATE_MANIPULATE, NULL, 0); rc = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while (rc != DBGKD_WAIT_PACKET); if (Reply->ReturnStatus != STATUS_SUCCESS) { ErrOut("Corrupted page with pfn %x\n", Reply->ReturnStatus); } KdOut("DbgKdCheckLowMemory 0x00000000\n"); return S_OK; } NTSTATUS ConnLiveKernelTargetInfo::KdFillMemory(IN ULONG Flags, IN ULONG64 Start, IN ULONG Size, IN PVOID Pattern, IN ULONG PatternSize, OUT PULONG Filled) { ULONG NtStatus; DBGKD_MANIPULATE_STATE64 Manip; PDBGKD_MANIPULATE_STATE64 Reply; NTSTATUS Status = STATUS_SUCCESS; DBG_ASSERT(m_KdMaxManipulate > DbgKdFillMemoryApi && (Flags & 0xffff0000) == 0 && PatternSize <= MAX_MANIP_TRANSFER); // Invalidate any cached memory. if (Flags & DBGKD_FILL_MEMORY_VIRTUAL) { InvalidateMemoryCaches(TRUE); } else if (Flags & DBGKD_FILL_MEMORY_PHYSICAL) { m_PhysicalCache.Remove(Start, Size); } // // Initialize state manipulate message to fill memory. // Manip.ApiNumber = DbgKdFillMemoryApi; Manip.u.FillMemory.Address = Start; Manip.u.FillMemory.Length = Size; Manip.u.FillMemory.Flags = (USHORT)Flags; Manip.u.FillMemory.PatternLength = (USHORT)PatternSize; // // Send the message and data to the target system and wait // for a reply. // ULONG Recv; do { m_Transport->WritePacket(&Manip, sizeof(Manip), PACKET_TYPE_KD_STATE_MANIPULATE, Pattern, (USHORT)PatternSize); Recv = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while ((Recv != DBGKD_WAIT_PACKET) || (Reply->ApiNumber != DbgKdFillMemoryApi)); NtStatus = Reply->ReturnStatus; *Filled = Reply->u.FillMemory.Length; KdOut("DbgKdFillMemory returns %08lx\n", NtStatus); return NtStatus; } HRESULT ConnLiveKernelTargetInfo::FillVirtual( THIS_ IN ProcessInfo* Process, IN ULONG64 Start, IN ULONG Size, IN PVOID Pattern, IN ULONG PatternSize, OUT PULONG Filled ) { HRESULT Status; if (m_KdMaxManipulate <= DbgKdFillMemoryApi || PatternSize > MAX_MANIP_TRANSFER) { Status = TargetInfo::FillVirtual(Process, Start, Size, Pattern, PatternSize, Filled); } else { NTSTATUS NtStatus = KdFillMemory(DBGKD_FILL_MEMORY_VIRTUAL, Start, Size, Pattern, PatternSize, Filled); Status = CONV_NT_STATUS(NtStatus); } return Status; } HRESULT ConnLiveKernelTargetInfo::FillPhysical( THIS_ IN ULONG64 Start, IN ULONG Size, IN PVOID Pattern, IN ULONG PatternSize, OUT PULONG Filled ) { HRESULT Status; if (m_KdMaxManipulate <= DbgKdFillMemoryApi || PatternSize > MAX_MANIP_TRANSFER) { Status = TargetInfo::FillPhysical(Start, Size, Pattern, PatternSize, Filled); } else { NTSTATUS NtStatus = KdFillMemory(DBGKD_FILL_MEMORY_PHYSICAL, Start, Size, Pattern, PatternSize, Filled); Status = CONV_NT_STATUS(NtStatus); } return Status; } HRESULT ConnLiveKernelTargetInfo::QueryAddressInformation(ProcessInfo* Process, ULONG64 Address, ULONG InSpace, PULONG OutSpace, PULONG OutFlags) { HRESULT Status; if (m_KdMaxManipulate <= DbgKdQueryMemoryApi) { Status = TargetInfo::QueryAddressInformation(Process, Address, InSpace, OutSpace, OutFlags); } else { DBGKD_MANIPULATE_STATE64 Manip; PDBGKD_MANIPULATE_STATE64 Reply; NTSTATUS NtStatus; // // Initialize state manipulate message to query memory. // Manip.ApiNumber = DbgKdQueryMemoryApi; Manip.u.QueryMemory.Address = Address; Manip.u.QueryMemory.Reserved = 0; Manip.u.QueryMemory.AddressSpace = InSpace; Manip.u.QueryMemory.Flags = 0; // // Send the message and data to the target system and wait // for a reply. // ULONG Recv; do { m_Transport->WritePacket(&Manip, sizeof(Manip), PACKET_TYPE_KD_STATE_MANIPULATE, NULL, 0); Recv = m_Transport-> WaitForPacket(PACKET_TYPE_KD_STATE_MANIPULATE, &Reply); } while ((Recv != DBGKD_WAIT_PACKET) || (Reply->ApiNumber != DbgKdQueryMemoryApi)); NtStatus = Reply->ReturnStatus; *OutSpace = Reply->u.QueryMemory.AddressSpace; *OutFlags = Reply->u.QueryMemory.Flags; KdOut("DbgKdQueryMemory returns %08lx\n", NtStatus); Status = CONV_NT_STATUS(NtStatus); } return Status; } //---------------------------------------------------------------------------- // // LocalLiveKernelTargetInfo data space methods. // //---------------------------------------------------------------------------- HRESULT LocalLiveKernelTargetInfo::ReadVirtual( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { SYSDBG_VIRTUAL Cmd; NTSTATUS Status = STATUS_SUCCESS; // // The kernel only allows operations up to // KDP_MESSAGE_BUFFER_SIZE, so break things up // into chunks if necessary. // *BytesRead = 0; Cmd.Address = (PVOID)(ULONG_PTR)Offset; Cmd.Buffer = Buffer; while (BufferSize > 0) { ULONG ChunkDone; if (BufferSize > PACKET_MAX_SIZE) { Cmd.Request = PACKET_MAX_SIZE; } else { Cmd.Request = BufferSize; } // The kernel stubs avoid faults so all memory // must be paged in ahead of time. There's // still the possibility that something could // get paged out after this but the assumption is // that the vulnerability is small and it's much // better than implementing dual code paths in // the kernel. if (IsBadWritePtr(Cmd.Buffer, Cmd.Request)) { Status = STATUS_INVALID_PARAMETER; break; } ChunkDone = 0; Status = g_NtDllCalls.NtSystemDebugControl(SysDbgReadVirtual, &Cmd, sizeof(Cmd), NULL, 0, &ChunkDone); if (!NT_SUCCESS(Status) && Status != STATUS_UNSUCCESSFUL) { break; } if (ChunkDone == 0) { // If some data was processed consider it a success. Status = *BytesRead > 0 ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; break; } Cmd.Address = (PVOID)((PUCHAR)Cmd.Address + ChunkDone); Cmd.Buffer = (PVOID)((PUCHAR)Cmd.Buffer + ChunkDone); BufferSize -= ChunkDone; *BytesRead += ChunkDone; } return CONV_NT_STATUS(Status); } HRESULT LocalLiveKernelTargetInfo::WriteVirtual( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { SYSDBG_VIRTUAL Cmd; NTSTATUS Status = STATUS_SUCCESS; // // The kernel only allows operations up to // KDP_MESSAGE_BUFFER_SIZE, so break things up // into chunks if necessary. // *BytesWritten = 0; Cmd.Address = (PVOID)(ULONG_PTR)Offset; Cmd.Buffer = Buffer; while (BufferSize > 0) { ULONG ChunkDone; if (BufferSize > PACKET_MAX_SIZE) { Cmd.Request = PACKET_MAX_SIZE; } else { Cmd.Request = BufferSize; } // The kernel stubs avoid faults so all memory // must be paged in ahead of time. There's // still the possibility that something could // get paged out after this but the assumption is // that the vulnerability is small and it's much // better than implementing dual code paths in // the kernel. if (IsBadReadPtr(Cmd.Buffer, Cmd.Request)) { Status = STATUS_INVALID_PARAMETER; break; } ChunkDone = 0; Status = g_NtDllCalls.NtSystemDebugControl(SysDbgWriteVirtual, &Cmd, sizeof(Cmd), NULL, 0, &ChunkDone); if (!NT_SUCCESS(Status) && Status != STATUS_UNSUCCESSFUL) { break; } if (ChunkDone == 0) { // If some data was processed consider it a success. Status = *BytesWritten > 0 ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; break; } Cmd.Address = (PVOID)((PUCHAR)Cmd.Address + ChunkDone); Cmd.Buffer = (PVOID)((PUCHAR)Cmd.Buffer + ChunkDone); BufferSize -= ChunkDone; *BytesWritten += ChunkDone; } if (NT_SUCCESS(Status)) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_VIRTUAL); } return CONV_NT_STATUS(Status); } HRESULT LocalLiveKernelTargetInfo::ReadPhysical( THIS_ IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, IN ULONG Flags, OUT PULONG BytesRead ) { SYSDBG_PHYSICAL Cmd; NTSTATUS Status = STATUS_SUCCESS; if (Flags != PHYS_FLAG_DEFAULT) { return E_NOTIMPL; } // // The kernel only allows operations up to // KDP_MESSAGE_BUFFER_SIZE, so break things up // into chunks if necessary. // *BytesRead = 0; Cmd.Address.QuadPart = Offset; Cmd.Buffer = Buffer; while (BufferSize > 0) { ULONG ChunkDone; if (BufferSize > PACKET_MAX_SIZE) { Cmd.Request = PACKET_MAX_SIZE; } else { Cmd.Request = BufferSize; } // The kernel stubs avoid faults so all memory // must be paged in ahead of time. There's // still the possibility that something could // get paged out after this but the assumption is // that the vulnerability is small and it's much // better than implementing dual code paths in // the kernel. if (IsBadWritePtr(Cmd.Buffer, Cmd.Request)) { Status = STATUS_INVALID_PARAMETER; break; } ChunkDone = 0; Status = g_NtDllCalls.NtSystemDebugControl(SysDbgReadPhysical, &Cmd, sizeof(Cmd), NULL, 0, &ChunkDone); if (!NT_SUCCESS(Status) && Status != STATUS_UNSUCCESSFUL) { break; } if (ChunkDone == 0) { // If some data was processed consider it a success. Status = *BytesRead > 0 ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; break; } Cmd.Address.QuadPart += ChunkDone; Cmd.Buffer = (PVOID)((PUCHAR)Cmd.Buffer + ChunkDone); BufferSize -= ChunkDone; *BytesRead += ChunkDone; } return CONV_NT_STATUS(Status); } HRESULT LocalLiveKernelTargetInfo::WritePhysical( THIS_ IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, IN ULONG Flags, OUT PULONG BytesWritten ) { SYSDBG_PHYSICAL Cmd; NTSTATUS Status = STATUS_SUCCESS; if (Flags != PHYS_FLAG_DEFAULT) { return E_NOTIMPL; } // // The kernel only allows operations up to // KDP_MESSAGE_BUFFER_SIZE, so break things up // into chunks if necessary. // *BytesWritten = 0; Cmd.Address.QuadPart = Offset; Cmd.Buffer = Buffer; while (BufferSize > 0) { ULONG ChunkDone; if (BufferSize > PACKET_MAX_SIZE) { Cmd.Request = PACKET_MAX_SIZE; } else { Cmd.Request = BufferSize; } // The kernel stubs avoid faults so all memory // must be paged in ahead of time. There's // still the possibility that something could // get paged out after this but the assumption is // that the vulnerability is small and it's much // better than implementing dual code paths in // the kernel. if (IsBadReadPtr(Cmd.Buffer, Cmd.Request)) { Status = STATUS_INVALID_PARAMETER; break; } ChunkDone = 0; Status = g_NtDllCalls.NtSystemDebugControl(SysDbgWritePhysical, &Cmd, sizeof(Cmd), NULL, 0, &ChunkDone); if (!NT_SUCCESS(Status) && Status != STATUS_UNSUCCESSFUL) { break; } if (ChunkDone == 0) { // If some data was processed consider it a success. Status = *BytesWritten > 0 ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; break; } Cmd.Address.QuadPart += ChunkDone; Cmd.Buffer = (PVOID)((PUCHAR)Cmd.Buffer + ChunkDone); BufferSize -= ChunkDone; *BytesWritten += ChunkDone; } if (NT_SUCCESS(Status)) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_PHYSICAL); } return CONV_NT_STATUS(Status); } HRESULT LocalLiveKernelTargetInfo::ReadControl( THIS_ IN ULONG Processor, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { // The kernel stubs avoid faults so all memory // must be paged in ahead of time. There's // still the possibility that something could // get paged out after this but the assumption is // that the vulnerability is small and it's much // better than implementing dual code paths in // the kernel. if (IsBadWritePtr(Buffer, BufferSize)) { return E_INVALIDARG; } SYSDBG_CONTROL_SPACE Cmd; Cmd.Address = Offset; Cmd.Buffer = Buffer; Cmd.Request = BufferSize; Cmd.Processor = Processor; NTSTATUS Status = g_NtDllCalls.NtSystemDebugControl(SysDbgReadControlSpace, &Cmd, sizeof(Cmd), NULL, 0, BytesRead); return CONV_NT_STATUS(Status); } HRESULT LocalLiveKernelTargetInfo::WriteControl( THIS_ IN ULONG Processor, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { // The kernel stubs avoid faults so all memory // must be paged in ahead of time. There's // still the possibility that something could // get paged out after this but the assumption is // that the vulnerability is small and it's much // better than implementing dual code paths in // the kernel. if (IsBadReadPtr(Buffer, BufferSize)) { return E_INVALIDARG; } SYSDBG_CONTROL_SPACE Cmd; Cmd.Address = Offset; Cmd.Buffer = Buffer; Cmd.Request = BufferSize; Cmd.Processor = Processor; NTSTATUS Status = g_NtDllCalls.NtSystemDebugControl(SysDbgWriteControlSpace, &Cmd, sizeof(Cmd), NULL, 0, BytesWritten); if (NT_SUCCESS(Status)) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_CONTROL); } return CONV_NT_STATUS(Status); } HRESULT LocalLiveKernelTargetInfo::ReadIo( THIS_ IN ULONG InterfaceType, IN ULONG BusNumber, IN ULONG AddressSpace, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { // The kernel stubs avoid faults so all memory // must be paged in ahead of time. There's // still the possibility that something could // get paged out after this but the assumption is // that the vulnerability is small and it's much // better than implementing dual code paths in // the kernel. if (IsBadWritePtr(Buffer, BufferSize)) { return E_INVALIDARG; } SYSDBG_IO_SPACE Cmd; Cmd.Address = Offset; Cmd.Buffer = Buffer; Cmd.Request = BufferSize; Cmd.InterfaceType = (INTERFACE_TYPE)InterfaceType; Cmd.BusNumber = BusNumber; Cmd.AddressSpace = AddressSpace; NTSTATUS Status = g_NtDllCalls.NtSystemDebugControl(SysDbgReadIoSpace, &Cmd, sizeof(Cmd), NULL, 0, BytesRead); return CONV_NT_STATUS(Status); } HRESULT LocalLiveKernelTargetInfo::WriteIo( THIS_ IN ULONG InterfaceType, IN ULONG BusNumber, IN ULONG AddressSpace, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { // The kernel stubs avoid faults so all memory // must be paged in ahead of time. There's // still the possibility that something could // get paged out after this but the assumption is // that the vulnerability is small and it's much // better than implementing dual code paths in // the kernel. if (IsBadReadPtr(Buffer, BufferSize)) { return E_INVALIDARG; } SYSDBG_IO_SPACE Cmd; Cmd.Address = Offset; Cmd.Buffer = Buffer; Cmd.Request = BufferSize; Cmd.InterfaceType = (INTERFACE_TYPE)InterfaceType; Cmd.BusNumber = BusNumber; Cmd.AddressSpace = AddressSpace; NTSTATUS Status = g_NtDllCalls.NtSystemDebugControl(SysDbgWriteIoSpace, &Cmd, sizeof(Cmd), NULL, 0, BytesWritten); if (NT_SUCCESS(Status)) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_IO); } return CONV_NT_STATUS(Status); } HRESULT LocalLiveKernelTargetInfo::ReadMsr( THIS_ IN ULONG Msr, OUT PULONG64 Value ) { SYSDBG_MSR Cmd; Cmd.Msr = Msr; NTSTATUS Status = g_NtDllCalls.NtSystemDebugControl(SysDbgReadMsr, &Cmd, sizeof(Cmd), &Cmd, sizeof(Cmd), NULL); if (NT_SUCCESS(Status)) { *Value = Cmd.Data; } return CONV_NT_STATUS(Status); } HRESULT LocalLiveKernelTargetInfo::WriteMsr( THIS_ IN ULONG Msr, IN ULONG64 Value ) { SYSDBG_MSR Cmd; Cmd.Msr = Msr; Cmd.Data = Value; NTSTATUS Status = g_NtDllCalls.NtSystemDebugControl(SysDbgWriteMsr, &Cmd, sizeof(Cmd), NULL, 0, NULL); if (NT_SUCCESS(Status)) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_MSR); } return CONV_NT_STATUS(Status); } HRESULT LocalLiveKernelTargetInfo::ReadBusData( THIS_ IN ULONG BusDataType, IN ULONG BusNumber, IN ULONG SlotNumber, IN ULONG Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { // The kernel stubs avoid faults so all memory // must be paged in ahead of time. There's // still the possibility that something could // get paged out after this but the assumption is // that the vulnerability is small and it's much // better than implementing dual code paths in // the kernel. if (IsBadWritePtr(Buffer, BufferSize)) { return E_INVALIDARG; } SYSDBG_BUS_DATA Cmd; Cmd.Address = Offset; Cmd.Buffer = Buffer; Cmd.Request = BufferSize; Cmd.BusDataType = (BUS_DATA_TYPE)BusDataType; Cmd.BusNumber = BusNumber; Cmd.SlotNumber = SlotNumber; NTSTATUS Status = g_NtDllCalls.NtSystemDebugControl(SysDbgReadBusData, &Cmd, sizeof(Cmd), NULL, 0, BytesRead); return CONV_NT_STATUS(Status); } HRESULT LocalLiveKernelTargetInfo::WriteBusData( THIS_ IN ULONG BusDataType, IN ULONG BusNumber, IN ULONG SlotNumber, IN ULONG Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { // The kernel stubs avoid faults so all memory // must be paged in ahead of time. There's // still the possibility that something could // get paged out after this but the assumption is // that the vulnerability is small and it's much // better than implementing dual code paths in // the kernel. if (IsBadReadPtr(Buffer, BufferSize)) { return E_INVALIDARG; } SYSDBG_BUS_DATA Cmd; Cmd.Address = Offset; Cmd.Buffer = Buffer; Cmd.Request = BufferSize; Cmd.BusDataType = (BUS_DATA_TYPE)BusDataType; Cmd.BusNumber = BusNumber; Cmd.SlotNumber = SlotNumber; NTSTATUS Status = g_NtDllCalls.NtSystemDebugControl(SysDbgWriteBusData, &Cmd, sizeof(Cmd), NULL, 0, BytesWritten); if (NT_SUCCESS(Status)) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_BUS_DATA); } return CONV_NT_STATUS(Status); } HRESULT LocalLiveKernelTargetInfo::CheckLowMemory( ) { NTSTATUS Status = g_NtDllCalls.NtSystemDebugControl(SysDbgCheckLowMemory, NULL, 0, NULL, 0, NULL); return CONV_NT_STATUS(Status); } //---------------------------------------------------------------------------- // // ExdiLiveKernelTargetInfo data space methods. // //---------------------------------------------------------------------------- HRESULT ExdiLiveKernelTargetInfo::ReadVirtual( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { HRESULT Status = m_Server-> ReadVirtualMemory(Offset, BufferSize, 8, (PBYTE)Buffer, BytesRead); return Status; } HRESULT ExdiLiveKernelTargetInfo::WriteVirtual( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { HRESULT Status = m_Server-> WriteVirtualMemory(Offset, BufferSize, 8, (PBYTE)Buffer, BytesWritten); if (Status == S_OK) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_VIRTUAL); } return Status; } HRESULT ExdiLiveKernelTargetInfo::ReadPhysical( THIS_ IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, IN ULONG Flags, OUT PULONG BytesRead ) { if (Flags != PHYS_FLAG_DEFAULT) { return E_NOTIMPL; } HRESULT Status = m_Server-> ReadPhysicalMemoryOrPeriphIO(Offset, 0, BufferSize, 8, (PBYTE)Buffer); if (Status == S_OK) { *BytesRead = BufferSize; } return Status; } HRESULT ExdiLiveKernelTargetInfo::WritePhysical( THIS_ IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, IN ULONG Flags, OUT PULONG BytesWritten ) { if (Flags != PHYS_FLAG_DEFAULT) { return E_NOTIMPL; } HRESULT Status = m_Server-> WritePhysicalMemoryOrPeriphIO(Offset, 0, BufferSize, 8, (PBYTE)Buffer); if (Status == S_OK) { *BytesWritten = BufferSize; NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_PHYSICAL); } return Status; } HRESULT ExdiLiveKernelTargetInfo::ReadControl( THIS_ IN ULONG Processor, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { // No Ioctl defined for this. return E_UNEXPECTED; } HRESULT ExdiLiveKernelTargetInfo::WriteControl( THIS_ IN ULONG Processor, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { // No Ioctl defined for this. return E_UNEXPECTED; } HRESULT ExdiLiveKernelTargetInfo::ReadIo( THIS_ IN ULONG InterfaceType, IN ULONG BusNumber, IN ULONG AddressSpace, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { HRESULT Status = m_Server-> ReadPhysicalMemoryOrPeriphIO(Offset, 1, BufferSize, 8, (PBYTE)Buffer); if (Status == S_OK) { *BytesRead = BufferSize; } return Status; } HRESULT ExdiLiveKernelTargetInfo::WriteIo( THIS_ IN ULONG InterfaceType, IN ULONG BusNumber, IN ULONG AddressSpace, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { HRESULT Status = m_Server-> WritePhysicalMemoryOrPeriphIO(Offset, 1, BufferSize, 8, (PBYTE)Buffer); if (Status == S_OK) { *BytesWritten = BufferSize; NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_IO); } return Status; } HRESULT ExdiLiveKernelTargetInfo::ReadMsr( THIS_ IN ULONG Msr, OUT PULONG64 Value ) { HRESULT Status; if (DBGENG_EXDI_IOC_READ_MSR <= m_IoctlMin || DBGENG_EXDI_IOC_READ_MSR >= m_IoctlMax) { // Read MSR Ioctl not supported. return E_NOTIMPL; } DBGENG_EXDI_IOCTL_MSR_IN IoctlIn; DBGENG_EXDI_IOCTL_READ_MSR_OUT IoctlOut; ULONG OutUsed; IoctlIn.Code = DBGENG_EXDI_IOC_READ_MSR; IoctlIn.Index = Msr; if ((Status = m_Server-> Ioctl(sizeof(IoctlIn), (PBYTE)&IoctlIn, sizeof(IoctlOut), &OutUsed, (PBYTE)&IoctlOut)) != S_OK) { return Status; } *Value = IoctlOut.Value; return S_OK; } HRESULT ExdiLiveKernelTargetInfo::WriteMsr( THIS_ IN ULONG Msr, IN ULONG64 Value ) { HRESULT Status; if (DBGENG_EXDI_IOC_WRITE_MSR <= m_IoctlMin || DBGENG_EXDI_IOC_WRITE_MSR >= m_IoctlMax) { // Write MSR Ioctl not supported. return E_NOTIMPL; } DBGENG_EXDI_IOCTL_MSR_IN IoctlIn; ULONG OutUsed; IoctlIn.Code = DBGENG_EXDI_IOC_WRITE_MSR; IoctlIn.Index = Msr; IoctlIn.Value = Value; Status = m_Server->Ioctl(sizeof(IoctlIn), (PBYTE)&IoctlIn, 0, &OutUsed, (PBYTE)&IoctlIn); if (Status == S_OK) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_MSR); } return Status; } HRESULT ExdiLiveKernelTargetInfo::ReadBusData( THIS_ IN ULONG BusDataType, IN ULONG BusNumber, IN ULONG SlotNumber, IN ULONG Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { // No Ioctl defined for this. return E_UNEXPECTED; } HRESULT ExdiLiveKernelTargetInfo::WriteBusData( THIS_ IN ULONG BusDataType, IN ULONG BusNumber, IN ULONG SlotNumber, IN ULONG Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { // No Ioctl defined for this. return E_UNEXPECTED; } HRESULT ExdiLiveKernelTargetInfo::CheckLowMemory( ) { // XXX drewb - This doesn't have any meaning in // the general case. What about when we know it's // NT on the other side of the emulator? return E_UNEXPECTED; } //---------------------------------------------------------------------------- // // UserTargetInfo data space methods. // //---------------------------------------------------------------------------- HRESULT LiveUserTargetInfo::ReadVirtual( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { if (m_Local) { return ReadVirtualUncached(Process, Offset, Buffer, BufferSize, BytesRead); } else { return Process->m_VirtualCache. Read(Offset, Buffer, BufferSize, BytesRead); } } HRESULT LiveUserTargetInfo::WriteVirtual( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { if (m_Local) { return WriteVirtualUncached(Process, Offset, Buffer, BufferSize, BytesWritten); } else { return Process->m_VirtualCache. Write(Offset, Buffer, BufferSize, BytesWritten); } } HRESULT LiveUserTargetInfo::ReadVirtualUncached( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { // ReadProcessMemory will fail if any part of the // region to read does not have read access. This // routine attempts to read the largest valid prefix // so it has to break up reads on page boundaries. HRESULT Status = S_OK; ULONG TotalBytesRead = 0; ULONG Read; ULONG ReadSize; while (BufferSize > 0) { // Calculate bytes to read and don't let read cross // a page boundary. ReadSize = m_Machine->m_PageSize - (ULONG) (Offset & (m_Machine->m_PageSize - 1)); ReadSize = min(BufferSize, ReadSize); if ((Status = m_Services-> ReadVirtual(Process->m_SysHandle, Offset, Buffer, ReadSize, &Read)) != S_OK) { if (TotalBytesRead != 0) { // If we've read something consider this a success. Status = S_OK; } break; } TotalBytesRead += Read; Offset += Read; Buffer = (PVOID)((PUCHAR)Buffer + Read); BufferSize -= (DWORD)Read; } if (Status == S_OK) { if (BytesRead != NULL) { *BytesRead = (DWORD)TotalBytesRead; } } return Status; } HRESULT LiveUserTargetInfo::WriteVirtualUncached( THIS_ IN ProcessInfo* Process, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { ULONG RealBytesWritten; HRESULT Status = m_Services-> WriteVirtual(Process->m_SysHandle, Offset, Buffer, BufferSize, &RealBytesWritten); *BytesWritten = (DWORD) RealBytesWritten; if (Status == S_OK) { NotifyChangeDebuggeeState(DEBUG_CDS_DATA, DEBUG_DATA_SPACE_VIRTUAL); } return Status; } HRESULT LiveUserTargetInfo::GetUnloadedModuleListHead(ProcessInfo* Process, PULONG64 Head) { // Get the address of the dynamic function table list head which is the // the same for all processes. This only has to be done once. if (Process->m_RtlUnloadList) { *Head = Process->m_RtlUnloadList; return S_OK; } if (m_Services-> GetUnloadedModuleListHead(Process->m_SysHandle, &Process->m_RtlUnloadList) == S_OK) { *Head = Process->m_RtlUnloadList; return S_OK; } return TargetInfo::GetUnloadedModuleListHead(Process, Head); } HRESULT LiveUserTargetInfo::GetFunctionTableListHead(ProcessInfo* Process, PULONG64 Head) { // Get the address of the dynamic function table list head which is the // the same for all processes. This only has to be done once. if (Process->m_DynFuncTableList) { *Head = Process->m_DynFuncTableList; return S_OK; } if (m_Services-> GetFunctionTableListHead(Process->m_SysHandle, &Process->m_DynFuncTableList) == S_OK) { *Head = Process->m_DynFuncTableList; return S_OK; } return TargetInfo::GetFunctionTableListHead(Process, Head); } HRESULT LiveUserTargetInfo::ReadOutOfProcessDynamicFunctionTable(ProcessInfo* Process, PWSTR Dll, ULONG64 Table, PULONG RetTableSize, PVOID* RetTableData) { HRESULT Status; PVOID TableData; ULONG TableSize; // Allocate an initial buffer of a reasonable size to try // and get the data in a single call. TableSize = 65536; for (;;) { TableData = malloc(TableSize); if (TableData == NULL) { return E_OUTOFMEMORY; } // // We assume that the function table data doesn't // change between OOP calls as long as the debuggee isn't // executing. To increase performance we cache loaded // DLLs as long as things are halted. // OUT_OF_PROC_FUNC_TABLE_DLL* OopDll = Process->FindOopFuncTableDll(Dll); ULONG64 DllHandle; Status = m_Services-> GetOutOfProcessFunctionTableW(Process->m_SysHandle, Dll, OopDll ? OopDll->Handle : 0, Table, TableData, TableSize, &TableSize, &DllHandle); if (Status == S_OK) { // If we haven't cached this DLL yet do so now. if (!OopDll) { // Failure to cache isn't critical, it // just means more loads later. if (Process->AddOopFuncTableDll(Dll, DllHandle) != S_OK) { m_Services->FreeLibrary(DllHandle); } } break; } free(TableData); if (Status == S_FALSE) { // Buffer was too small so loop and try again with // the newly retrieved size. } else { return Status; } } *RetTableSize = TableSize; *RetTableData = TableData; return S_OK; } HRESULT LiveUserTargetInfo::QueryMemoryRegion(ProcessInfo* Process, PULONG64 Handle, BOOL HandleIsOffset, PMEMORY_BASIC_INFORMATION64 Info) { MEMORY_BASIC_INFORMATION64 MemInfo; HRESULT Status; for (;;) { ULONG Used; // The handle is always an offset in this mode so // there's no need to check. if ((Status = m_Services-> QueryVirtual(Process->m_SysHandle, *Handle, &MemInfo, sizeof(MemInfo), &Used)) != S_OK) { return Status; } if (m_Machine->m_Ptr64) { if (Used != sizeof(MEMORY_BASIC_INFORMATION64)) { return E_FAIL; } *Info = MemInfo; } else { if (Used != sizeof(MEMORY_BASIC_INFORMATION32)) { return E_FAIL; } MemoryBasicInformation32To64((MEMORY_BASIC_INFORMATION32*)&MemInfo, Info); } if (HandleIsOffset || !((Info->Protect & PAGE_GUARD) || (Info->Protect & PAGE_NOACCESS) || (Info->State & MEM_FREE) || (Info->State & MEM_RESERVE))) { break; } *Handle = Info->BaseAddress + Info->RegionSize; } *Handle = Info->BaseAddress + Info->RegionSize; return S_OK; } HRESULT LiveUserTargetInfo::ReadHandleData( IN ProcessInfo* Process, IN ULONG64 Handle, IN ULONG DataType, OUT OPTIONAL PVOID Buffer, IN ULONG BufferSize, OUT OPTIONAL PULONG DataSize ) { return m_Services-> ReadHandleData(Process->m_SysHandle, Handle, DataType, Buffer, BufferSize, DataSize); } HRESULT LiveUserTargetInfo::GetProcessorId( ULONG Processor, PDEBUG_PROCESSOR_IDENTIFICATION_ALL Id ) { ULONG Done; return m_Services->GetProcessorId(Id, sizeof(*Id), &Done); } HRESULT LiveUserTargetInfo::GetProcessorSpeed( ULONG Processor, PULONG Speed ) { return E_UNEXPECTED; } HRESULT LiveUserTargetInfo::GetGenericProcessorFeatures( ULONG Processor, PULONG64 Features, ULONG FeaturesSize, PULONG Used ) { return m_Services-> GetGenericProcessorFeatures(Features, FeaturesSize, Used); } HRESULT LiveUserTargetInfo::GetSpecificProcessorFeatures( ULONG Processor, PULONG64 Features, ULONG FeaturesSize, PULONG Used ) { return m_Services-> GetSpecificProcessorFeatures(Features, FeaturesSize, Used); } //---------------------------------------------------------------------------- // // IDebugDataSpaces. // //---------------------------------------------------------------------------- STDMETHODIMP DebugClient::ReadVirtual( THIS_ IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG BytesTemp; Status = CurReadVirtual(Offset, Buffer, BufferSize, BytesRead != NULL ? BytesRead : &BytesTemp); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::WriteVirtual( THIS_ IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG BytesTemp; Status = g_Target->WriteVirtual(g_Process, Offset, Buffer, BufferSize, BytesWritten != NULL ? BytesWritten : &BytesTemp); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::SearchVirtual( THIS_ IN ULONG64 Offset, IN ULONG64 Length, IN PVOID Pattern, IN ULONG PatternSize, IN ULONG PatternGranularity, OUT PULONG64 MatchOffset ) { if (PatternGranularity == 0 || PatternSize % PatternGranularity) { return E_INVALIDARG; } HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { Status = g_Target->SearchVirtual(g_Process, Offset, Length, Pattern, PatternSize, PatternGranularity, MatchOffset); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::ReadVirtualUncached( THIS_ IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG BytesTemp; Status = g_Target-> ReadVirtualUncached(g_Process, Offset, Buffer, BufferSize, BytesRead != NULL ? BytesRead : &BytesTemp); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::WriteVirtualUncached( THIS_ IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG BytesTemp; Status = g_Target-> WriteVirtualUncached(g_Process, Offset, Buffer, BufferSize, BytesWritten != NULL ? BytesWritten : &BytesTemp); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::ReadPointersVirtual( THIS_ IN ULONG Count, IN ULONG64 Offset, OUT /* size_is(Count) */ PULONG64 Ptrs ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { Status = S_OK; while (Count-- > 0) { if ((Status = g_Target-> ReadPointer(g_Process, g_Machine, Offset, Ptrs)) != S_OK) { break; } Offset += g_Machine->m_Ptr64 ? sizeof(ULONG64) : sizeof(ULONG); Ptrs++; } } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::WritePointersVirtual( THIS_ IN ULONG Count, IN ULONG64 Offset, IN /* size_is(Count) */ PULONG64 Ptrs ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { Status = S_OK; while (Count-- > 0) { if ((Status = g_Target-> WritePointer(g_Process, g_Machine, Offset, *Ptrs)) != S_OK) { break; } Offset += g_Machine->m_Ptr64 ? sizeof(ULONG64) : sizeof(ULONG); Ptrs++; } } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::ReadPhysical( THIS_ IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG BytesTemp; Status = g_Target-> ReadPhysical(Offset, Buffer, BufferSize, PHYS_FLAG_DEFAULT, BytesRead != NULL ? BytesRead : &BytesTemp); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::WritePhysical( THIS_ IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG BytesTemp; Status = g_Target-> WritePhysical(Offset, Buffer, BufferSize, PHYS_FLAG_DEFAULT, BytesWritten != NULL ? BytesWritten : &BytesTemp); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::ReadControl( THIS_ IN ULONG Processor, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG BytesTemp; // KSPECIAL_REGISTER content is kept in control space // so accessing control space may touch data that's // cached in the current machine KSPECIAL_REGISTERS. // Flush the current machine to maintain consistency. g_Target->FlushRegContext(); Status = g_Target-> ReadControl(Processor, Offset, Buffer, BufferSize, BytesRead != NULL ? BytesRead : &BytesTemp); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::WriteControl( THIS_ IN ULONG Processor, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG BytesTemp; // KSPECIAL_REGISTER content is kept in control space // so accessing control space may touch data that's // cached in the current machine KSPECIAL_REGISTERS. // Flush the current machine to maintain consistency. g_Target->FlushRegContext(); Status = g_Target-> WriteControl(Processor, Offset, Buffer, BufferSize, BytesWritten != NULL ? BytesWritten : &BytesTemp); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::ReadIo( THIS_ IN ULONG InterfaceType, IN ULONG BusNumber, IN ULONG AddressSpace, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG BytesTemp; Status = g_Target-> ReadIo(InterfaceType, BusNumber, AddressSpace, Offset, Buffer, BufferSize, BytesRead != NULL ? BytesRead : &BytesTemp); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::WriteIo( THIS_ IN ULONG InterfaceType, IN ULONG BusNumber, IN ULONG AddressSpace, IN ULONG64 Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG BytesTemp; Status = g_Target-> WriteIo(InterfaceType, BusNumber, AddressSpace, Offset, Buffer, BufferSize, BytesWritten != NULL ? BytesWritten : &BytesTemp); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::ReadMsr( THIS_ IN ULONG Msr, OUT PULONG64 Value ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { Status = g_Target->ReadMsr(Msr, Value); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::WriteMsr( THIS_ IN ULONG Msr, IN ULONG64 Value ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { Status = g_Target->WriteMsr(Msr, Value); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::ReadBusData( THIS_ IN ULONG BusDataType, IN ULONG BusNumber, IN ULONG SlotNumber, IN ULONG Offset, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesRead ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG BytesTemp; Status = g_Target-> ReadBusData(BusDataType, BusNumber, SlotNumber, Offset, Buffer, BufferSize, BytesRead != NULL ? BytesRead : &BytesTemp); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::WriteBusData( THIS_ IN ULONG BusDataType, IN ULONG BusNumber, IN ULONG SlotNumber, IN ULONG Offset, IN PVOID Buffer, IN ULONG BufferSize, OUT PULONG BytesWritten ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG BytesTemp; Status = g_Target-> WriteBusData(BusDataType, BusNumber, SlotNumber, Offset, Buffer, BufferSize, BytesWritten != NULL ? BytesWritten : &BytesTemp); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::CheckLowMemory( THIS ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { Status = g_Target->CheckLowMemory(); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::ReadDebuggerData( THIS_ IN ULONG Index, OUT PVOID Buffer, IN ULONG BufferSize, OUT OPTIONAL PULONG DataSize ) { HRESULT Status; ENTER_ENGINE(); // Wait till the machine is accessible because on dump files the // debugger data block requires symbols to be loaded. if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; goto Exit; } PVOID Data; ULONG Size; ULONG64 DataSpace; if (Index < sizeof(g_Target->m_KdDebuggerData)) { // Even though internally all of the debugger data is // a single buffer that could be read arbitrarily we // restrict access to the defined constants to // preserve the abstraction that each constant refers // to a separate piece of data. Data = (PUCHAR)&g_Target->m_KdDebuggerData + Index; Size = sizeof(ULONG64); if ((Index >= DEBUG_DATA_OffsetKThreadNextProcessor) && (Index <= DEBUG_DATA_SizeEThread)) { Size = sizeof(USHORT); } else if (Index & (sizeof(ULONG64) - 1)) { Status = E_INVALIDARG; goto Exit; } } else { switch(Index) { case DEBUG_DATA_PaeEnabled: DataSpace = (BOOLEAN)g_Target->m_KdDebuggerData.PaeEnabled; Data = &DataSpace; Size = sizeof(BOOLEAN); break; case DEBUG_DATA_SharedUserData: DataSpace = g_Target->m_TypeInfo.SharedUserDataOffset; Data = &DataSpace; Size = sizeof(ULONG64); break; case DEBUG_DATA_ProductType: Data = &g_Target->m_ProductType; Size = sizeof(g_Target->m_ProductType); break; case DEBUG_DATA_SuiteMask: Data = &g_Target->m_SuiteMask; Size = sizeof(g_Target->m_SuiteMask); break; default: Status = E_INVALIDARG; goto Exit; } } Status = FillDataBuffer(Data, Size, Buffer, BufferSize, DataSize); Exit: LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::ReadProcessorSystemData( THIS_ IN ULONG Processor, IN ULONG Index, OUT PVOID Buffer, IN ULONG BufferSize, OUT OPTIONAL PULONG DataSize ) { HRESULT Status = S_OK; PVOID Data; ULONG Size; ULONG64 DataSpace; DEBUG_PROCESSOR_IDENTIFICATION_ALL AllId; ENTER_ENGINE(); switch(Index) { case DEBUG_DATA_KPCR_OFFSET: case DEBUG_DATA_KPRCB_OFFSET: case DEBUG_DATA_KTHREAD_OFFSET: if (!IS_MACHINE_SET(g_Target)) { Status = E_UNEXPECTED; } else { Status = g_Target-> GetProcessorSystemDataOffset(Processor, Index, &DataSpace); Data = &DataSpace; Size = sizeof(DataSpace); } break; case DEBUG_DATA_BASE_TRANSLATION_VIRTUAL_OFFSET: if (!IS_MACHINE_SET(g_Target)) { Status = E_UNEXPECTED; } else { Status = g_Machine->GetBaseTranslationVirtualOffset(&DataSpace); Data = &DataSpace; Size = sizeof(DataSpace); } break; case DEBUG_DATA_PROCESSOR_IDENTIFICATION: if (!g_Target) { Status = E_UNEXPECTED; } else { ZeroMemory(&AllId, sizeof(AllId)); Status = g_Target->GetProcessorId(Processor, &AllId); Data = &AllId; Size = sizeof(AllId); } break; case DEBUG_DATA_PROCESSOR_SPEED: if (!g_Target) { Status = E_UNEXPECTED; } else { DataSpace = 0; Status = g_Target-> GetProcessorSpeed(Processor, (PULONG) &DataSpace); Data = &DataSpace; Size = sizeof(ULONG); } break; default: Status = E_INVALIDARG; break; } if (Status == S_OK) { if (DataSize != NULL) { *DataSize = Size; } if (BufferSize < Size) { Status = S_FALSE; Size = BufferSize; } memcpy(Buffer, Data, Size); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::VirtualToPhysical( THIS_ IN ULONG64 Virtual, OUT PULONG64 Physical ) { HRESULT Status; ENTER_ENGINE(); if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG Levels; ULONG PfIndex; Status = g_Machine-> GetVirtualTranslationPhysicalOffsets(g_Thread, Virtual, NULL, 0, &Levels, &PfIndex, Physical); // GVTPO returns a special error code if the translation // succeeded down to the level of the actual data but // the data page itself is in the page file. This is used // for the page file dump support. To an external caller, // though, it's not useful so translate it into the standard // page-not-available error. if (Status == HR_PAGE_IN_PAGE_FILE) { Status = HR_PAGE_NOT_AVAILABLE; } } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::GetVirtualTranslationPhysicalOffsets( THIS_ IN ULONG64 Virtual, OUT OPTIONAL /* size_is(OffsetsSize) */ PULONG64 Offsets, IN ULONG OffsetsSize, OUT OPTIONAL PULONG Levels ) { HRESULT Status; ENTER_ENGINE(); ULONG _Levels = 0; if (!IS_CUR_MACHINE_ACCESSIBLE()) { Status = E_UNEXPECTED; } else { ULONG PfIndex; ULONG64 LastPhys; Status = g_Machine-> GetVirtualTranslationPhysicalOffsets(g_Thread, Virtual, Offsets, OffsetsSize, &_Levels, &PfIndex, &LastPhys); // GVTPO returns a special error code if the translation // succeeded down to the level of the actual data but // the data page itself is in the page file. This is used // for the page file dump support. To an external caller, // though, it's not useful so translate it into the standard // page-not-available error. if (Status == HR_PAGE_IN_PAGE_FILE) { Status = HR_PAGE_NOT_AVAILABLE; } // If no translations occurred return the given failure. // If there was a failure but translations occurred return // S_FALSE to indicate the translation was incomplete. if (_Levels > 0 && Status != S_OK) { Status = S_FALSE; } } if (Levels != NULL) { *Levels = _Levels; } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::ReadHandleData( THIS_ IN ULONG64 Handle, IN ULONG DataType, OUT OPTIONAL PVOID Buffer, IN ULONG BufferSize, OUT OPTIONAL PULONG DataSize ) { HRESULT Status; ENTER_ENGINE(); if (!g_Process) { Status = E_UNEXPECTED; } else { Status = g_Target->ReadHandleData(g_Process, Handle, DataType, Buffer, BufferSize, DataSize); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::FillVirtual( THIS_ IN ULONG64 Start, IN ULONG Size, IN PVOID Pattern, IN ULONG PatternSize, OUT OPTIONAL PULONG Filled ) { HRESULT Status; ENTER_ENGINE(); if (PatternSize == 0) { Status = E_INVALIDARG; } else if (!g_Target || !g_Process) { Status = E_UNEXPECTED; } else { ULONG _Filled = 0; Status = g_Target->FillVirtual(g_Process, Start, Size, Pattern, PatternSize, &_Filled); if (Filled != NULL) { *Filled = _Filled; } } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::FillPhysical( THIS_ IN ULONG64 Start, IN ULONG Size, IN PVOID Pattern, IN ULONG PatternSize, OUT OPTIONAL PULONG Filled ) { HRESULT Status; ENTER_ENGINE(); if (PatternSize == 0) { Status = E_INVALIDARG; } else if (!g_Target) { Status = E_UNEXPECTED; } else { ULONG _Filled = 0; Status = g_Target-> FillPhysical(Start, Size, Pattern, PatternSize, &_Filled); if (Filled != NULL) { *Filled = _Filled; } } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::QueryVirtual( THIS_ IN ULONG64 Offset, OUT PMEMORY_BASIC_INFORMATION64 Info ) { HRESULT Status; ENTER_ENGINE(); if (!g_Target || !g_Process) { Status = E_UNEXPECTED; } else if (!IS_USER_TARGET(g_Target)) { return E_NOTIMPL; } else { ULONG64 Handle = Offset; Status = g_Target->QueryMemoryRegion(g_Process, &Handle, TRUE, Info); } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::ReadImageNtHeaders( THIS_ IN ULONG64 ImageBase, OUT PIMAGE_NT_HEADERS64 Headers ) { HRESULT Status; ENTER_ENGINE(); if (!g_Target || !g_Process) { Status = E_UNEXPECTED; } else { Status = g_Target->ReadImageNtHeaders(g_Process, ImageBase, Headers); } LEAVE_ENGINE(); return Status; } HRESULT GetFirstBlobHeaderOffset(PULONG64 OffsetRet) { HRESULT Status; ULONG64 Offset; DUMP_BLOB_FILE_HEADER FileHdr; if ((Status = g_Target-> GetTaggedBaseOffset(&Offset)) != S_OK || (Status = g_Target-> ReadTagged(Offset, &FileHdr, sizeof(FileHdr))) != S_OK) { return Status; } if (FileHdr.Signature1 != DUMP_BLOB_SIGNATURE1 || FileHdr.Signature2 != DUMP_BLOB_SIGNATURE2) { // No blob data. return E_NOINTERFACE; } if (FileHdr.HeaderSize != sizeof(FileHdr)) { return HR_DATA_CORRUPT; } *OffsetRet = Offset + FileHdr.HeaderSize; return S_OK; } HRESULT ReadBlobHeaderAtOffset(ULONG64 Offset, PDUMP_BLOB_HEADER BlobHdr) { HRESULT Status; if ((Status = g_Target-> ReadTagged(Offset, BlobHdr, sizeof(*BlobHdr))) != S_OK) { return Status; } if (BlobHdr->HeaderSize != sizeof(*BlobHdr)) { return HR_DATA_CORRUPT; } return S_OK; } STDMETHODIMP DebugClient::ReadTagged( THIS_ IN LPGUID Tag, IN ULONG Offset, OUT OPTIONAL PVOID Buffer, IN ULONG BufferSize, OUT OPTIONAL PULONG TotalSize ) { HRESULT Status; ENTER_ENGINE(); if (!g_Target) { Status = E_UNEXPECTED; goto Exit; } ULONG64 HdrOffs; DUMP_BLOB_HEADER BlobHdr; if ((Status = GetFirstBlobHeaderOffset(&HdrOffs)) != S_OK) { goto Exit; } for (;;) { if ((Status = ReadBlobHeaderAtOffset(HdrOffs, &BlobHdr)) != S_OK) { // There's no way to know whether a blob should // be present or not so all failures just turn // into blob-not-present. Status = E_NOINTERFACE; goto Exit; } HdrOffs += BlobHdr.HeaderSize + BlobHdr.PrePad; if (DbgIsEqualIID(*Tag, BlobHdr.Tag)) { break; } HdrOffs += BlobHdr.DataSize + BlobHdr.PostPad; } if (Offset >= BlobHdr.DataSize) { Status = E_INVALIDARG; goto Exit; } if (TotalSize) { *TotalSize = BlobHdr.DataSize; } if (Buffer) { if (BufferSize > BlobHdr.DataSize) { BufferSize = BlobHdr.DataSize; } Status = g_Target->ReadTagged(HdrOffs + Offset, Buffer, BufferSize); } else { Status = S_OK; } Exit: LEAVE_ENGINE(); return Status; } struct TaggedEnum { ULONG64 Offset; }; STDMETHODIMP DebugClient::StartEnumTagged( THIS_ OUT PULONG64 Handle ) { HRESULT Status; ENTER_ENGINE(); TaggedEnum* Enum; if (!g_Target) { Status = E_UNEXPECTED; } else if (!(Enum = new TaggedEnum)) { Status = E_OUTOFMEMORY; } else { Status = GetFirstBlobHeaderOffset(&Enum->Offset); if (Status == S_OK) { *Handle = (ULONG64)(ULONG_PTR)Enum; } else { delete Enum; } } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::GetNextTagged( THIS_ IN ULONG64 Handle, OUT LPGUID Tag, OUT PULONG Size ) { HRESULT Status; ENTER_ENGINE(); TaggedEnum* Enum = (TaggedEnum*)(ULONG_PTR)Handle; DUMP_BLOB_HEADER BlobHdr; if (!g_Target) { Status = E_UNEXPECTED; } else if ((Status = ReadBlobHeaderAtOffset(Enum->Offset, &BlobHdr)) != S_OK) { // There's no way to know whether a blob should // be present or not so all failures just turn // into blob-not-present. Status = S_FALSE; ZeroMemory(Tag, sizeof(*Tag)); *Size = 0; } else { *Tag = BlobHdr.Tag; *Size = BlobHdr.DataSize; Enum->Offset += BlobHdr.HeaderSize + BlobHdr.PrePad + BlobHdr.DataSize + BlobHdr.PostPad; } LEAVE_ENGINE(); return Status; } STDMETHODIMP DebugClient::EndEnumTagged( THIS_ IN ULONG64 Handle ) { HRESULT Status; ENTER_ENGINE(); if (!g_Target) { Status = E_UNEXPECTED; } else { delete (TaggedEnum*)(ULONG_PTR)Handle; Status = S_OK; } LEAVE_ENGINE(); return Status; }