diff --git a/CMakeLists.txt b/CMakeLists.txt
index e1b52b68..5c292523 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -292,10 +292,11 @@ if(MI_WIN_DBG_EXTS)
endif()
if(WIN32 AND MI_WIN_DBG_EXTS)
- list(APPEND mi_sources ${PROJECT_SOURCE_DIR}/src/prim/windows/windbg/mimalloc_windbg.cpp)
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_utils.cpp)
+ list(APPEND mi_sources ${PROJECT_SOURCE_DIR}/src/prim/windows/windbg/mimalloc_windbg.cpp)
endif()
if(MI_GUARDED)
diff --git a/src/prim/windows/windbg/mimalloc_windbg.cpp b/src/prim/windows/windbg/mimalloc_windbg.cpp
index 16778884..a205ef3e 100644
--- a/src/prim/windows/windbg/mimalloc_windbg.cpp
+++ b/src/prim/windows/windbg/mimalloc_windbg.cpp
@@ -78,6 +78,8 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK DebugExtensionInitialize(PULON
g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "Start here:\n");
g_DebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, "Dump Options\n");
g_DebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, "Dump Arenas\n");
+ g_DebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, "Show Help commands\n");
+ g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n");
return S_OK;
}
diff --git a/src/prim/windows/windbg/mimalloc_windbg_arenas.cpp b/src/prim/windows/windbg/mimalloc_windbg_arenas.cpp
index 5c77e6d7..ce2aa8fe 100644
--- a/src/prim/windows/windbg/mimalloc_windbg_arenas.cpp
+++ b/src/prim/windows/windbg/mimalloc_windbg_arenas.cpp
@@ -7,6 +7,12 @@ terms of the MIT license. A copy of the license can be found in the file
#include "mimalloc_windbg_utils.h"
+/*
+Command: !mi_dump_arenas
+ - Arenas are a fundamental component of mimalloc’s memory management.
+ - A high-level dump of arenas helps diagnose overall memory allocation behavior,
+ including fragmentation and leaks.
+*/
extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_arenas(PDEBUG_CLIENT client, PCSTR args) {
UNREFERENCED_PARAMETER(args);
@@ -19,6 +25,7 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_arenas(PDEBUG_CLIENT c
return E_FAIL;
}
+ g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n");
PrintLink(subprocMainAddr, std::format("dx -r1 (*((mimalloc!mi_subproc_s *)0x{:016X}))", subprocMainAddr), "subproc_main");
mi_subproc_t subprocMain {};
@@ -30,16 +37,96 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_arenas(PDEBUG_CLIENT c
// Print results
size_t arenaCount = subprocMain.arena_count.load();
- g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "Arena count: %llu\n", arenaCount);
+
+ size_t totalSlices = 0;
+ size_t totalCommittedSlices = 0;
+ size_t totalCommittedPages = 0;
+ size_t totalAbandonedPages = 0;
+
+ for (size_t i = 0; i < arenaCount; ++i) {
+ ULONG64 arenaAddr = subprocMainAddr + offsetof(mi_subproc_t, arenas) + (i * sizeof(std::atomic));
+ ULONG64 arenaValueAddr = 0;
+ HRESULT hr = ReadMemory(arenaAddr, &arenaValueAddr, sizeof(ULONG64));
+ if (FAILED(hr) || arenaValueAddr == 0) {
+ g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to read mi_arena_t pointer at 0x%016llx.\n", arenaAddr);
+ return hr;
+ }
+
+ mi_arena_t arena {};
+ hr = ReadMemory(arenaValueAddr, &arena, sizeof(mi_arena_t));
+ if (FAILED(hr)) {
+ g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to read mi_arena_t at 0x%016llx.\n", arenaAddr);
+ return hr;
+ }
+
+ totalSlices += arena.slice_count;
+
+ // Count committed slices using the bitmap in arena->slices_committed.
+ mi_bitmap_t slicesCommitted {};
+ hr = ReadMemory(reinterpret_cast(arena.slices_committed), &slicesCommitted, sizeof(mi_bitmap_t));
+ if (FAILED(hr) || arenaValueAddr == 0) {
+ g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to read arena.slices_committed pointer at 0x%016llx.\n", reinterpret_cast(arena.slices_committed));
+ return hr;
+ }
+
+ size_t committed = mi_bitmap_count(&slicesCommitted);
+ totalCommittedSlices += committed;
+
+ // Count committed pages using the bitmap in arena->pages.
+ mi_bitmap_t pages {};
+ hr = ReadMemory(reinterpret_cast(arena.pages), &pages, sizeof(mi_bitmap_t));
+ if (FAILED(hr) || arenaValueAddr == 0) {
+ g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to read pages pointer at 0x%016llx.\n", reinterpret_cast(arena.pages));
+ return hr;
+ }
+ size_t committedPages = mi_bitmap_count(&pages);
+ totalCommittedPages += committedPages;
+
+ // For abandoned pages, iterate over each bin (0 to MI_BIN_COUNT - 1).
+ for (int bin = 0; bin < MI_BIN_COUNT; bin++) {
+ ULONG64 itemAdr = reinterpret_cast(arena.pages_abandoned[bin]);
+ mi_bitmap_t abandonedBmp {};
+ hr = ReadMemory(itemAdr, &abandonedBmp, sizeof(mi_bitmap_t));
+ if (FAILED(hr) || arenaValueAddr == 0) {
+ g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to read pages_abandoned item at 0x%016llx.\n", itemAdr);
+ return hr;
+ }
+
+ size_t abandonedPages = mi_bitmap_count(&abandonedBmp);
+ totalAbandonedPages += abandonedPages;
+ }
+ }
+
+ g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\nTotal Arenas: %d\n", arenaCount);
+
+ // Summary that aids in detecting fragmentation or undercommitment.
+ double pctCommitted = (totalSlices > 0) ? (totalCommittedSlices * 100.0 / totalSlices) : 0.0;
+ const auto arenaStats = std::format(" Total Slices: {}\n Committed Slices: {} ({}%)", totalSlices, totalCommittedSlices, pctCommitted);
+ g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\nAggregated Arena Statistics:\n%s\n", arenaStats.c_str());
+
+ const auto pageStats = std::format(" Committed Pages: {}\n Abandoned Pages: {}", totalCommittedPages, totalAbandonedPages);
+ g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\nAggregated Page Statistics:\n%s\n", pageStats.c_str());
+ g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n");
for (size_t i = 0; i < arenaCount; ++i) {
ULONG64 arenaAddr = subprocMainAddr + offsetof(mi_subproc_t, arenas) + (i * sizeof(std::atomic));
PrintLink(arenaAddr, std::format("!mi_dump_arena 0x{:016X}", arenaAddr), std::format("Arena {}", i));
}
+ g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n");
+
return S_OK;
}
+/*
+Command: !mi_dump_arena
+ - This command provides a detailed view of a specific arena, including its
+ internal structures and state.
+ - It helps diagnose issues related to specific arenas, such as memory leaks,
+ fragmentation, or incorrect state.
+ - The detailed dump includes information about slices, pages, and other
+ critical components of the arena.
+*/
extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_arena(PDEBUG_CLIENT client, PCSTR args) {
if (!args || !*args) {
g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "Usage: !mi_dump_arena \n");
@@ -75,8 +162,56 @@ extern "C" __declspec(dllexport) HRESULT CALLBACK mi_dump_arena(PDEBUG_CLIENT cl
g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "[0x%p] - Exclusive: %s\n", arenaAddr + offsetof(mi_arena_t, is_exclusive), arena.is_exclusive ? "Yes" : "No");
g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "[0x%p] - Purge Expire: %d\n", arenaAddr + offsetof(mi_arena_t, purge_expire), arena.purge_expire.load());
- // g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "[0x%p] - Slices Free (Count): %d\n", arena.slices_free->chunk_count.load());
- // g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "[0x%p] - Slices Committed (Max Accessed): %d\n", arena.slices_committed.load());
+ size_t totalSlices = 0;
+ size_t totalCommittedSlices = 0;
+ size_t totalCommittedPages = 0;
+ size_t totalAbandonedPages = 0;
+
+ totalSlices += arena.slice_count;
+
+ // Count committed slices using the bitmap in arena->slices_committed.
+ mi_bitmap_t slicesCommitted {};
+ hr = ReadMemory(reinterpret_cast(arena.slices_committed), &slicesCommitted, sizeof(mi_bitmap_t));
+ if (FAILED(hr) || arenaValueAddr == 0) {
+ g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to read arena.slices_committed pointer at 0x%016llx.\n", reinterpret_cast(arena.slices_committed));
+ return hr;
+ }
+
+ size_t committed = mi_bitmap_count(&slicesCommitted);
+ totalCommittedSlices += committed;
+
+ // Count committed pages using the bitmap in arena->pages.
+ mi_bitmap_t pages {};
+ hr = ReadMemory(reinterpret_cast(arena.pages), &pages, sizeof(mi_bitmap_t));
+ if (FAILED(hr) || arenaValueAddr == 0) {
+ g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to read pages pointer at 0x%016llx.\n", reinterpret_cast(arena.pages));
+ return hr;
+ }
+ size_t committedPages = mi_bitmap_count(&pages);
+ totalCommittedPages += committedPages;
+
+ // For abandoned pages, iterate over each bin (0 to MI_BIN_COUNT - 1).
+ for (int bin = 0; bin < MI_BIN_COUNT; bin++) {
+ ULONG64 itemAdr = reinterpret_cast(arena.pages_abandoned[bin]);
+ mi_bitmap_t abandonedBmp {};
+ hr = ReadMemory(itemAdr, &abandonedBmp, sizeof(mi_bitmap_t));
+ if (FAILED(hr) || arenaValueAddr == 0) {
+ g_DebugControl->Output(DEBUG_OUTPUT_ERROR, "ERROR: Failed to read pages_abandoned item at 0x%016llx.\n", itemAdr);
+ return hr;
+ }
+
+ size_t abandonedPages = mi_bitmap_count(&abandonedBmp);
+ totalAbandonedPages += abandonedPages;
+ }
+
+ // Summary that aids in detecting fragmentation or undercommitment.
+ double pctCommitted = (totalSlices > 0) ? (totalCommittedSlices * 100.0 / totalSlices) : 0.0;
+ const auto arenaStats = std::format(" Total Slices: {}\n Committed Slices: {} ({}%)", totalSlices, totalCommittedSlices, pctCommitted);
+ g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\nAggregated Arena Statistics:\n%s\n", arenaStats.c_str());
+
+ const auto pageStats = std::format(" Committed Pages: {}\n Abandoned Pages: {}", totalCommittedPages, totalAbandonedPages);
+ g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\nAggregated Page Statistics:\n%s\n", pageStats.c_str());
+ g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n");
return S_OK;
}
diff --git a/src/prim/windows/windbg/mimalloc_windbg_help.cpp b/src/prim/windows/windbg/mimalloc_windbg_help.cpp
new file mode 100644
index 00000000..fdda8491
--- /dev/null
+++ b/src/prim/windows/windbg/mimalloc_windbg_help.cpp
@@ -0,0 +1,23 @@
+/* ----------------------------------------------------------------------------
+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_show_help
+*/
+extern "C" __declspec(dllexport) HRESULT CALLBACK mi_show_help(PDEBUG_CLIENT client, PCSTR args) {
+ UNREFERENCED_PARAMETER(args);
+
+ HRESULT hr = S_OK;
+
+ // Print Help
+ g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "Hello from MiMalloc WinDbg Extension!\n");
+ g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "Start here:\n");
+
+ return S_OK;
+}
\ No newline at end of file
diff --git a/src/prim/windows/windbg/mimalloc_windbg_utils.cpp b/src/prim/windows/windbg/mimalloc_windbg_utils.cpp
index f4098b7b..06de0e1b 100644
--- a/src/prim/windows/windbg/mimalloc_windbg_utils.cpp
+++ b/src/prim/windows/windbg/mimalloc_windbg_utils.cpp
@@ -23,6 +23,7 @@ HRESULT FindMimallocBase() {
return g_DebugSymbols->GetModuleByModuleName("mimalloc", 0, NULL, &g_MiMallocBase);
}
+// Function to get the offset of a symbol in the debuggee process
HRESULT GetSymbolOffset(const char* symbolName, ULONG64& outOffset) {
// Ensure debug interfaces are valid
if (!g_DebugSymbols || g_MiMallocBase == 0) {
@@ -43,6 +44,7 @@ HRESULT GetSymbolOffset(const char* symbolName, ULONG64& outOffset) {
return S_OK;
}
+// Function to read memory from the debuggee process
HRESULT ReadMemory(const char* symbolName, void* outBuffer, size_t bufferSize) {
if (!g_DataSpaces) {
return E_FAIL;
@@ -65,6 +67,7 @@ HRESULT ReadMemory(const char* symbolName, void* outBuffer, size_t bufferSize) {
return S_OK;
}
+// Function to read memory from a specific address
HRESULT ReadMemory(ULONG64 address, void* outBuffer, size_t bufferSize) {
if (!g_DataSpaces) {
return E_FAIL;
@@ -80,6 +83,7 @@ HRESULT ReadMemory(ULONG64 address, void* outBuffer, size_t bufferSize) {
return S_OK;
}
+// Function to read a string from the debuggee process
HRESULT ReadString(const char* symbolName, std::string& outBuffer) {
if (!g_DataSpaces) {
return E_FAIL;
@@ -115,4 +119,20 @@ HRESULT ReadString(const char* symbolName, std::string& outBuffer) {
}
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).
+// It uses the popcount64 function (which uses __popcnt64 on MSVC) to count bits.
+size_t mi_bitmap_count(mi_bitmap_t* bmp) {
+ size_t chunkCount = bmp->chunk_count.load();
+ size_t totalCount = 0;
+ for (size_t i = 0; i < chunkCount; i++) {
+ for (size_t j = 0; j < MI_BCHUNK_FIELDS; j++) {
+ mi_bfield_t field = bmp->chunks[i].bfields[j];
+ totalCount += (size_t)popcount64(field);
+ }
+ }
+ return totalCount;
}
\ No newline at end of file
diff --git a/src/prim/windows/windbg/mimalloc_windbg_utils.h b/src/prim/windows/windbg/mimalloc_windbg_utils.h
index 5d60af5d..66d15401 100644
--- a/src/prim/windows/windbg/mimalloc_windbg_utils.h
+++ b/src/prim/windows/windbg/mimalloc_windbg_utils.h
@@ -30,12 +30,31 @@ HRESULT GetSymbolOffset(const char* symbolName, ULONG64& 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);
+size_t mi_bitmap_count(mi_bitmap_t* bmp);
inline void PrintLink(ULONG64 addr, std::string cmd, std::string linkText, std::string extraText = "") {
g_DebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL,
std::format("[0x{:016X}] {} {}\n", addr, cmd, linkText, extraText).c_str());
}
+#if defined(_MSC_VER)
+#include
+static inline int popcount64(uint64_t x) {
+ return (int)__popcnt64(x);
+}
+#else
+// Portable fallback: count bits in a 64-bit value.
+static inline int popcount64(uint64_t x) {
+ int count = 0;
+ while (x) {
+ count += x & 1;
+ x >>= 1;
+ }
+ return count;
+}
+#endif
+
+// TODO Remove the code below once it is avaialble in the mimalloc header
typedef struct mi_arena_s {
mi_memid_t memid; // memid of the memory area
mi_subproc_t* subproc; // subprocess this arena belongs to (`this 'in' this->subproc->arenas`)