From 3deac1bc607f3ca4d42478f668c54eaf98c5d883 Mon Sep 17 00:00:00 2001 From: Gustavo Varo Date: Mon, 10 Mar 2025 16:24:46 -0400 Subject: [PATCH] Fix Dump_areans to correctly read memory before accessing the data --- CMakeLists.txt | 3 +- src/prim/windows/windbg/mimalloc_windbg.cpp | 2 + .../windows/windbg/mimalloc_windbg_arenas.cpp | 141 +++++++++++++++++- .../windows/windbg/mimalloc_windbg_help.cpp | 23 +++ .../windows/windbg/mimalloc_windbg_utils.cpp | 20 +++ .../windows/windbg/mimalloc_windbg_utils.h | 19 +++ 6 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 src/prim/windows/windbg/mimalloc_windbg_help.cpp 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`)