From 489d40d87d25232358940d93ce06b752964d7872 Mon Sep 17 00:00:00 2001 From: bicarus <202771338+bicarus-dev@users.noreply.github.com> Date: Thu, 28 May 2026 23:57:34 -0700 Subject: [PATCH] utils: dump memory info on crash (#708) For diagnosing out of memory errors / malloc failures. --- src/spice2x/launcher/signal.cpp | 4 +++ src/spice2x/util/memutils.cpp | 55 +++++++++++++++++++++++++++++++++ src/spice2x/util/memutils.h | 2 ++ 3 files changed, 61 insertions(+) diff --git a/src/spice2x/launcher/signal.cpp b/src/spice2x/launcher/signal.cpp index 764b269..4c73e8c 100644 --- a/src/spice2x/launcher/signal.cpp +++ b/src/spice2x/launcher/signal.cpp @@ -14,6 +14,7 @@ #include "util/detour.h" #include "util/libutils.h" #include "util/logging.h" +#include "util/memutils.h" #include "cfg/configurator.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"); } + // dump memory information + memutils::show_available_memory(); + // this will stall all UI threads for this process show_popup_for_crash(); diff --git a/src/spice2x/util/memutils.cpp b/src/spice2x/util/memutils.cpp index 7ca92d0..679cc05 100644 --- a/src/spice2x/util/memutils.cpp +++ b/src/spice2x/util/memutils.cpp @@ -44,6 +44,61 @@ namespace memutils { 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(used) / static_cast(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(mem_status.ullTotalVirtual); + while (addr < max_addr && + VirtualQuery(reinterpret_cast(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(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) : addr(addr), reset(reset), size(size) { diff --git a/src/spice2x/util/memutils.h b/src/spice2x/util/memutils.h index 4c1a635..4dd980c 100644 --- a/src/spice2x/util/memutils.h +++ b/src/spice2x/util/memutils.h @@ -14,6 +14,8 @@ namespace memutils { DWORDLONG vmem_total_used(); DWORDLONG vmem_used(); + void show_available_memory(); + /* * Helper class to unprotect/reprotect memory safely. * It will free it's mode override on destruction.