diff --git a/CMakeLists.txt b/CMakeLists.txt index e4296bda..8bd96167 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -295,6 +295,7 @@ if(WIN32 AND MI_WIN_DBG_EXTS) list(APPEND mi_sources ${PROJECT_SOURCE_DIR}/src/prim/windows/windbg/mimalloc_windbg_arenas.cpp) list(APPEND mi_sources ${PROJECT_SOURCE_DIR}/src/prim/windows/windbg/mimalloc_windbg_help.cpp) list(APPEND mi_sources ${PROJECT_SOURCE_DIR}/src/prim/windows/windbg/mimalloc_windbg_options.cpp) + list(APPEND mi_sources ${PROJECT_SOURCE_DIR}/src/prim/windows/windbg/mimalloc_windbg_process_info.cpp) list(APPEND mi_sources ${PROJECT_SOURCE_DIR}/src/prim/windows/windbg/mimalloc_windbg_settings.cpp) list(APPEND mi_sources ${PROJECT_SOURCE_DIR}/src/prim/windows/windbg/mimalloc_windbg_stats.cpp) list(APPEND mi_sources ${PROJECT_SOURCE_DIR}/src/prim/windows/windbg/mimalloc_windbg_utils.cpp) diff --git a/src/prim/windows/windbg/mimalloc_windbg.cpp b/src/prim/windows/windbg/mimalloc_windbg.cpp index de00af76..fde04860 100644 --- a/src/prim/windows/windbg/mimalloc_windbg.cpp +++ b/src/prim/windows/windbg/mimalloc_windbg.cpp @@ -52,6 +52,17 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK DebugExtensionInitialize(PULON return hr; } + // Query for the IDebugSystemObjects4 interface + hr = g_DebugClient->QueryInterface(__uuidof(IDebugSystemObjects4), (void**)&g_DebugSystemObjects); + if (FAILED(hr)) { + g_DebugSymbols->Release(); + g_DebugControl->Release(); + g_DebugClient->Release(); + g_DataSpaces->Release(); + + return hr; + } + // Find mimalloc base address at startup hr = FindMimallocBase(); if (FAILED(hr) || g_MiMallocBase == 0) { @@ -80,6 +91,7 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK DebugExtensionInitialize(PULON g_DebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, "Dump Arenas\n"); g_DebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, "Dump Statistics\n"); g_DebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, "Show Extension Settings\n"); + g_DebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, "Dump Process Info\n"); g_DebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, "Show Help commands\n"); g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n"); diff --git a/src/prim/windows/windbg/mimalloc_windbg_options.cpp b/src/prim/windows/windbg/mimalloc_windbg_options.cpp index 0ef41729..15cf54c1 100644 --- a/src/prim/windows/windbg/mimalloc_windbg_options.cpp +++ b/src/prim/windows/windbg/mimalloc_windbg_options.cpp @@ -12,6 +12,8 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_options(PDEBUG_CLIENT HRESULT hr = S_OK; + g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n"); + ULONG64 optionsAddr = 0; hr = GetSymbolOffset("mi_options", optionsAddr); if (FAILED(hr) || optionsAddr == 0) { diff --git a/src/prim/windows/windbg/mimalloc_windbg_process_info.cpp b/src/prim/windows/windbg/mimalloc_windbg_process_info.cpp new file mode 100644 index 00000000..9dbf287b --- /dev/null +++ b/src/prim/windows/windbg/mimalloc_windbg_process_info.cpp @@ -0,0 +1,115 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) Microsoft Research +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +#include "mimalloc_windbg_utils.h" + +/* +Command: !mi_dump_process_info +*/ +extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_process_info(PDEBUG_CLIENT client, PCSTR args) { + UNREFERENCED_PARAMETER(args); + + HRESULT hr = S_OK; + + g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n"); + + // Get process handle + ULONG64 processHandle = 0; + hr = g_DebugSystemObjects->GetCurrentProcessHandle(&processHandle); + if (FAILED(hr)) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to get Process Handle.\n"); + return hr; + } + + // Get process ID + ULONG processId = 0; + hr = g_DebugSystemObjects->GetCurrentProcessSystemId(&processId); + if (FAILED(hr)) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to get Process ID.\n"); + return hr; + } + + // Get executable arguments + ULONG64 pebAddr = 0; + hr = g_DebugSystemObjects->GetCurrentProcessPeb(&pebAddr); + if (FAILED(hr) || pebAddr == 0) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to get PEB address.\n"); + return hr; + } + + // Get offset of ProcessParameters + ULONG processParametersOffset = 0; + hr = GetNtSymbolOffset("_PEB", "ProcessParameters", processParametersOffset); + if (FAILED(hr)) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to get ProcessParameters offset.\n"); + return hr; + } + + ULONG64 processParametersAddr = 0; + hr = ReadMemory(pebAddr + processParametersOffset, &processParametersAddr, sizeof(ULONG64)); + if (FAILED(hr)) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to read ProcessParameters address from PEB.\n"); + return hr; + } + + // Get offset of CommandLine + ULONG commandLineOffset = 0; + hr = GetNtSymbolOffset("_RTL_USER_PROCESS_PARAMETERS", "CommandLine", commandLineOffset); + if (FAILED(hr)) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to get CommandLine offset.\n"); + return hr; + } + + UNICODE_STRING cmdLine {}; + hr = ReadMemory(processParametersAddr + commandLineOffset, &cmdLine, sizeof(cmdLine)); + if (FAILED(hr) || cmdLine.Buffer == 0 || cmdLine.Length == 0) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to read CommandLine struct.\n"); + return hr; + } + + // Read the command line string + std::wstring commandLine; + hr = ReadWideString(cmdLine.Buffer, commandLine, cmdLine.Length / sizeof(WCHAR)); + if (FAILED(hr)) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to read CommandLine string.\n"); + return hr; + } + + // Get Processor Type (x86, x64, ARM64, etc.) + ULONG processorType = 0; + hr = g_DebugControl->GetActualProcessorType(&processorType); + const char* arch = (processorType == IMAGE_FILE_MACHINE_AMD64) ? "x64" + : (processorType == IMAGE_FILE_MACHINE_I386) ? "x86" + : (processorType == IMAGE_FILE_MACHINE_ARM64) ? "ARM64" + : "Unknown"; + + // Read NUMA node count + ULONG64 numaNodeOffSet = 0; + hr = GetSymbolOffset("_mi_numa_node_count", numaNodeOffSet); + if (FAILED(hr)) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to get NUMA node count address.\n"); + return hr; + } + + ULONG numaNodeCount = 0; + hr = ReadMemory(numaNodeOffSet, &numaNodeCount, sizeof(ULONG)); + if (FAILED(hr)) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to read NUMA node count.\n"); + return hr; + } + + // Output debuggee process info + g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "Debugging Process (From Debuggee Context):\n"); + g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, " Process ID : %u (0x%X)\n", processId, processId); + g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, " Command Line : %s\n", commandLine.c_str()); + g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, " Architecture : %s\n", arch); + g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, " NUMA Node : %u\n", numaNodeCount); + g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, " Process Handle : %p\n", processHandle); + + g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n"); + return S_OK; +} diff --git a/src/prim/windows/windbg/mimalloc_windbg_utils.cpp b/src/prim/windows/windbg/mimalloc_windbg_utils.cpp index 9145cf01..cb693cb0 100644 --- a/src/prim/windows/windbg/mimalloc_windbg_utils.cpp +++ b/src/prim/windows/windbg/mimalloc_windbg_utils.cpp @@ -14,6 +14,7 @@ IDebugClient* g_DebugClient = nullptr; IDebugControl4* g_DebugControl = nullptr; IDebugSymbols3* g_DebugSymbols = nullptr; IDebugDataSpaces* g_DataSpaces = nullptr; +IDebugSystemObjects4* g_DebugSystemObjects = nullptr; // Function to find mimalloc.dll base address at startup HRESULT FindMimallocBase() { @@ -45,6 +46,37 @@ HRESULT GetSymbolOffset(const char* symbolName, ULONG64& outOffset) { return S_OK; } +// Function to get the offset of a nt symbol in the debuggee process +HRESULT GetNtSymbolOffset(const char* typeName, const char* fieldName, ULONG& outOffset) { + // Ensure debug interfaces are valid + if (!g_DebugSymbols || g_MiMallocBase == 0) { + return E_FAIL; + } + + ULONG index = 0; + ULONG64 base = 0; + HRESULT hr = g_DebugSymbols->GetModuleByModuleName("ntdll", 0, &index, &base); + if (FAILED(hr) || index == 0 || base == 0) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to locate module name for ntdll"); + return E_FAIL; + } + + ULONG typeId = 0; + hr = g_DebugSymbols->GetTypeId(base, typeName, &typeId); + if (FAILED(hr) || typeId == 0) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to locate type id for: %s\n", typeName); + return E_FAIL; + } + + hr = g_DebugSymbols->GetFieldTypeAndOffset(base, typeId, fieldName, nullptr, &outOffset); + if (FAILED(hr) || outOffset == 0) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to locate symbol: %s\n", fieldName); + return E_FAIL; + } + + return S_OK; +} + // Function to read memory from the debuggee process HRESULT ReadMemory(const char* symbolName, void* outBuffer, size_t bufferSize) { if (!g_DataSpaces) { @@ -122,6 +154,24 @@ HRESULT ReadString(const char* symbolName, std::string& outBuffer) { return S_OK; } +HRESULT ReadWideString(ULONG64 address, std::wstring& outString, size_t maxLength) { + if (!g_DataSpaces) { + return E_FAIL; + } + + WCHAR buffer[1025] = {0}; // max 1024 chars + null terminator + size_t readLength = min(maxLength, 1024); + + HRESULT hr = g_DataSpaces->ReadVirtual(address, buffer, (ULONG)(readLength * sizeof(WCHAR)), nullptr); + if (FAILED(hr)) { + g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to read string from address 0x%llx.\n", address); + return hr; + } + + outString = buffer; + return S_OK; +} + // Helper function to count the number of set bits in a mi_bitmap_t. // This implementation assumes that the bitmap is organized into chunks, // and that each chunk contains MI_BCHUNK_FIELDS of type mi_bfield_t (usually a 64-bit word). diff --git a/src/prim/windows/windbg/mimalloc_windbg_utils.h b/src/prim/windows/windbg/mimalloc_windbg_utils.h index 0ac65eee..66a7472d 100644 --- a/src/prim/windows/windbg/mimalloc_windbg_utils.h +++ b/src/prim/windows/windbg/mimalloc_windbg_utils.h @@ -24,15 +24,18 @@ extern IDebugClient* g_DebugClient; extern IDebugControl4* g_DebugControl; extern IDebugSymbols3* g_DebugSymbols; extern IDebugDataSpaces* g_DataSpaces; +extern IDebugSystemObjects4* g_DebugSystemObjects; static inline double MinCommittedSlicesPct = 60.0; // if below threshold, potential fragmentation. static inline double MaxAbandonedPagesRatio = 0.4; // If abandoned pages exceed 40% of committed pages. HRESULT FindMimallocBase(); HRESULT GetSymbolOffset(const char* symbolName, ULONG64& outOffset); +HRESULT GetNtSymbolOffset(const char* typeName, const char* fieldName, ULONG& outOffset); HRESULT ReadMemory(const char* symbolName, void* outBuffer, size_t bufferSize); HRESULT ReadMemory(ULONG64 address, void* outBuffer, size_t bufferSize); HRESULT ReadString(const char* symbolName, std::string& outBuffer); +HRESULT ReadWideString(ULONG64 address, std::wstring& outString, size_t maxLength = 1024); size_t mi_bitmap_count(mi_bitmap_t* bmp); std::string FormatSize(std::size_t bytes); std::string FormatNumber(double num); @@ -42,6 +45,12 @@ inline void PrintLink(ULONG64 addr, std::string cmd, std::string linkText, std:: std::format("[0x{:016X}] {} {}\n", addr, cmd, linkText, extraText).c_str()); } +struct UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + ULONG64 Buffer; +}; + #if defined(_MSC_VER) #include static inline int popcount64(uint64_t x) {