utils: dump memory info on crash (#708)

For diagnosing out of memory errors / malloc failures.
This commit is contained in:
bicarus
2026-05-28 23:57:34 -07:00
committed by GitHub
parent 3a468a9a3b
commit 489d40d87d
3 changed files with 61 additions and 0 deletions
+4
View File
@@ -14,6 +14,7 @@
#include "util/detour.h" #include "util/detour.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/memutils.h"
#include "cfg/configurator.h" #include "cfg/configurator.h"
#include "logger.h" #include "logger.h"
@@ -175,6 +176,9 @@ static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *Exception
log_warning("signal", "minidump creation function not available, skipping"); log_warning("signal", "minidump creation function not available, skipping");
} }
// dump memory information
memutils::show_available_memory();
// this will stall all UI threads for this process // this will stall all UI threads for this process
show_popup_for_crash(); show_popup_for_crash();
+55
View File
@@ -44,6 +44,61 @@ namespace memutils {
return get_mem_counters().PrivateUsage; return get_mem_counters().PrivateUsage;
} }
void show_available_memory() {
constexpr double MB = 1024.0 * 1024.0;
auto mem_status = get_mem_status();
const auto pct = [](DWORDLONG used, DWORDLONG total) -> double {
return total ? (100.0 * static_cast<double>(used) / static_cast<double>(total)) : 0.0;
};
log_info("memutils",
"system physical memory: {:.1f} / {:.1f} MiB used ({:.1f}%)",
mem_total_used() / MB, mem_total() / MB,
pct(mem_total_used(), mem_total()));
log_info("memutils",
"system page file: {:.1f} / {:.1f} MiB used ({:.1f}%, {:.1f} MiB commit headroom)",
vmem_total_used() / MB, vmem_total() / MB,
pct(vmem_total_used(), vmem_total()),
mem_status.ullAvailPageFile / MB);
// process virtual address space - 32-bit processes die at ~2 GiB
// (or ~4 GiB with /LARGEADDRESSAWARE) regardless of how much RAM is free
const DWORDLONG va_used = mem_status.ullTotalVirtual - mem_status.ullAvailVirtual;
log_info("memutils",
"process virtual address space: {:.1f} / {:.1f} MiB used ({:.1f}%)",
va_used / MB, mem_status.ullTotalVirtual / MB,
pct(va_used, mem_status.ullTotalVirtual));
// largest contiguous free block in the process address space
// (large allocations can fail due to fragmentation even with plenty available)
MEMORY_BASIC_INFORMATION mbi{};
SIZE_T largest_free = 0;
uintptr_t addr = 0;
const uintptr_t max_addr = static_cast<uintptr_t>(mem_status.ullTotalVirtual);
while (addr < max_addr &&
VirtualQuery(reinterpret_cast<LPCVOID>(addr), &mbi, sizeof(mbi)) == sizeof(mbi)) {
if (mbi.State == MEM_FREE && mbi.RegionSize > largest_free) {
largest_free = mbi.RegionSize;
}
const uintptr_t next = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
if (next <= addr) {
break;
}
addr = next;
}
log_info("memutils",
"process largest free virtual block: {:.1f} MiB ({:.1f}% of total VA)",
largest_free / MB,
pct(largest_free, mem_status.ullTotalVirtual));
log_info("memutils",
"process working set: {:.1f} MiB, private bytes: {:.1f} MiB",
mem_used() / MB, vmem_used() / MB);
}
VProtectGuard::VProtectGuard(void *addr, size_t size, DWORD mode, bool reset) VProtectGuard::VProtectGuard(void *addr, size_t size, DWORD mode, bool reset)
: addr(addr), reset(reset), size(size) : addr(addr), reset(reset), size(size)
{ {
+2
View File
@@ -14,6 +14,8 @@ namespace memutils {
DWORDLONG vmem_total_used(); DWORDLONG vmem_total_used();
DWORDLONG vmem_used(); DWORDLONG vmem_used();
void show_available_memory();
/* /*
* Helper class to unprotect/reprotect memory safely. * Helper class to unprotect/reprotect memory safely.
* It will free it's mode override on destruction. * It will free it's mode override on destruction.