mirror of
https://github.com/microsoft/mimalloc.git
synced 2025-05-07 15:59:32 +03:00
Fix Dump_areans to correctly read memory before accessing the data
This commit is contained in:
parent
922720b2af
commit
3deac1bc60
6 changed files with 204 additions and 4 deletions
|
@ -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)
|
||||
|
|
|
@ -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, "<link cmd=\"!mi_dump_options\">Dump Options</link>\n");
|
||||
g_DebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, "<link cmd=\"!mi_dump_arenas\">Dump Arenas</link>\n");
|
||||
g_DebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, "<link cmd=\"!mi_show_help\">Show Help commands</link>\n");
|
||||
g_DebugControl->Output(DEBUG_OUTPUT_NORMAL, "\n");
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
|
|
@ -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<mi_arena_t*>));
|
||||
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<ULONG64>(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<ULONG64>(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<ULONG64>(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<ULONG64>(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<ULONG64>(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<mi_arena_t*>));
|
||||
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 <arena_address>
|
||||
- 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 <arena_address>\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<ULONG64>(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<ULONG64>(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<ULONG64>(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<ULONG64>(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<ULONG64>(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;
|
||||
}
|
||||
|
|
23
src/prim/windows/windbg/mimalloc_windbg_help.cpp
Normal file
23
src/prim/windows/windbg/mimalloc_windbg_help.cpp
Normal file
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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}] <link cmd=\"{}\">{}</link> {}\n", addr, cmd, linkText, extraText).c_str());
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
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`)
|
||||
|
|
Loading…
Add table
Reference in a new issue