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) {