From a241b80695828ca79c7c00725dabdb62e60b2a98 Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Thu, 18 Sep 2025 10:55:16 +0200 Subject: [PATCH 1/8] feat(crashdump): Add crash dump functionality --- Core/GameEngine/CMakeLists.txt | 2 + Core/GameEngine/Include/Common/GameMemory.h | 96 +++ Core/GameEngine/Include/Common/MiniDumper.h | 114 +++ .../GameEngine/Source/Common/System/Debug.cpp | 23 + .../Source/Common/System/GameMemory.cpp | 111 ++- .../Source/Common/System/MiniDumper.cpp | 710 ++++++++++++++++++ .../Source/WWVegas/WWLib/CMakeLists.txt | 1 + .../Source/WWVegas/WWLib/DbgHelpLoader.cpp | 28 + .../Source/WWVegas/WWLib/DbgHelpLoader.h | 39 +- .../WWVegas/WWLib/DbgHelpLoader_minidump.h | 255 +++++++ Generals/Code/Main/WinMain.cpp | 20 + GeneralsMD/Code/Main/WinMain.cpp | 20 + cmake/config-memory.cmake | 12 + 13 files changed, 1427 insertions(+), 4 deletions(-) create mode 100644 Core/GameEngine/Include/Common/MiniDumper.h create mode 100644 Core/GameEngine/Source/Common/System/MiniDumper.cpp create mode 100644 Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index faebd73f6cc..95748f544ad 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -72,6 +72,7 @@ set(GAMEENGINE_SRC # Include/Common/MapObject.h # Include/Common/MapReaderWriterInfo.h # Include/Common/MessageStream.h + Include/Common/MiniDumper.h # Include/Common/MiniLog.h Include/Common/MiscAudio.h # Include/Common/MissionStats.h @@ -660,6 +661,7 @@ set(GAMEENGINE_SRC # Source/Common/System/List.cpp Source/Common/System/LocalFile.cpp Source/Common/System/LocalFileSystem.cpp + Source/Common/System/MiniDumper.cpp Source/Common/System/ObjectStatusTypes.cpp # Source/Common/System/QuotedPrintable.cpp Source/Common/System/Radar.cpp diff --git a/Core/GameEngine/Include/Common/GameMemory.h b/Core/GameEngine/Include/Common/GameMemory.h index 8d173afec41..3a953de8351 100644 --- a/Core/GameEngine/Include/Common/GameMemory.h +++ b/Core/GameEngine/Include/Common/GameMemory.h @@ -223,6 +223,9 @@ class MemoryPool; class MemoryPoolFactory; class DynamicMemoryAllocator; class BlockCheckpointInfo; +#ifdef RTS_ENABLE_CRASHDUMP +class AllocationRangeIterator; +#endif // TYPE DEFINES /////////////////////////////////////////////////////////////// @@ -279,6 +282,14 @@ class Checkpointable }; #endif +#ifdef RTS_ENABLE_CRASHDUMP +struct MemoryPoolAllocatedRange +{ + char* allocationAddr; + size_t allocationSize; +}; +#endif + // ---------------------------------------------------------------------------- /** A MemoryPool provides a way to efficiently allocate objects of the same (or similar) @@ -384,6 +395,9 @@ class MemoryPool /// return true iff this block was allocated by this pool. Bool debugIsBlockInPool(void *pBlock); #endif +#ifdef RTS_ENABLE_CRASHDUMP + friend class AllocationRangeIterator; +#endif }; // ---------------------------------------------------------------------------- @@ -474,6 +488,10 @@ class DynamicMemoryAllocator Bool debugIsPoolInDma(MemoryPool *pool); #endif // MEMORYPOOL_DEBUG +#ifdef RTS_ENABLE_CRASHDUMP + Int getRawBlockCount() const; + void fillAllocationRangeForRawBlockN(const Int n, MemoryPoolAllocatedRange& allocationRange) const; +#endif }; // ---------------------------------------------------------------------------- @@ -481,6 +499,69 @@ class DynamicMemoryAllocator enum { MAX_SPECIAL_USED = 256 }; #endif +#ifdef RTS_ENABLE_CRASHDUMP +class AllocationRangeIterator +{ + typedef const MemoryPoolAllocatedRange value_type; + typedef const MemoryPoolAllocatedRange* pointer; + typedef const MemoryPoolAllocatedRange& reference; + +public: + + AllocationRangeIterator(const MemoryPoolFactory* factory); + AllocationRangeIterator(MemoryPool& pool, MemoryPoolBlob& blob) + { + m_currentPool = &pool; + m_currentBlobInPool = &blob; + m_factory = NULL; + m_range = MemoryPoolAllocatedRange(); + }; + + AllocationRangeIterator(MemoryPool* pool, MemoryPoolBlob* blob) + { + m_currentPool = pool; + m_currentBlobInPool = blob; + m_factory = NULL; + m_range = MemoryPoolAllocatedRange(); + }; + + AllocationRangeIterator() + { + m_currentPool = NULL; + m_currentBlobInPool = NULL; + m_factory = NULL; + m_range = MemoryPoolAllocatedRange(); + }; + + reference operator*() { UpdateRange(); return m_range; } + pointer operator->() { UpdateRange(); return &m_range; } + + // Prefix increment + AllocationRangeIterator& operator++() { MoveToNextBlob(); return *this; } + + // Postfix increment + AllocationRangeIterator operator++(int) { AllocationRangeIterator tmp = *this; ++(*this); return tmp; } + + friend const bool operator== (const AllocationRangeIterator& a, const AllocationRangeIterator& b) + { + return a.m_currentBlobInPool == b.m_currentBlobInPool; + }; + + friend const bool operator!= (const AllocationRangeIterator& a, const AllocationRangeIterator& b) + { + return a.m_currentBlobInPool != b.m_currentBlobInPool; + }; + +private: + const MemoryPoolFactory* m_factory; + MemoryPool* m_currentPool; + MemoryPoolBlob* m_currentBlobInPool; + MemoryPoolAllocatedRange m_range; + void UpdateRange(); + void MoveToNextBlob(); +}; +#endif + // ---------------------------------------------------------------------------- /** The class that manages all the MemoryPools and DynamicMemoryAllocators. @@ -573,6 +654,21 @@ class MemoryPoolFactory void debugResetCheckpoints(); #endif +#ifdef RTS_ENABLE_CRASHDUMP + AllocationRangeIterator cbegin() const + { + return AllocationRangeIterator(this); + } + + AllocationRangeIterator cend() const + { + return AllocationRangeIterator(NULL, NULL); + } + + Int getMemoryPoolCount() const; + MemoryPool* getMemoryPoolN(const Int n) const; + friend class AllocationRangeIterator; +#endif }; // how many bytes are we allowed to 'waste' per pool allocation before the debug code starts yelling at us... diff --git a/Core/GameEngine/Include/Common/MiniDumper.h b/Core/GameEngine/Include/Common/MiniDumper.h new file mode 100644 index 00000000000..1d32a736798 --- /dev/null +++ b/Core/GameEngine/Include/Common/MiniDumper.h @@ -0,0 +1,114 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#ifdef RTS_ENABLE_CRASHDUMP +#include "DbgHelpLoader.h" + +enum DumpType CPP_11(: Char) +{ + // Smallest dump type with call stacks and some supporting variables + DUMP_TYPE_MINIMAL = 'M', + // Large dump including all memory regions allocated by the GameMemory implementaion + DUMP_TYPE_GAMEMEMORY = 'X', + // Largest dump size including complete memory contents of the process + DUMP_TYPE_FULL = 'F', +}; + +enum MiniDumperExitCode CPP_11(: Int) +{ + DUMPER_EXIT_SUCCESS = 0x0, + DUMPER_EXIT_FAILURE_WAIT = 0x37DA1040, + DUMPER_EXIT_FAILURE_PARAM = 0x4EA527BB, + DUMPER_EXIT_FORCED_TERMINATE = 0x158B1154, +}; + +class MiniDumper +{ +public: + MiniDumper(); + Bool IsInitialized() const; + void TriggerMiniDump(DumpType dumpType); + void TriggerMiniDumpForException(_EXCEPTION_POINTERS* e_info, DumpType dumpType); + static void initMiniDumper(const AsciiString& userDirPath); + static void shutdownMiniDumper(); + static LONG WINAPI DumpingExceptionFilter(_EXCEPTION_POINTERS* e_info); + +private: + void Initialize(const AsciiString& userDirPath); + void ShutDown(); + void CreateMiniDump(DumpType dumpType); + BOOL DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize); + void CleanupResources(); + Bool IsDumpThreadStillRunning() const; + void ShutdownDumpThread(); + Bool ShouldWriteDataSegsForModule(const PWCHAR module) const; + + // Callbacks from dbghelp + static BOOL CALLBACK MiniDumpCallback(PVOID CallbackParam, PMINIDUMP_CALLBACK_INPUT CallbackInput, PMINIDUMP_CALLBACK_OUTPUT CallbackOutput); + BOOL CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP_CALLBACK_OUTPUT& output); + + // Thread procs + static DWORD WINAPI MiniDumpThreadProc(LPVOID lpParam); + DWORD ThreadProcInternal(); + + // Dump file directory bookkeeping + Bool InitializeDumpDirectory(const AsciiString& userDirPath); + static void KeepNewestFiles(const std::string& directory, const DumpType dumpType, const Int keepCount); + + // Struct to hold file information + struct FileInfo + { + std::string name; + FILETIME lastWriteTime; + }; + + static bool CompareByLastWriteTime(const FileInfo& a, const FileInfo& b); + +private: + Bool m_miniDumpInitialized; + Bool m_loadedDbgHelp; + DumpType m_requestedDumpType; + + // Path buffers + Char m_dumpDir[MAX_PATH]; + Char m_dumpFile[MAX_PATH]; + WideChar m_executablePath[MAX_PATH]; + + // Event handles + HANDLE m_dumpRequested; + HANDLE m_dumpComplete; + HANDLE m_quitting; + + // Thread handles + HANDLE m_dumpThread; + DWORD m_dumpThreadId; + +#ifndef DISABLE_GAMEMEMORY + // Internal memory dumping progress state + int m_dumpObjectsState; + int m_dumpObjectsSubState; + int m_dmaRawBlockIndex; + + AllocationRangeIterator m_rangeIter; +#endif +}; + +extern MiniDumper* TheMiniDumper; +#endif diff --git a/Core/GameEngine/Source/Common/System/Debug.cpp b/Core/GameEngine/Source/Common/System/Debug.cpp index 385dd856161..d06e76a323c 100644 --- a/Core/GameEngine/Source/Common/System/Debug.cpp +++ b/Core/GameEngine/Source/Common/System/Debug.cpp @@ -70,6 +70,9 @@ #if defined(DEBUG_STACKTRACE) || defined(IG_DEBUG_STACKTRACE) #include "Common/StackDump.h" #endif +#ifdef RTS_ENABLE_CRASHDUMP +#include "Common/MiniDumper.h" +#endif // Horrible reference, but we really, really need to know if we are windowed. extern bool DX8Wrapper_IsWindowed; @@ -727,6 +730,22 @@ double SimpleProfiler::getAverageTime() } } + +static void TriggerMiniDump() +{ +#ifdef RTS_ENABLE_CRASHDUMP + if (TheMiniDumper && TheMiniDumper->IsInitialized()) + { + // Do dumps both with and without extended info + TheMiniDumper->TriggerMiniDump(DUMP_TYPE_MINIMAL); + TheMiniDumper->TriggerMiniDump(DUMP_TYPE_GAMEMEMORY); + } + + MiniDumper::shutdownMiniDumper(); +#endif +} + + void ReleaseCrash(const char *reason) { /// do additional reporting on the crash, if possible @@ -737,6 +756,8 @@ void ReleaseCrash(const char *reason) } } + TriggerMiniDump(); + char prevbuf[ _MAX_PATH ]; char curbuf[ _MAX_PATH ]; @@ -813,6 +834,8 @@ void ReleaseCrashLocalized(const AsciiString& p, const AsciiString& m) return; } + TriggerMiniDump(); + UnicodeString prompt = TheGameText->fetch(p); UnicodeString mesg = TheGameText->fetch(m); diff --git a/Core/GameEngine/Source/Common/System/GameMemory.cpp b/Core/GameEngine/Source/Common/System/GameMemory.cpp index 78a855c00dc..6f379727c5c 100644 --- a/Core/GameEngine/Source/Common/System/GameMemory.cpp +++ b/Core/GameEngine/Source/Common/System/GameMemory.cpp @@ -408,9 +408,11 @@ class MemoryPoolSingleBlock #ifdef MEMORYPOOL_BOUNDINGWALL Int m_wallPattern; ///< unique seed value for the bounding-walls for this block #endif +#if defined(MEMORYPOOL_DEBUG) || defined(RTS_ENABLE_CRASHDUMP) + Int m_logicalSize; ///< logical size of block (not including overhead, walls, etc.) +#endif #ifdef MEMORYPOOL_DEBUG const char *m_debugLiteralTagString; ///< ptr to the tagstring for this block. - Int m_logicalSize; ///< logical size of block (not including overhead, walls, etc.) Int m_wastedSize; ///< if allocated via DMA, the "wasted" bytes Short m_magicCookie; ///< magic value used to verify that the block is one of ours (as opposed to random pointer) Short m_debugFlags; ///< misc flags @@ -442,10 +444,12 @@ class MemoryPoolSingleBlock MemoryPoolSingleBlock *getNextRawBlock(); void setNextRawBlock(MemoryPoolSingleBlock *b); +#if defined(MEMORYPOOL_DEBUG) || defined(RTS_ENABLE_CRASHDUMP) + Int debugGetLogicalSize(); +#endif #ifdef MEMORYPOOL_DEBUG void debugIgnoreLeaksForThisBlock(); const char *debugGetLiteralTagString(); - Int debugGetLogicalSize(); Int debugGetWastedSize(); void debugSetWastedSize(Int waste); void debugVerifyBlock(); @@ -501,6 +505,9 @@ class MemoryPoolBlob #ifdef MEMORYPOOL_CHECKPOINTING void debugResetCheckpoints(); #endif +#ifdef RTS_ENABLE_CRASHDUMP + void fillAllocatedRange(MemoryPoolAllocatedRange& range); +#endif }; @@ -640,7 +647,7 @@ inline const char *MemoryPoolSingleBlock::debugGetLiteralTagString() } #endif -#ifdef MEMORYPOOL_DEBUG +#if defined(MEMORYPOOL_DEBUG) || defined(RTS_ENABLE_CRASHDUMP) /** accessor */ @@ -875,6 +882,8 @@ void MemoryPoolSingleBlock::initBlock(Int logicalSize, MemoryPoolBlob *owningBlo } #endif } +#elif defined(RTS_ENABLE_CRASHDUMP) + m_logicalSize = logicalSize; #endif // MEMORYPOOL_DEBUG #ifdef MEMORYPOOL_CHECKPOINTING @@ -1382,6 +1391,14 @@ void MemoryPoolBlob::debugResetCheckpoints() } #endif +#ifdef RTS_ENABLE_CRASHDUMP +void MemoryPoolBlob::fillAllocatedRange(MemoryPoolAllocatedRange& range) +{ + range.allocationAddr = m_blockData; + range.allocationSize = m_totalBlocksInBlob * MemoryPoolSingleBlock::calcRawBlockSize(m_owningPool->getAllocationSize()); +} +#endif + //----------------------------------------------------------------------------- // METHODS for Checkpointable //----------------------------------------------------------------------------- @@ -2573,6 +2590,29 @@ void DynamicMemoryAllocator::debugDmaInfoReport( FILE *fp ) } #endif +#ifdef RTS_ENABLE_CRASHDUMP +Int DynamicMemoryAllocator::getRawBlockCount() const +{ + Int count = 0; + for (MemoryPoolSingleBlock* block = m_rawBlocks; block; block = block->getNextRawBlock()) + { + ++count; + } + + return count; +} +void DynamicMemoryAllocator::fillAllocationRangeForRawBlockN(const Int n, MemoryPoolAllocatedRange& allocationRange) const +{ + MemoryPoolSingleBlock* block = m_rawBlocks; + for (int i = 0; i < n; ++i) + { + block = block->getNextRawBlock(); + } + allocationRange.allocationAddr = reinterpret_cast(block); + allocationRange.allocationSize = block->calcRawBlockSize(block->debugGetLogicalSize()); +} +#endif + //----------------------------------------------------------------------------- // METHODS for MemoryPoolFactory //----------------------------------------------------------------------------- @@ -3232,6 +3272,71 @@ void MemoryPoolFactory::debugMemoryReport(Int flags, Int startCheckpoint, Int en } #endif +#ifdef RTS_ENABLE_CRASHDUMP +Int MemoryPoolFactory::getMemoryPoolCount() const +{ + Int count = 0; + MemoryPool* current = m_firstPoolInFactory; + while (current != NULL) + { + ++count; + current = current->getNextPoolInList(); + } + + return count; +} + +MemoryPool* MemoryPoolFactory::getMemoryPoolN(const Int n) const +{ + Int count = 0; + MemoryPool* current = m_firstPoolInFactory; + while (count < n && current != NULL) + { + ++count; + current = current->getNextPoolInList(); + } + + return current; +} + +AllocationRangeIterator::AllocationRangeIterator(const MemoryPoolFactory* factory) +{ + m_factory = factory; + m_currentPool = factory->m_firstPoolInFactory; + m_currentBlobInPool = m_currentPool->m_firstBlob; + m_range = MemoryPoolAllocatedRange(); +} + +void AllocationRangeIterator::UpdateRange() +{ + m_currentBlobInPool->fillAllocatedRange(m_range); +} + +void AllocationRangeIterator::MoveToNextBlob() +{ + // Advances to the next blob, advancing to the next MemoryPool if needed. + m_currentBlobInPool = m_currentBlobInPool->getNextInList(); + if (m_currentBlobInPool != NULL) + { + return; + } + do + { + m_currentPool = m_currentPool->getNextPoolInList(); + } while (m_currentPool != NULL && m_currentPool->m_firstBlob == NULL); + + if (m_currentPool != NULL) + { + m_currentBlobInPool = m_currentPool->m_firstBlob; + } + else + { + m_currentBlobInPool = NULL; + } +} + +#endif + //----------------------------------------------------------------------------- // GLOBAL FUNCTIONS //----------------------------------------------------------------------------- diff --git a/Core/GameEngine/Source/Common/System/MiniDumper.cpp b/Core/GameEngine/Source/Common/System/MiniDumper.cpp new file mode 100644 index 00000000000..a8ed689d452 --- /dev/null +++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp @@ -0,0 +1,710 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine + +#ifdef RTS_ENABLE_CRASHDUMP +#include "Common/MiniDumper.h" +#include +#include "Common/GameMemory.h" +#include "gitinfo.h" + +// Globals for storing the pointer to the exception +_EXCEPTION_POINTERS* g_dumpException = NULL; +DWORD g_dumpExceptionThreadId = 0; + +MiniDumper* TheMiniDumper = NULL; + +// Globals containing state about the current exception that's used for context in the mini dump. +// These are populated by MiniDumper::DumpingExceptionFilter to store a copy of the exception in case it goes out of scope +_EXCEPTION_POINTERS g_exceptionPointers = { 0 }; +EXCEPTION_RECORD g_exceptionRecord = { 0 }; +CONTEXT g_exceptionContext = { 0 }; + +constexpr const char* DumpFileNamePrefix = "Crash"; + +void MiniDumper::initMiniDumper(const AsciiString& userDirPath) +{ + DEBUG_ASSERTCRASH(TheMiniDumper == NULL, ("MiniDumper::initMiniDumper called on already created instance")); + + // Use placement new on the process heap so TheMiniDumper is placed outside the MemoryPoolFactory managed area. + // If the crash is due to corrupted MemoryPoolFactory structures, try to mitigate the chances of MiniDumper memory also being corrupted + TheMiniDumper = new (::HeapAlloc(::GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, sizeof(MiniDumper))) MiniDumper; + TheMiniDumper->Initialize(userDirPath); +} + +void MiniDumper::shutdownMiniDumper() +{ + if (TheMiniDumper) + { + TheMiniDumper->ShutDown(); + TheMiniDumper->~MiniDumper(); + ::HeapFree(::GetProcessHeap(), NULL, TheMiniDumper); + TheMiniDumper = NULL; + } +} + +MiniDumper::MiniDumper() +{ + m_miniDumpInitialized = false; + m_loadedDbgHelp = false; + m_requestedDumpType = DUMP_TYPE_MINIMAL; + m_dumpRequested = NULL; + m_dumpComplete = NULL; + m_quitting = NULL; + m_dumpThread = NULL; + m_dumpThreadId = 0; +#ifndef DISABLE_GAMEMEMORY + m_dumpObjectsState = 0; + m_dumpObjectsSubState = 0; + m_dmaRawBlockIndex = 0; +#endif + m_dumpDir[0] = 0; + m_dumpFile[0] = 0; + m_executablePath[0] = 0; +}; + +LONG WINAPI MiniDumper::DumpingExceptionFilter(_EXCEPTION_POINTERS* e_info) +{ + // Store the exception info in the global variables for later use by the dumping thread + g_exceptionRecord = *(e_info->ExceptionRecord); + g_exceptionContext = *(e_info->ContextRecord); + g_exceptionPointers.ContextRecord = &g_exceptionContext; + g_exceptionPointers.ExceptionRecord = &g_exceptionRecord; + g_dumpException = &g_exceptionPointers; + + return EXCEPTION_EXECUTE_HANDLER; +} + +void MiniDumper::TriggerMiniDump(DumpType dumpType) +{ + if (!m_miniDumpInitialized) + { + DEBUG_LOG(("MiniDumper::TriggerMiniDump: Attempted to use an uninitialized instance.")); + return; + } + + __try + { + // Use DebugBreak to raise an exception that can be caught in the __except block + ::DebugBreak(); + } + __except (DumpingExceptionFilter(GetExceptionInformation())) + { + TriggerMiniDumpForException(g_dumpException, dumpType); + } +} + +void MiniDumper::TriggerMiniDumpForException(_EXCEPTION_POINTERS* e_info, DumpType dumpType) +{ + if (!m_miniDumpInitialized) + { + DEBUG_LOG(("MiniDumper::TriggerMiniDumpForException: Attempted to use an uninitialized instance.")); + return; + } + + g_dumpException = e_info; + g_dumpExceptionThreadId = ::GetCurrentThreadId(); + m_requestedDumpType = dumpType; +#ifdef DISABLE_GAMEMEMORY + if (m_requestedDumpType == DUMP_TYPE_GAMEMEMORY) + { + // Dump the whole process if the game memory implementation is turned off + m_requestedDumpType = DUMP_TYPE_FULL; + } +#endif + + DEBUG_ASSERTCRASH(IsDumpThreadStillRunning(), ("MiniDumper::TriggerMiniDumpForException: Dumping thread has exited.")); + ::SetEvent(m_dumpRequested); + DWORD wait = ::WaitForSingleObject(m_dumpComplete, INFINITE); + if (wait != WAIT_OBJECT_0) + { + if (wait == WAIT_FAILED) + { + DEBUG_LOG(("MiniDumper::TriggerMiniDumpForException: Waiting for minidump triggering failed: status=%u, error=%u", wait, ::GetLastError())); + } + else + { + DEBUG_LOG(("MiniDumper::TriggerMiniDumpForException: Waiting for minidump triggering failed: status=%u", wait)); + } + } + + ::ResetEvent(m_dumpComplete); +} + +void MiniDumper::Initialize(const AsciiString& userDirPath) +{ + m_loadedDbgHelp = DbgHelpLoader::load(); + + // We want to only use the dbghelp.dll from the OS installation, as the one bundled with the game does not support MiniDump functionality + if (!(m_loadedDbgHelp && DbgHelpLoader::isLoadedFromSystem())) + { + DEBUG_LOG(("MiniDumper::Initialize: Unable to load system-provided dbghelp.dll, minidump functionality disabled.")); + return; + } + + DWORD executableSize = ::GetModuleFileNameW(NULL, m_executablePath, ARRAY_SIZE(m_executablePath)); + if (executableSize == 0 || executableSize == ARRAY_SIZE(m_executablePath)) + { + DEBUG_LOG(("MiniDumper::Initialize: Could not get executable file name. Returned value=%u", executableSize)); + return; + } + + // Create & store dump folder + if (!InitializeDumpDirectory(userDirPath)) + { + return; + } + + m_dumpRequested = CreateEvent(NULL, TRUE, FALSE, NULL); + m_dumpComplete = CreateEvent(NULL, TRUE, FALSE, NULL); + m_quitting = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (m_dumpRequested == NULL || m_dumpComplete == NULL || m_quitting == NULL) + { + // Something went wrong with the creation of the events.. + DEBUG_LOG(("MiniDumper::Initialize: Unable to create events: error=%u", ::GetLastError())); + return; + } + + m_dumpThread = ::CreateThread(NULL, 0, MiniDumpThreadProc, this, CREATE_SUSPENDED, &m_dumpThreadId); + if (!m_dumpThread) + { + DEBUG_LOG(("MiniDumper::Initialize: Unable to create thread: error=%u", ::GetLastError())); + return; + } + + if (::ResumeThread(m_dumpThread) != 1) + { + DEBUG_LOG(("MiniDumper::Initialize: Unable to resume thread: error=%u", ::GetLastError())); + return; + } + + DEBUG_LOG(("MiniDumper::Initialize: Configured to store crash dumps in '%s'", m_dumpDir)); + m_miniDumpInitialized = true; +} + +Bool MiniDumper::IsInitialized() const +{ + return m_miniDumpInitialized; +} + +Bool MiniDumper::IsDumpThreadStillRunning() const +{ + DWORD exitCode; + if (m_dumpThread != NULL && ::GetExitCodeThread(m_dumpThread, &exitCode) && exitCode == STILL_ACTIVE) + { + return true; + } + + return false; +} + +Bool MiniDumper::InitializeDumpDirectory(const AsciiString& userDirPath) +{ + constexpr const Int MaxExtendedFileCount = 2; + constexpr const Int MaxFullFileCount = 2; + constexpr const Int MaxMiniFileCount = 10; + + strlcpy(m_dumpDir, userDirPath.str(), ARRAY_SIZE(m_dumpDir)); + strlcat(m_dumpDir, "CrashDumps\\", ARRAY_SIZE(m_dumpDir)); + if (::_access(m_dumpDir, 0) != 0) + { + if (!::CreateDirectory(m_dumpDir, NULL)) + { + DEBUG_LOG(("MiniDumper::Initialize: Unable to create path for crash dumps at '%s': error=%u", m_dumpDir, ::GetLastError())); + return false; + } + } + + // Clean up old files (we keep a maximum of 10 small, 2 extended and 2 full) + KeepNewestFiles(m_dumpDir, DUMP_TYPE_GAMEMEMORY, MaxExtendedFileCount); + KeepNewestFiles(m_dumpDir, DUMP_TYPE_FULL, MaxFullFileCount); + KeepNewestFiles(m_dumpDir, DUMP_TYPE_MINIMAL, MaxMiniFileCount); + + return true; +} + +void MiniDumper::ShutdownDumpThread() +{ + if (IsDumpThreadStillRunning()) + { + DEBUG_ASSERTCRASH(m_quitting != NULL, ("MiniDumper::ShutdownDumpThread: Dump thread still running despite m_quitting being NULL")); + ::SetEvent(m_quitting); + + DWORD waitRet = ::WaitForSingleObject(m_dumpThread, 3000); + switch (waitRet) + { + case WAIT_OBJECT_0: + // Wait for thread exit was successful + break; + case WAIT_TIMEOUT: + DEBUG_LOG(("MiniDumper::ShutdownDumpThread: Waiting for dumping thread to exit timed out, killing thread", waitRet)); + ::TerminateThread(m_dumpThread, DUMPER_EXIT_FORCED_TERMINATE); + break; + case WAIT_FAILED: + DEBUG_LOG(("MiniDumper::ShutdownDumpThread: Waiting for minidump triggering failed: status=%u, error=%u", waitRet, ::GetLastError())); + break; + default: + DEBUG_LOG(("MiniDumper::ShutdownDumpThread: Waiting for minidump triggering failed: status=%u", waitRet)); + break; + } + } +} + +void MiniDumper::ShutDown() +{ + ShutdownDumpThread(); + + if (m_dumpThread != NULL) + { + DEBUG_ASSERTCRASH(!IsDumpThreadStillRunning(), ("MiniDumper::ShutDown: ShutdownDumpThread() was unable to stop Dump thread")); + ::CloseHandle(m_dumpThread); + m_dumpThread = NULL; + } + + if (m_quitting != NULL) + { + ::CloseHandle(m_quitting); + m_quitting = NULL; + } + + if (m_dumpComplete != NULL) + { + ::CloseHandle(m_dumpComplete); + m_dumpComplete = NULL; + } + + if (m_dumpRequested != NULL) + { + ::CloseHandle(m_dumpRequested); + m_dumpRequested = NULL; + } + + if (m_loadedDbgHelp) + { + DbgHelpLoader::unload(); + m_loadedDbgHelp = false; + } + + m_miniDumpInitialized = false; +} + +DWORD MiniDumper::ThreadProcInternal() +{ + while (true) + { + HANDLE waitEvents[2] = { m_dumpRequested, m_quitting }; + DWORD event = ::WaitForMultipleObjects(ARRAY_SIZE(waitEvents), waitEvents, FALSE, INFINITE); + switch (event) + { + case WAIT_OBJECT_0 + 0: + // A dump is requested (m_dumpRequested) + ::ResetEvent(m_dumpComplete); + CreateMiniDump(m_requestedDumpType); + ::ResetEvent(m_dumpRequested); + ::SetEvent(m_dumpComplete); + break; + case WAIT_OBJECT_0 + 1: + // Quit (m_quitting) + return DUMPER_EXIT_SUCCESS; + case WAIT_FAILED: + DEBUG_LOG(("MiniDumper::ThreadProcInternal: Waiting for events failed: status=%u, error=%u", event, ::GetLastError())); + return DUMPER_EXIT_FAILURE_WAIT; + default: + DEBUG_LOG(("MiniDumper::ThreadProcInternal: Waiting for events failed: status=%u", event)); + return DUMPER_EXIT_FAILURE_WAIT; + } + } +} + +DWORD WINAPI MiniDumper::MiniDumpThreadProc(LPVOID lpParam) +{ + if (lpParam == NULL) + { + DEBUG_LOG(("MiniDumper::MiniDumpThreadProc: The provided parameter was NULL, exiting thread.")); + return DUMPER_EXIT_FAILURE_PARAM; + } + + MiniDumper* dumper = static_cast(lpParam); + return dumper->ThreadProcInternal(); +} + + +void MiniDumper::CreateMiniDump(DumpType dumpType) +{ + // Create a unique dump file name, using the path from m_dumpDir, in m_dumpFile + SYSTEMTIME sysTime; + ::GetLocalTime(&sysTime); +#if RTS_GENERALS + const Char product = 'G'; +#elif RTS_ZEROHOUR + const Char product = 'Z'; +#endif + Char dumpTypeSpecifier = static_cast(dumpType); + DWORD currentProcessId = ::GetCurrentProcessId(); + + // m_dumpDir is stored with trailing backslash in Initialize + snprintf(m_dumpFile, ARRAY_SIZE(m_dumpFile), "%s%s%c%c-%04d%02d%02d-%02d%02d%02d-%s-pid%ld.dmp", + m_dumpDir, DumpFileNamePrefix, dumpTypeSpecifier, product, sysTime.wYear, sysTime.wMonth, + sysTime.wDay, sysTime.wHour, sysTime.wMinute, sysTime.wSecond, + GitShortSHA1, currentProcessId); + + HANDLE dumpFile = ::CreateFile(m_dumpFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (dumpFile == NULL || dumpFile == INVALID_HANDLE_VALUE) + { + DEBUG_LOG(("MiniDumper::CreateMiniDump: Unable to create dump file '%s': error=%u", m_dumpFile, ::GetLastError())); + return; + } + + PMINIDUMP_EXCEPTION_INFORMATION exceptionInfoPtr = NULL; + MINIDUMP_EXCEPTION_INFORMATION exceptionInfo = { 0 }; + if (g_dumpException != NULL) + { + exceptionInfo.ExceptionPointers = g_dumpException; + exceptionInfo.ThreadId = g_dumpExceptionThreadId; + exceptionInfo.ClientPointers = FALSE; + exceptionInfoPtr = &exceptionInfo; + } + + PMINIDUMP_CALLBACK_INFORMATION callbackInfoPtr = NULL; + MINIDUMP_CALLBACK_INFORMATION callBackInfo = { 0 }; + if (dumpType == DUMP_TYPE_GAMEMEMORY) + { + callBackInfo.CallbackRoutine = MiniDumpCallback; + callBackInfo.CallbackParam = this; + callbackInfoPtr = &callBackInfo; + } + + int dumpTypeFlags = MiniDumpNormal; + switch (dumpType) + { + case DUMP_TYPE_FULL: + dumpTypeFlags |= MiniDumpWithFullMemory; + FALLTHROUGH; + case DUMP_TYPE_GAMEMEMORY: + dumpTypeFlags |= MiniDumpWithDataSegs | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithFullMemoryInfo | MiniDumpWithPrivateReadWriteMemory; + FALLTHROUGH; + case DUMP_TYPE_MINIMAL: + dumpTypeFlags |= MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory; + break; + } + + MINIDUMP_TYPE miniDumpType = static_cast(dumpTypeFlags); + BOOL success = DbgHelpLoader::miniDumpWriteDump( + ::GetCurrentProcess(), + currentProcessId, + dumpFile, + miniDumpType, + exceptionInfoPtr, + NULL, + callbackInfoPtr); + + if (!success) + { + DEBUG_LOG(("MiniDumper::CreateMiniDump: Unable to write minidump file '%s': error=%u", m_dumpFile, ::GetLastError())); + } + else + { + DEBUG_LOG(("MiniDumper::CreateMiniDump: Successfully wrote minidump file to '%s'", m_dumpFile)); + } + + ::CloseHandle(dumpFile); +} + +BOOL CALLBACK MiniDumper::MiniDumpCallback(PVOID CallbackParam, PMINIDUMP_CALLBACK_INPUT CallbackInput, PMINIDUMP_CALLBACK_OUTPUT CallbackOutput) +{ + if (CallbackParam == NULL || CallbackInput == NULL || CallbackOutput == NULL) + { + DEBUG_LOG(("MiniDumper::MiniDumpCallback: Required parameters were null; CallbackParam=%p, CallbackInput=%p, CallbackOutput=%p.", + CallbackParam, CallbackInput, CallbackOutput)); + return false; + } + + MiniDumper* dumper = static_cast(CallbackParam); + return dumper->CallbackInternal(*CallbackInput, *CallbackOutput); +} + +Bool MiniDumper::ShouldWriteDataSegsForModule(const PWCHAR module) const +{ + // Only include data segments for the game, ntdll and kernel32 modules to keep dump size low + static constexpr const WideChar* wanted_modules[] = { L"ntdll.dll", L"kernel32.dll"}; + if (endsWithNoCase(module, m_executablePath)) + { + return true; + } + + for (size_t i = 0; i < ARRAY_SIZE(wanted_modules); ++i) + { + if (endsWithNoCase(module, wanted_modules[i])) + { + return true; + } + } + + return false; +} + +// This is where the memory regions and things are being filtered +BOOL MiniDumper::CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP_CALLBACK_OUTPUT& output) +{ + BOOL retVal = TRUE; + switch (input.CallbackType) + { + case IncludeModuleCallback: + retVal = TRUE; + break; + case ModuleCallback: + if (output.ModuleWriteFlags & ModuleWriteDataSeg) + { + if (!ShouldWriteDataSegsForModule(input.Module.FullPath)) + { + // Exclude data segments for the module + output.ModuleWriteFlags &= (~ModuleWriteDataSeg); + } + } + + retVal = TRUE; + break; + case IncludeThreadCallback: + // We want all threads except the dumping thread + if (input.IncludeThread.ThreadId == m_dumpThreadId) + { + retVal = FALSE; + } + break; + case ThreadCallback: + retVal = TRUE; + break; + case ThreadExCallback: + retVal = TRUE; + break; + case MemoryCallback: +#ifndef DISABLE_GAMEMEMORY + do + { + // DumpMemoryObjects will return false once it's completed, signalling the end of memory callbacks + retVal = DumpMemoryObjects(output.MemoryBase, output.MemorySize); + } while ((output.MemoryBase == NULL || output.MemorySize == NULL) && retVal == TRUE); +#else + retVal = FALSE; +#endif + break; + case ReadMemoryFailureCallback: + DEBUG_LOG(("MiniDumper::CallbackInternal: ReadMemoryFailure with MemoryBase=%llu, MemorySize=%lu: error=%u", + input.ReadMemoryFailure.Offset, input.ReadMemoryFailure.Bytes, input.ReadMemoryFailure.FailureStatus)); + retVal = TRUE; + break; + case CancelCallback: + output.Cancel = FALSE; + output.CheckCancel = FALSE; + retVal = TRUE; + break; + } + + return retVal; +} + +#ifndef DISABLE_GAMEMEMORY +BOOL MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) +{ + BOOL moreToDo = TRUE; + // m_dumpObjectsState is used to keep track of the current "phase" of the memory dumping process + // m_dumpObjectsSubState is used to keep track of the progress within each phase, and is reset when advancing on to the next phase + switch (m_dumpObjectsState) + { + case 0: + { + // Dump all the MemoryPool instances in TheMemoryPoolFactory + // This only dumps the metadata, not the actual MemoryPool contents (done in the next phase). + if (TheMemoryPoolFactory == NULL) + { + ++m_dumpObjectsState; + break; + } + + Int poolCount = TheMemoryPoolFactory->getMemoryPoolCount(); + //m_dumpObjectsSubState contains the index in TheMemoryPoolFactory of the MemoryPool that is being processed + if (m_dumpObjectsSubState < poolCount) + { + MemoryPool* pool = TheMemoryPoolFactory->getMemoryPoolN(m_dumpObjectsSubState); + if (pool != NULL) + { + memoryBase = reinterpret_cast(pool); + memorySize = sizeof(MemoryPool); + ++m_dumpObjectsSubState; + } + else + { + m_dumpObjectsSubState = poolCount; + } + } + + if (m_dumpObjectsSubState == poolCount) + { + m_dumpObjectsSubState = 0; + ++m_dumpObjectsState; + } + break; + } + case 1: + { + // Iterate through all the allocations of memory pools and containing blobs that has been done via the memory pool factory + // and include all of the storage space allocated for objects + if (TheMemoryPoolFactory == NULL) + { + ++m_dumpObjectsState; + break; + } + + //m_dumpObjectsSubState is used to track if the iterator needs to be initialized, otherwise just a counter of the number of items dumped + if (m_dumpObjectsSubState == 0) + { + m_rangeIter = TheMemoryPoolFactory->cbegin(); + ++m_dumpObjectsSubState; + } + + // m_RangeIter should != cend() at this point before advancing, unless the memory pool factory is corrupted (or has 0 entries) + memoryBase = reinterpret_cast(m_rangeIter->allocationAddr); + memorySize = m_rangeIter->allocationSize; + ++m_dumpObjectsSubState; + ++m_rangeIter; + + if (m_rangeIter == TheMemoryPoolFactory->cend()) + { + ++m_dumpObjectsState; + m_dumpObjectsSubState = 0; + } + break; + } + case 2: + { + // Iterate through all the direct allocations ("raw blocks") done by DMAs, as these are done outside of the + // memory pool factory allocations dumped in the previous phase. + if (TheDynamicMemoryAllocator == NULL) + { + ++m_dumpObjectsState; + break; + } + + DynamicMemoryAllocator* allocator = TheDynamicMemoryAllocator; + + //m_dumpObjectsSubState is used to track the index of the allocator we are currently traversing + for (int i = 0; i < m_dumpObjectsSubState; ++i) + { + allocator = allocator->getNextDmaInList(); + } + + MemoryPoolAllocatedRange rawBlockRange = {0}; + int rawBlocksInDma = allocator->getRawBlockCount(); + if (m_dmaRawBlockIndex < rawBlocksInDma) + { + // Dump this block + allocator->fillAllocationRangeForRawBlockN(m_dmaRawBlockIndex, rawBlockRange); + memoryBase = reinterpret_cast(rawBlockRange.allocationAddr); + memorySize = rawBlockRange.allocationSize; + ++m_dmaRawBlockIndex; + } + + if (rawBlocksInDma == m_dmaRawBlockIndex) + { + // Advance to the next DMA + ++m_dumpObjectsSubState; + m_dmaRawBlockIndex = 0; + if (allocator->getNextDmaInList() == NULL) + { + // Done iterating through all the DMAs + m_dumpObjectsSubState = 0; + ++m_dumpObjectsState; + } + } + break; + } + default: + // Done, set "no more stuff" values + m_dumpObjectsState = 0; + m_dumpObjectsSubState = 0; + m_dmaRawBlockIndex = 0; + memoryBase = 0; + memorySize = 0; + moreToDo = FALSE; + break; + } + + return moreToDo; +} +#endif + +// Comparator for sorting files by last modified time (newest first) +bool MiniDumper::CompareByLastWriteTime(const FileInfo& a, const FileInfo& b) +{ + return ::CompareFileTime(&a.lastWriteTime, &b.lastWriteTime) > 0; +} + +void MiniDumper::KeepNewestFiles(const std::string& directory, const DumpType dumpType, const Int keepCount) +{ + // directory already contains trailing backslash + std::string searchPath = directory + DumpFileNamePrefix + static_cast(dumpType) + "*"; + WIN32_FIND_DATA findData; + HANDLE hFind = ::FindFirstFile(searchPath.c_str(), &findData); + + if (hFind == INVALID_HANDLE_VALUE) + { + if (::GetLastError() != ERROR_FILE_NOT_FOUND) + { + DEBUG_LOG(("MiniDumper::KeepNewestFiles: Unable to find files in directory '%s': error=%u", searchPath.c_str(), ::GetLastError())); + } + + return; + } + + std::vector files; + do + { + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + continue; + } + + // Store file info + FileInfo fileInfo; + fileInfo.name = directory + findData.cFileName; + fileInfo.lastWriteTime = findData.ftLastWriteTime; + files.push_back(fileInfo); + + } while (::FindNextFile(hFind, &findData)); + + ::FindClose(hFind); + + // Sort files by last modified time in descending order + std::sort(files.begin(), files.end(), CompareByLastWriteTime); + + // Delete files beyond the newest keepCount + for (size_t i = keepCount; i < files.size(); ++i) + { + if (::DeleteFile(files[i].name.c_str())) + { + DEBUG_LOG(("MiniDumper::KeepNewestFiles: Deleted old dump file '%s'.", files[i].name.c_str())); + } + else + { + DEBUG_LOG(("MiniDumper::KeepNewestFiles: Failed to delete file '%s': error=%u", files[i].name.c_str(), ::GetLastError())); + } + } +} +#endif diff --git a/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt index 3eec2f42f8e..997a6c02420 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt @@ -36,6 +36,7 @@ set(WWLIB_SRC DbgHelpGuard.h DbgHelpLoader.cpp DbgHelpLoader.h + DbgHelpLoader_minidump.h Except.cpp Except.h FastAllocator.cpp diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp index 96898d5097f..368bcd03ca7 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp +++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp @@ -33,6 +33,9 @@ DbgHelpLoader::DbgHelpLoader() , m_symSetOptions(NULL) , m_symFunctionTableAccess(NULL) , m_stackWalk(NULL) +#ifdef RTS_ENABLE_CRASHDUMP + , m_miniDumpWriteDump(NULL) +#endif , m_dllModule(HMODULE(0)) , m_referenceCount(0) , m_failed(false) @@ -118,6 +121,9 @@ bool DbgHelpLoader::load() Inst->m_symSetOptions = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymSetOptions")); Inst->m_symFunctionTableAccess = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymFunctionTableAccess")); Inst->m_stackWalk = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "StackWalk")); +#ifdef RTS_ENABLE_CRASHDUMP + Inst->m_miniDumpWriteDump = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "MiniDumpWriteDump")); +#endif if (Inst->m_symInitialize == NULL || Inst->m_symCleanup == NULL) { @@ -171,6 +177,9 @@ void DbgHelpLoader::freeResources() Inst->m_symSetOptions = NULL; Inst->m_symFunctionTableAccess = NULL; Inst->m_stackWalk = NULL; +#ifdef RTS_ENABLE_CRASHDUMP + Inst->m_miniDumpWriteDump = NULL; +#endif Inst->m_loadedFromSystem = false; } @@ -332,3 +341,22 @@ BOOL DbgHelpLoader::stackWalk( return FALSE; } + +#ifdef RTS_ENABLE_CRASHDUMP +BOOL DbgHelpLoader::miniDumpWriteDump( + HANDLE hProcess, + DWORD ProcessId, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + PMINIDUMP_CALLBACK_INFORMATION CallbackParam) +{ + CriticalSectionClass::LockClass lock(CriticalSection); + + if (Inst != NULL && Inst->m_miniDumpWriteDump) + return Inst->m_miniDumpWriteDump(hProcess, ProcessId, hFile, DumpType, ExceptionParam, UserStreamParam, CallbackParam); + + return FALSE; +} +#endif diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h index c2167db075e..9bc0dce4194 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h +++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h @@ -23,6 +23,9 @@ #include #include // Must be included after Windows.h #include +#ifdef RTS_ENABLE_CRASHDUMP +#include +#endif #include "mutex.h" #include "SystemAllocator.h" @@ -109,9 +112,30 @@ class DbgHelpLoader PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE TranslateAddress); +#ifdef RTS_ENABLE_CRASHDUMP + static BOOL WINAPI miniDumpWriteDump( + HANDLE hProcess, + DWORD ProcessId, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + PMINIDUMP_CALLBACK_INFORMATION CallbackParam); +#endif + private: static void freeResources(); +#ifdef RTS_ENABLE_CRASHDUMP + typedef BOOL(WINAPI* MiniDumpWriteDump_t)( + HANDLE hProcess, + DWORD ProcessId, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + PMINIDUMP_CALLBACK_INFORMATION CallbackParam); +#endif typedef BOOL (WINAPI *SymInitialize_t) ( HANDLE hProcess, @@ -167,6 +191,17 @@ class DbgHelpLoader PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE TranslateAddress); +#ifdef RTS_ENABLE_CRASHDUMP + typedef BOOL(WINAPI* MiniDumpWriteDump_t)( + HANDLE hProcess, + DWORD ProcessId, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + PMINIDUMP_CALLBACK_INFORMATION CallbackParam); +#endif + SymInitialize_t m_symInitialize; SymCleanup_t m_symCleanup; SymLoadModule_t m_symLoadModule; @@ -177,13 +212,15 @@ class DbgHelpLoader SymSetOptions_t m_symSetOptions; SymFunctionTableAccess_t m_symFunctionTableAccess; StackWalk_t m_stackWalk; +#ifdef RTS_ENABLE_CRASHDUMP + MiniDumpWriteDump_t m_miniDumpWriteDump; +#endif typedef std::set, stl::system_allocator > Processes; Processes m_initializedProcesses; HMODULE m_dllModule; int m_referenceCount; - CriticalSectionClass m_criticalSection; bool m_failed; bool m_loadedFromSystem; }; diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h new file mode 100644 index 00000000000..a882dbd6705 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h @@ -0,0 +1,255 @@ +#pragma once + +#ifdef RTS_ENABLE_CRASHDUMP + +// Backported defines from minidumpapiset.h for VC6. +// minidumpapiset.h is Copyright (C) Microsoft Corporation. All rights reserved. + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#include + +typedef enum _MINIDUMP_CALLBACK_TYPE { + ModuleCallback, + ThreadCallback, + ThreadExCallback, + IncludeThreadCallback, + IncludeModuleCallback, + MemoryCallback, + CancelCallback, + WriteKernelMinidumpCallback, + KernelMinidumpStatusCallback, + RemoveMemoryCallback, + IncludeVmRegionCallback, + IoStartCallback, + IoWriteAllCallback, + IoFinishCallback, + ReadMemoryFailureCallback, + SecondaryFlagsCallback, + IsProcessSnapshotCallback, + VmStartCallback, + VmQueryCallback, + VmPreReadCallback, + VmPostReadCallback +} MINIDUMP_CALLBACK_TYPE; + +typedef struct _MINIDUMP_THREAD_CALLBACK { + ULONG ThreadId; + HANDLE ThreadHandle; +#if defined(_ARM64_) + ULONG Pad; +#endif + CONTEXT Context; + ULONG SizeOfContext; + ULONG64 StackBase; + ULONG64 StackEnd; +} MINIDUMP_THREAD_CALLBACK, * PMINIDUMP_THREAD_CALLBACK; + +typedef struct _MINIDUMP_THREAD_EX_CALLBACK { + ULONG ThreadId; + HANDLE ThreadHandle; +#if defined(_ARM64_) + ULONG Pad; +#endif + CONTEXT Context; + ULONG SizeOfContext; + ULONG64 StackBase; + ULONG64 StackEnd; + ULONG64 BackingStoreBase; + ULONG64 BackingStoreEnd; +} MINIDUMP_THREAD_EX_CALLBACK, * PMINIDUMP_THREAD_EX_CALLBACK; + +typedef struct _MINIDUMP_MODULE_CALLBACK { + PWCHAR FullPath; + ULONG64 BaseOfImage; + ULONG SizeOfImage; + ULONG CheckSum; + ULONG TimeDateStamp; + VS_FIXEDFILEINFO VersionInfo; + PVOID CvRecord; + ULONG SizeOfCvRecord; + PVOID MiscRecord; + ULONG SizeOfMiscRecord; +} MINIDUMP_MODULE_CALLBACK, * PMINIDUMP_MODULE_CALLBACK; + +typedef struct _MINIDUMP_INCLUDE_THREAD_CALLBACK { + ULONG ThreadId; +} MINIDUMP_INCLUDE_THREAD_CALLBACK, * PMINIDUMP_INCLUDE_THREAD_CALLBACK; + +typedef struct _MINIDUMP_INCLUDE_MODULE_CALLBACK { + ULONG64 BaseOfImage; +} MINIDUMP_INCLUDE_MODULE_CALLBACK, * PMINIDUMP_INCLUDE_MODULE_CALLBACK; + +typedef struct _MINIDUMP_IO_CALLBACK { + HANDLE Handle; + ULONG64 Offset; + PVOID Buffer; + ULONG BufferBytes; +} MINIDUMP_IO_CALLBACK, * PMINIDUMP_IO_CALLBACK; + +typedef struct _MINIDUMP_READ_MEMORY_FAILURE_CALLBACK +{ + ULONG64 Offset; + ULONG Bytes; + HRESULT FailureStatus; +} MINIDUMP_READ_MEMORY_FAILURE_CALLBACK, +* PMINIDUMP_READ_MEMORY_FAILURE_CALLBACK; + +typedef struct _MINIDUMP_VM_QUERY_CALLBACK +{ + ULONG64 Offset; +} MINIDUMP_VM_QUERY_CALLBACK, * PMINIDUMP_VM_QUERY_CALLBACK; + +typedef struct _MINIDUMP_VM_PRE_READ_CALLBACK +{ + ULONG64 Offset; + PVOID Buffer; + ULONG Size; +} MINIDUMP_VM_PRE_READ_CALLBACK, * PMINIDUMP_VM_PRE_READ_CALLBACK; + +typedef struct _MINIDUMP_VM_POST_READ_CALLBACK +{ + ULONG64 Offset; + PVOID Buffer; + ULONG Size; + ULONG Completed; + HRESULT Status; +} MINIDUMP_VM_POST_READ_CALLBACK, * PMINIDUMP_VM_POST_READ_CALLBACK; + +typedef struct _MINIDUMP_MEMORY_INFO { + ULONG64 BaseAddress; + ULONG64 AllocationBase; + ULONG32 AllocationProtect; + ULONG32 __alignment1; + ULONG64 RegionSize; + ULONG32 State; + ULONG32 Protect; + ULONG32 Type; + ULONG32 __alignment2; +} MINIDUMP_MEMORY_INFO, * PMINIDUMP_MEMORY_INFO; + +typedef struct _MINIDUMP_CALLBACK_INPUT { + ULONG ProcessId; + HANDLE ProcessHandle; + ULONG CallbackType; + union { + HRESULT Status; + MINIDUMP_THREAD_CALLBACK Thread; + MINIDUMP_THREAD_EX_CALLBACK ThreadEx; + MINIDUMP_MODULE_CALLBACK Module; + MINIDUMP_INCLUDE_THREAD_CALLBACK IncludeThread; + MINIDUMP_INCLUDE_MODULE_CALLBACK IncludeModule; + MINIDUMP_IO_CALLBACK Io; + MINIDUMP_READ_MEMORY_FAILURE_CALLBACK ReadMemoryFailure; + ULONG SecondaryFlags; + MINIDUMP_VM_QUERY_CALLBACK VmQuery; + MINIDUMP_VM_PRE_READ_CALLBACK VmPreRead; + MINIDUMP_VM_POST_READ_CALLBACK VmPostRead; + }; +} MINIDUMP_CALLBACK_INPUT, * PMINIDUMP_CALLBACK_INPUT; + +typedef struct _MINIDUMP_CALLBACK_OUTPUT { + union { + ULONG ModuleWriteFlags; + ULONG ThreadWriteFlags; + ULONG SecondaryFlags; + struct { + ULONG64 MemoryBase; + ULONG MemorySize; + }; + struct { + BOOL CheckCancel; + BOOL Cancel; + }; + HANDLE Handle; + struct { + MINIDUMP_MEMORY_INFO VmRegion; + BOOL Continue; + }; + struct { + HRESULT VmQueryStatus; + MINIDUMP_MEMORY_INFO VmQueryResult; + }; + struct { + HRESULT VmReadStatus; + ULONG VmReadBytesCompleted; + }; + HRESULT Status; + }; +} MINIDUMP_CALLBACK_OUTPUT, * PMINIDUMP_CALLBACK_OUTPUT; + +typedef struct _MINIDUMP_EXCEPTION_INFORMATION { + DWORD ThreadId; + PEXCEPTION_POINTERS ExceptionPointers; + BOOL ClientPointers; +} MINIDUMP_EXCEPTION_INFORMATION, * PMINIDUMP_EXCEPTION_INFORMATION; + +typedef struct _MINIDUMP_USER_STREAM { + ULONG32 Type; + ULONG BufferSize; + PVOID Buffer; + +} MINIDUMP_USER_STREAM, * PMINIDUMP_USER_STREAM; + +typedef struct _MINIDUMP_USER_STREAM_INFORMATION { + ULONG UserStreamCount; + PMINIDUMP_USER_STREAM UserStreamArray; +} MINIDUMP_USER_STREAM_INFORMATION, * PMINIDUMP_USER_STREAM_INFORMATION; + +typedef +BOOL +(WINAPI* MINIDUMP_CALLBACK_ROUTINE) ( + PVOID CallbackParam, + PMINIDUMP_CALLBACK_INPUT CallbackInput, + PMINIDUMP_CALLBACK_OUTPUT CallbackOutput + ); + +typedef struct _MINIDUMP_CALLBACK_INFORMATION { + MINIDUMP_CALLBACK_ROUTINE CallbackRoutine; + PVOID CallbackParam; +} MINIDUMP_CALLBACK_INFORMATION, * PMINIDUMP_CALLBACK_INFORMATION; + +typedef enum _MINIDUMP_TYPE { + MiniDumpNormal = 0x00000000, + MiniDumpWithDataSegs = 0x00000001, + MiniDumpWithFullMemory = 0x00000002, + MiniDumpWithHandleData = 0x00000004, + MiniDumpFilterMemory = 0x00000008, + MiniDumpScanMemory = 0x00000010, + MiniDumpWithUnloadedModules = 0x00000020, + MiniDumpWithIndirectlyReferencedMemory = 0x00000040, + MiniDumpFilterModulePaths = 0x00000080, + MiniDumpWithProcessThreadData = 0x00000100, + MiniDumpWithPrivateReadWriteMemory = 0x00000200, + MiniDumpWithoutOptionalData = 0x00000400, + MiniDumpWithFullMemoryInfo = 0x00000800, + MiniDumpWithThreadInfo = 0x00001000, + MiniDumpWithCodeSegs = 0x00002000, + MiniDumpWithoutAuxiliaryState = 0x00004000, + MiniDumpWithFullAuxiliaryState = 0x00008000, + MiniDumpWithPrivateWriteCopyMemory = 0x00010000, + MiniDumpIgnoreInaccessibleMemory = 0x00020000, + MiniDumpWithTokenInformation = 0x00040000, + MiniDumpWithModuleHeaders = 0x00080000, + MiniDumpFilterTriage = 0x00100000, + MiniDumpWithAvxXStateContext = 0x00200000, + MiniDumpWithIptTrace = 0x00400000, + MiniDumpScanInaccessiblePartialPages = 0x00800000, + MiniDumpFilterWriteCombinedMemory = 0x01000000, + MiniDumpValidTypeFlags = 0x01ffffff, + MiniDumpNoIgnoreInaccessibleMemory = 0x02000000, + MiniDumpValidTypeFlagsEx = 0x03ffffff, +} MINIDUMP_TYPE; + +typedef enum _MODULE_WRITE_FLAGS { + ModuleWriteModule = 0x0001, + ModuleWriteDataSeg = 0x0002, + ModuleWriteMiscRecord = 0x0004, + ModuleWriteCvRecord = 0x0008, + ModuleReferencedByMemory = 0x0010, + ModuleWriteTlsData = 0x0020, + ModuleWriteCodeSegs = 0x0040, +} MODULE_WRITE_FLAGS; + +#include +#endif +#endif diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index 303d6da1cec..47e25f17494 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -64,6 +64,9 @@ #include "BuildVersion.h" #include "GeneratedVersion.h" #include "resource.h" +#ifdef RTS_ENABLE_CRASHDUMP +#include "Common/MiniDumper.h" +#endif // GLOBALS //////////////////////////////////////////////////////////////////// @@ -741,6 +744,16 @@ static CriticalSection critSec1, critSec2, critSec3, critSec4, critSec5; static LONG WINAPI UnHandledExceptionFilter( struct _EXCEPTION_POINTERS* e_info ) { DumpExceptionInfo( e_info->ExceptionRecord->ExceptionCode, e_info ); +#ifdef RTS_ENABLE_CRASHDUMP + if (TheMiniDumper && TheMiniDumper->IsInitialized()) + { + // Do dumps both with and without extended info + TheMiniDumper->TriggerMiniDumpForException(e_info, DUMP_TYPE_MINIMAL); + TheMiniDumper->TriggerMiniDumpForException(e_info, DUMP_TYPE_GAMEMEMORY); + } + + MiniDumper::shutdownMiniDumper(); +#endif return EXCEPTION_EXECUTE_HANDLER; } @@ -801,6 +814,10 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, CommandLine::parseCommandLineForStartup(); +#ifdef RTS_ENABLE_CRASHDUMP + // Initialize minidump facilities - requires TheGlobalData so performed after parseCommandLineForStartup + MiniDumper::initMiniDumper(TheGlobalData->getPath_UserData()); +#endif // register windows class and create application window if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) return exitcode; @@ -868,6 +885,9 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, } +#ifdef RTS_ENABLE_CRASHDUMP + MiniDumper::shutdownMiniDumper(); +#endif TheAsciiStringCriticalSection = NULL; TheUnicodeStringCriticalSection = NULL; TheDmaCriticalSection = NULL; diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 6017276941f..4c5cd64f703 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -67,6 +67,9 @@ #include "resource.h" #include +#ifdef RTS_ENABLE_CRASHDUMP +#include "Common/MiniDumper.h" +#endif // GLOBALS //////////////////////////////////////////////////////////////////// @@ -763,6 +766,16 @@ static CriticalSection critSec1, critSec2, critSec3, critSec4, critSec5; static LONG WINAPI UnHandledExceptionFilter( struct _EXCEPTION_POINTERS* e_info ) { DumpExceptionInfo( e_info->ExceptionRecord->ExceptionCode, e_info ); +#ifdef RTS_ENABLE_CRASHDUMP + if (TheMiniDumper && TheMiniDumper->IsInitialized()) + { + // Do dumps both with and without extended info + TheMiniDumper->TriggerMiniDumpForException(e_info, DUMP_TYPE_MINIMAL); + TheMiniDumper->TriggerMiniDumpForException(e_info, DUMP_TYPE_GAMEMEMORY); + } + + MiniDumper::shutdownMiniDumper(); +#endif return EXCEPTION_EXECUTE_HANDLER; } @@ -847,6 +860,10 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, #endif CommandLine::parseCommandLineForStartup(); +#ifdef RTS_ENABLE_CRASHDUMP + // Initialize minidump facilities - requires TheGlobalData so performed after parseCommandLineForStartup + MiniDumper::initMiniDumper(TheGlobalData->getPath_UserData()); +#endif // register windows class and create application window if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) @@ -916,6 +933,9 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, } +#ifdef RTS_ENABLE_CRASHDUMP + MiniDumper::shutdownMiniDumper(); +#endif TheUnicodeStringCriticalSection = NULL; TheDmaCriticalSection = NULL; TheMemoryPoolCriticalSection = NULL; diff --git a/cmake/config-memory.cmake b/cmake/config-memory.cmake index 72a28d1c94e..a9f34038983 100644 --- a/cmake/config-memory.cmake +++ b/cmake/config-memory.cmake @@ -20,6 +20,9 @@ option(RTS_MEMORYPOOL_DEBUG_INTENSE_VERIFY "Enables intensive verifications afte option(RTS_MEMORYPOOL_DEBUG_CHECK_BLOCK_OWNERSHIP "Enables debug to verify that a block actually belongs to the pool it is called with. This is great for debugging, but can be realllly slow, so is OFF by default." OFF) option(RTS_MEMORYPOOL_DEBUG_INTENSE_DMA_BOOKKEEPING "Prints statistics for memory usage of Memory Pools." OFF) +# Memory dump options +option(RTS_CRASHDUMP_ENABLE "Enables writing crash dumps on unhandled exceptions or release crash failures." ON) + # Game Memory features add_feature_info(GameMemoryEnable RTS_GAMEMEMORY_ENABLE "Build with the original game memory implementation") @@ -37,6 +40,8 @@ add_feature_info(MemoryPoolDebugIntenseVerify RTS_MEMORYPOOL_DEBUG_INTENSE_VERIF add_feature_info(MemoryPoolDebugCheckBlockOwnership RTS_MEMORYPOOL_DEBUG_CHECK_BLOCK_OWNERSHIP "Build with Memory Pool block ownership checks") add_feature_info(MemoryPoolDebugIntenseDmaBookkeeping RTS_MEMORYPOOL_DEBUG_INTENSE_DMA_BOOKKEEPING "Build with Memory Pool intense DMA bookkeeping") +# Memory dump features +add_feature_info(CrashDumpEnable "RTS_CRASHDUMP_ENABLE" "Build with Crash Dumps") # Game Memory features if(NOT RTS_GAMEMEMORY_ENABLE) @@ -87,3 +92,10 @@ else() target_compile_definitions(core_config INTERFACE INTENSE_DMA_BOOKKEEPING=1) endif() endif() + +if(RTS_CRASHDUMP_ENABLE) + target_compile_definitions(core_config INTERFACE RTS_ENABLE_CRASHDUMP=1) + if (IS_VS6_BUILD AND NOT RTS_BUILD_OPTION_VC6_FULL_DEBUG) + message(STATUS "Crash Dumps will be significantly less useful in VC6 builds without full debug info enabled") + endif() +endif() From 5e543ba237890ff596e2cc68f9d1e28b0fdfbe74 Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Thu, 27 Nov 2025 18:41:46 +0100 Subject: [PATCH 2/8] Refactor CallbackInternal and DumpMemoryObjects --- Core/GameEngine/Include/Common/MiniDumper.h | 16 +++- .../Source/Common/System/MiniDumper.cpp | 90 ++++++++----------- .../WWVegas/WWLib/DbgHelpLoader_minidump.h | 10 +++ 3 files changed, 61 insertions(+), 55 deletions(-) diff --git a/Core/GameEngine/Include/Common/MiniDumper.h b/Core/GameEngine/Include/Common/MiniDumper.h index 1d32a736798..213536d1896 100644 --- a/Core/GameEngine/Include/Common/MiniDumper.h +++ b/Core/GameEngine/Include/Common/MiniDumper.h @@ -39,6 +39,14 @@ enum MiniDumperExitCode CPP_11(: Int) DUMPER_EXIT_FORCED_TERMINATE = 0x158B1154, }; +enum DumpObjectsState CPP_11(: Int) +{ + MEMORY_POOLS, + MEMORY_POOL_ALLOCATIONS, + DMA_ALLOCATIONS, + COMPLETED +}; + class MiniDumper { public: @@ -54,7 +62,7 @@ class MiniDumper void Initialize(const AsciiString& userDirPath); void ShutDown(); void CreateMiniDump(DumpType dumpType); - BOOL DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize); + void DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize); void CleanupResources(); Bool IsDumpThreadStillRunning() const; void ShutdownDumpThread(); @@ -102,9 +110,9 @@ class MiniDumper #ifndef DISABLE_GAMEMEMORY // Internal memory dumping progress state - int m_dumpObjectsState; - int m_dumpObjectsSubState; - int m_dmaRawBlockIndex; + DumpObjectsState m_dumpObjectsState; + Int m_dumpObjectsSubState; + DynamicMemoryAllocator* m_currentAllocator; AllocationRangeIterator m_rangeIter; #endif diff --git a/Core/GameEngine/Source/Common/System/MiniDumper.cpp b/Core/GameEngine/Source/Common/System/MiniDumper.cpp index a8ed689d452..f7413a034fe 100644 --- a/Core/GameEngine/Source/Common/System/MiniDumper.cpp +++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp @@ -70,9 +70,9 @@ MiniDumper::MiniDumper() m_dumpThread = NULL; m_dumpThreadId = 0; #ifndef DISABLE_GAMEMEMORY - m_dumpObjectsState = 0; + m_dumpObjectsState = MEMORY_POOLS; m_dumpObjectsSubState = 0; - m_dmaRawBlockIndex = 0; + m_currentAllocator = NULL; #endif m_dumpDir[0] = 0; m_dumpFile[0] = 0; @@ -372,6 +372,8 @@ void MiniDumper::CreateMiniDump(DumpType dumpType) return; } + m_dumpObjectsState = MEMORY_POOLS; + PMINIDUMP_EXCEPTION_INFORMATION exceptionInfoPtr = NULL; MINIDUMP_EXCEPTION_INFORMATION exceptionInfo = { 0 }; if (g_dumpException != NULL) @@ -463,11 +465,12 @@ Bool MiniDumper::ShouldWriteDataSegsForModule(const PWCHAR module) const // This is where the memory regions and things are being filtered BOOL MiniDumper::CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP_CALLBACK_OUTPUT& output) { - BOOL retVal = TRUE; + BOOL success = TRUE; switch (input.CallbackType) { case IncludeModuleCallback: - retVal = TRUE; + case ThreadCallback: + case ThreadExCallback: break; case ModuleCallback: if (output.ModuleWriteFlags & ModuleWriteDataSeg) @@ -478,63 +481,53 @@ BOOL MiniDumper::CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP output.ModuleWriteFlags &= (~ModuleWriteDataSeg); } } - - retVal = TRUE; break; case IncludeThreadCallback: // We want all threads except the dumping thread if (input.IncludeThread.ThreadId == m_dumpThreadId) { - retVal = FALSE; + output.ThreadWriteFlags &= (~ThreadWriteThread); } break; - case ThreadCallback: - retVal = TRUE; - break; - case ThreadExCallback: - retVal = TRUE; - break; case MemoryCallback: #ifndef DISABLE_GAMEMEMORY do { - // DumpMemoryObjects will return false once it's completed, signalling the end of memory callbacks - retVal = DumpMemoryObjects(output.MemoryBase, output.MemorySize); - } while ((output.MemoryBase == NULL || output.MemorySize == NULL) && retVal == TRUE); + // DumpMemoryObjects will set outputMemorySize to 0 once it's completed, signalling the end of memory callbacks + DumpMemoryObjects(output.MemoryBase, output.MemorySize); + } while ((output.MemoryBase == 0 || output.MemorySize == 0) && m_dumpObjectsState != COMPLETED); #else - retVal = FALSE; + output.MemoryBase = 0; + output.MemorySize = 0; #endif break; case ReadMemoryFailureCallback: DEBUG_LOG(("MiniDumper::CallbackInternal: ReadMemoryFailure with MemoryBase=%llu, MemorySize=%lu: error=%u", input.ReadMemoryFailure.Offset, input.ReadMemoryFailure.Bytes, input.ReadMemoryFailure.FailureStatus)); - retVal = TRUE; break; case CancelCallback: output.Cancel = FALSE; output.CheckCancel = FALSE; - retVal = TRUE; break; } - return retVal; + return success; } #ifndef DISABLE_GAMEMEMORY -BOOL MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) +void MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) { - BOOL moreToDo = TRUE; // m_dumpObjectsState is used to keep track of the current "phase" of the memory dumping process // m_dumpObjectsSubState is used to keep track of the progress within each phase, and is reset when advancing on to the next phase switch (m_dumpObjectsState) { - case 0: + case MEMORY_POOLS: { // Dump all the MemoryPool instances in TheMemoryPoolFactory // This only dumps the metadata, not the actual MemoryPool contents (done in the next phase). if (TheMemoryPoolFactory == NULL) { - ++m_dumpObjectsState; + m_dumpObjectsState = MEMORY_POOL_ALLOCATIONS; break; } @@ -558,17 +551,17 @@ BOOL MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) if (m_dumpObjectsSubState == poolCount) { m_dumpObjectsSubState = 0; - ++m_dumpObjectsState; + m_dumpObjectsState = MEMORY_POOL_ALLOCATIONS; } break; } - case 1: + case MEMORY_POOL_ALLOCATIONS: { // Iterate through all the allocations of memory pools and containing blobs that has been done via the memory pool factory // and include all of the storage space allocated for objects if (TheMemoryPoolFactory == NULL) { - ++m_dumpObjectsState; + m_dumpObjectsState = DMA_ALLOCATIONS; break; } @@ -587,66 +580,61 @@ BOOL MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) if (m_rangeIter == TheMemoryPoolFactory->cend()) { - ++m_dumpObjectsState; + m_dumpObjectsState = DMA_ALLOCATIONS; m_dumpObjectsSubState = 0; } break; } - case 2: + case DMA_ALLOCATIONS: { // Iterate through all the direct allocations ("raw blocks") done by DMAs, as these are done outside of the // memory pool factory allocations dumped in the previous phase. if (TheDynamicMemoryAllocator == NULL) { - ++m_dumpObjectsState; + m_dumpObjectsState = COMPLETED; break; } - DynamicMemoryAllocator* allocator = TheDynamicMemoryAllocator; - - //m_dumpObjectsSubState is used to track the index of the allocator we are currently traversing - for (int i = 0; i < m_dumpObjectsSubState; ++i) + if (m_currentAllocator == NULL) { - allocator = allocator->getNextDmaInList(); + m_currentAllocator = TheDynamicMemoryAllocator; + // m_dumpObjectsSubState is used to track the index of the raw block in the allocator we are currently traversing + m_dumpObjectsSubState = 0; } MemoryPoolAllocatedRange rawBlockRange = {0}; - int rawBlocksInDma = allocator->getRawBlockCount(); - if (m_dmaRawBlockIndex < rawBlocksInDma) + int rawBlocksInDma = m_currentAllocator->getRawBlockCount(); + if (m_dumpObjectsSubState < rawBlocksInDma) { // Dump this block - allocator->fillAllocationRangeForRawBlockN(m_dmaRawBlockIndex, rawBlockRange); + m_currentAllocator->fillAllocationRangeForRawBlockN(m_dumpObjectsSubState, rawBlockRange); memoryBase = reinterpret_cast(rawBlockRange.allocationAddr); memorySize = rawBlockRange.allocationSize; - ++m_dmaRawBlockIndex; + ++m_dumpObjectsSubState; } - if (rawBlocksInDma == m_dmaRawBlockIndex) + if (rawBlocksInDma == m_dumpObjectsSubState) { // Advance to the next DMA - ++m_dumpObjectsSubState; - m_dmaRawBlockIndex = 0; - if (allocator->getNextDmaInList() == NULL) + m_currentAllocator = m_currentAllocator->getNextDmaInList(); + m_dumpObjectsSubState = 0; + + if (m_currentAllocator == NULL) { // Done iterating through all the DMAs - m_dumpObjectsSubState = 0; - ++m_dumpObjectsState; + m_dumpObjectsState = COMPLETED; } } break; } default: - // Done, set "no more stuff" values - m_dumpObjectsState = 0; + // Done, set "no more regions to dump" values + m_dumpObjectsState = COMPLETED; m_dumpObjectsSubState = 0; - m_dmaRawBlockIndex = 0; memoryBase = 0; memorySize = 0; - moreToDo = FALSE; break; } - - return moreToDo; } #endif diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h index a882dbd6705..272fa832f52 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h +++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h @@ -75,6 +75,16 @@ typedef struct _MINIDUMP_INCLUDE_THREAD_CALLBACK { ULONG ThreadId; } MINIDUMP_INCLUDE_THREAD_CALLBACK, * PMINIDUMP_INCLUDE_THREAD_CALLBACK; +typedef enum _THREAD_WRITE_FLAGS { + ThreadWriteThread = 0x0001, + ThreadWriteStack = 0x0002, + ThreadWriteContext = 0x0004, + ThreadWriteBackingStore = 0x0008, + ThreadWriteInstructionWindow = 0x0010, + ThreadWriteThreadData = 0x0020, + ThreadWriteThreadInfo = 0x0040, +} THREAD_WRITE_FLAGS; + typedef struct _MINIDUMP_INCLUDE_MODULE_CALLBACK { ULONG64 BaseOfImage; } MINIDUMP_INCLUDE_MODULE_CALLBACK, * PMINIDUMP_INCLUDE_MODULE_CALLBACK; From 7399ccee343fe45363cff57a7d12eaff0fae20bc Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Thu, 27 Nov 2025 23:31:57 +0100 Subject: [PATCH 3/8] Improve iteration in DumpMemoryObjects --- Core/GameEngine/Include/Common/GameMemory.h | 10 +- Core/GameEngine/Include/Common/MiniDumper.h | 4 +- .../Source/Common/System/GameMemory.cpp | 53 +++------ .../Source/Common/System/MiniDumper.cpp | 105 ++++++------------ 4 files changed, 58 insertions(+), 114 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameMemory.h b/Core/GameEngine/Include/Common/GameMemory.h index 3a953de8351..4f3d23b6127 100644 --- a/Core/GameEngine/Include/Common/GameMemory.h +++ b/Core/GameEngine/Include/Common/GameMemory.h @@ -285,7 +285,7 @@ class Checkpointable #ifdef RTS_ENABLE_CRASHDUMP struct MemoryPoolAllocatedRange { - char* allocationAddr; + const char* allocationAddr; size_t allocationSize; }; #endif @@ -489,8 +489,9 @@ class DynamicMemoryAllocator #endif // MEMORYPOOL_DEBUG #ifdef RTS_ENABLE_CRASHDUMP - Int getRawBlockCount() const; - void fillAllocationRangeForRawBlockN(const Int n, MemoryPoolAllocatedRange& allocationRange) const; + MemoryPoolSingleBlock* getFirstRawBlock() const; + MemoryPoolSingleBlock* getNextRawBlock(MemoryPoolSingleBlock* block) const; + void fillAllocationRangeForRawBlock(const MemoryPoolSingleBlock*, MemoryPoolAllocatedRange& allocationRange) const; #endif }; @@ -665,8 +666,7 @@ class MemoryPoolFactory return AllocationRangeIterator(NULL, NULL); } - Int getMemoryPoolCount() const; - MemoryPool* getMemoryPoolN(const Int n) const; + MemoryPool* getFirstMemoryPool() const; friend class AllocationRangeIterator; #endif }; diff --git a/Core/GameEngine/Include/Common/MiniDumper.h b/Core/GameEngine/Include/Common/MiniDumper.h index 213536d1896..68b8661ef30 100644 --- a/Core/GameEngine/Include/Common/MiniDumper.h +++ b/Core/GameEngine/Include/Common/MiniDumper.h @@ -41,6 +41,7 @@ enum MiniDumperExitCode CPP_11(: Int) enum DumpObjectsState CPP_11(: Int) { + BEGIN, MEMORY_POOLS, MEMORY_POOL_ALLOCATIONS, DMA_ALLOCATIONS, @@ -111,8 +112,9 @@ class MiniDumper #ifndef DISABLE_GAMEMEMORY // Internal memory dumping progress state DumpObjectsState m_dumpObjectsState; - Int m_dumpObjectsSubState; DynamicMemoryAllocator* m_currentAllocator; + MemoryPool* m_currentPool; + MemoryPoolSingleBlock* m_currentSingleBlock; AllocationRangeIterator m_rangeIter; #endif diff --git a/Core/GameEngine/Source/Common/System/GameMemory.cpp b/Core/GameEngine/Source/Common/System/GameMemory.cpp index 6f379727c5c..a5524e534b8 100644 --- a/Core/GameEngine/Source/Common/System/GameMemory.cpp +++ b/Core/GameEngine/Source/Common/System/GameMemory.cpp @@ -445,7 +445,7 @@ class MemoryPoolSingleBlock void setNextRawBlock(MemoryPoolSingleBlock *b); #if defined(MEMORYPOOL_DEBUG) || defined(RTS_ENABLE_CRASHDUMP) - Int debugGetLogicalSize(); + Int debugGetLogicalSize() const; #endif #ifdef MEMORYPOOL_DEBUG void debugIgnoreLeaksForThisBlock(); @@ -651,7 +651,7 @@ inline const char *MemoryPoolSingleBlock::debugGetLiteralTagString() /** accessor */ -inline Int MemoryPoolSingleBlock::debugGetLogicalSize() +inline Int MemoryPoolSingleBlock::debugGetLogicalSize() const { //USE_PERF_TIMER(MemoryPoolDebugging) not worth it return m_logicalSize; @@ -2591,24 +2591,19 @@ void DynamicMemoryAllocator::debugDmaInfoReport( FILE *fp ) #endif #ifdef RTS_ENABLE_CRASHDUMP -Int DynamicMemoryAllocator::getRawBlockCount() const +MemoryPoolSingleBlock* DynamicMemoryAllocator::getFirstRawBlock() const { - Int count = 0; - for (MemoryPoolSingleBlock* block = m_rawBlocks; block; block = block->getNextRawBlock()) - { - ++count; - } + return m_rawBlocks; +} - return count; +MemoryPoolSingleBlock* DynamicMemoryAllocator::getNextRawBlock(MemoryPoolSingleBlock* block) const +{ + return block->getNextRawBlock(); } -void DynamicMemoryAllocator::fillAllocationRangeForRawBlockN(const Int n, MemoryPoolAllocatedRange& allocationRange) const + +void DynamicMemoryAllocator::fillAllocationRangeForRawBlock(const MemoryPoolSingleBlock* block, MemoryPoolAllocatedRange& allocationRange) const { - MemoryPoolSingleBlock* block = m_rawBlocks; - for (int i = 0; i < n; ++i) - { - block = block->getNextRawBlock(); - } - allocationRange.allocationAddr = reinterpret_cast(block); + allocationRange.allocationAddr = reinterpret_cast(block); allocationRange.allocationSize = block->calcRawBlockSize(block->debugGetLogicalSize()); } #endif @@ -3273,30 +3268,9 @@ void MemoryPoolFactory::debugMemoryReport(Int flags, Int startCheckpoint, Int en #endif #ifdef RTS_ENABLE_CRASHDUMP -Int MemoryPoolFactory::getMemoryPoolCount() const +MemoryPool* MemoryPoolFactory::getFirstMemoryPool() const { - Int count = 0; - MemoryPool* current = m_firstPoolInFactory; - while (current != NULL) - { - ++count; - current = current->getNextPoolInList(); - } - - return count; -} - -MemoryPool* MemoryPoolFactory::getMemoryPoolN(const Int n) const -{ - Int count = 0; - MemoryPool* current = m_firstPoolInFactory; - while (count < n && current != NULL) - { - ++count; - current = current->getNextPoolInList(); - } - - return current; + return m_firstPoolInFactory; } AllocationRangeIterator::AllocationRangeIterator(const MemoryPoolFactory* factory) @@ -3334,7 +3308,6 @@ void AllocationRangeIterator::MoveToNextBlob() m_currentBlobInPool = NULL; } } - #endif //----------------------------------------------------------------------------- diff --git a/Core/GameEngine/Source/Common/System/MiniDumper.cpp b/Core/GameEngine/Source/Common/System/MiniDumper.cpp index f7413a034fe..9baa51984f5 100644 --- a/Core/GameEngine/Source/Common/System/MiniDumper.cpp +++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp @@ -71,8 +71,9 @@ MiniDumper::MiniDumper() m_dumpThreadId = 0; #ifndef DISABLE_GAMEMEMORY m_dumpObjectsState = MEMORY_POOLS; - m_dumpObjectsSubState = 0; m_currentAllocator = NULL; + m_currentSingleBlock = NULL; + m_currentPool = NULL; #endif m_dumpDir[0] = 0; m_dumpFile[0] = 0; @@ -372,7 +373,7 @@ void MiniDumper::CreateMiniDump(DumpType dumpType) return; } - m_dumpObjectsState = MEMORY_POOLS; + m_dumpObjectsState = BEGIN; PMINIDUMP_EXCEPTION_INFORMATION exceptionInfoPtr = NULL; MINIDUMP_EXCEPTION_INFORMATION exceptionInfo = { 0 }; @@ -495,7 +496,7 @@ BOOL MiniDumper::CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP { // DumpMemoryObjects will set outputMemorySize to 0 once it's completed, signalling the end of memory callbacks DumpMemoryObjects(output.MemoryBase, output.MemorySize); - } while ((output.MemoryBase == 0 || output.MemorySize == 0) && m_dumpObjectsState != COMPLETED); + } while (output.MemorySize == 0 && m_dumpObjectsState != COMPLETED); #else output.MemoryBase = 0; output.MemorySize = 0; @@ -506,6 +507,8 @@ BOOL MiniDumper::CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP input.ReadMemoryFailure.Offset, input.ReadMemoryFailure.Bytes, input.ReadMemoryFailure.FailureStatus)); break; case CancelCallback: + // The cancel callback checks if the current minidump should be cancelled. + // Set output to never wish to cancel, nor to receive cancel callbacks in the future. output.Cancel = FALSE; output.CheckCancel = FALSE; break; @@ -521,119 +524,85 @@ void MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) // m_dumpObjectsSubState is used to keep track of the progress within each phase, and is reset when advancing on to the next phase switch (m_dumpObjectsState) { + case BEGIN: + m_dumpObjectsState = MEMORY_POOLS; + if (TheMemoryPoolFactory) + { + m_currentPool = TheMemoryPoolFactory->getFirstMemoryPool(); + } + break; case MEMORY_POOLS: { // Dump all the MemoryPool instances in TheMemoryPoolFactory // This only dumps the metadata, not the actual MemoryPool contents (done in the next phase). - if (TheMemoryPoolFactory == NULL) + if (m_currentPool == NULL) { m_dumpObjectsState = MEMORY_POOL_ALLOCATIONS; - break; - } - - Int poolCount = TheMemoryPoolFactory->getMemoryPoolCount(); - //m_dumpObjectsSubState contains the index in TheMemoryPoolFactory of the MemoryPool that is being processed - if (m_dumpObjectsSubState < poolCount) - { - MemoryPool* pool = TheMemoryPoolFactory->getMemoryPoolN(m_dumpObjectsSubState); - if (pool != NULL) - { - memoryBase = reinterpret_cast(pool); - memorySize = sizeof(MemoryPool); - ++m_dumpObjectsSubState; - } - else + if (TheMemoryPoolFactory) { - m_dumpObjectsSubState = poolCount; + m_rangeIter = TheMemoryPoolFactory->cbegin(); } + break; } - if (m_dumpObjectsSubState == poolCount) - { - m_dumpObjectsSubState = 0; - m_dumpObjectsState = MEMORY_POOL_ALLOCATIONS; - } + memoryBase = reinterpret_cast(m_currentPool); + memorySize = sizeof(MemoryPool); + m_currentPool = m_currentPool->getNextPoolInList(); break; } case MEMORY_POOL_ALLOCATIONS: { // Iterate through all the allocations of memory pools and containing blobs that has been done via the memory pool factory // and include all of the storage space allocated for objects - if (TheMemoryPoolFactory == NULL) + if (m_rangeIter == TheMemoryPoolFactory->cend()) { m_dumpObjectsState = DMA_ALLOCATIONS; + m_currentAllocator = TheDynamicMemoryAllocator; + m_currentSingleBlock = m_currentAllocator->getFirstRawBlock(); break; } - //m_dumpObjectsSubState is used to track if the iterator needs to be initialized, otherwise just a counter of the number of items dumped - if (m_dumpObjectsSubState == 0) - { - m_rangeIter = TheMemoryPoolFactory->cbegin(); - ++m_dumpObjectsSubState; - } - - // m_RangeIter should != cend() at this point before advancing, unless the memory pool factory is corrupted (or has 0 entries) memoryBase = reinterpret_cast(m_rangeIter->allocationAddr); memorySize = m_rangeIter->allocationSize; - ++m_dumpObjectsSubState; ++m_rangeIter; - - if (m_rangeIter == TheMemoryPoolFactory->cend()) - { - m_dumpObjectsState = DMA_ALLOCATIONS; - m_dumpObjectsSubState = 0; - } break; } case DMA_ALLOCATIONS: { // Iterate through all the direct allocations ("raw blocks") done by DMAs, as these are done outside of the // memory pool factory allocations dumped in the previous phase. - if (TheDynamicMemoryAllocator == NULL) + if (m_currentAllocator == NULL) { m_dumpObjectsState = COMPLETED; break; } - if (m_currentAllocator == NULL) + if (m_currentSingleBlock == NULL) { - m_currentAllocator = TheDynamicMemoryAllocator; - // m_dumpObjectsSubState is used to track the index of the raw block in the allocator we are currently traversing - m_dumpObjectsSubState = 0; + // Iterated to a new allocator, start iterating over its blocks + m_currentSingleBlock = m_currentAllocator->getFirstRawBlock(); } - MemoryPoolAllocatedRange rawBlockRange = {0}; - int rawBlocksInDma = m_currentAllocator->getRawBlockCount(); - if (m_dumpObjectsSubState < rawBlocksInDma) - { - // Dump this block - m_currentAllocator->fillAllocationRangeForRawBlockN(m_dumpObjectsSubState, rawBlockRange); - memoryBase = reinterpret_cast(rawBlockRange.allocationAddr); - memorySize = rawBlockRange.allocationSize; - ++m_dumpObjectsSubState; - } + MemoryPoolAllocatedRange rawBlockRange = { 0 }; + m_currentAllocator->fillAllocationRangeForRawBlock(m_currentSingleBlock, rawBlockRange); + memoryBase = reinterpret_cast(rawBlockRange.allocationAddr); + memorySize = rawBlockRange.allocationSize; + m_currentSingleBlock = m_currentAllocator->getNextRawBlock(m_currentSingleBlock); - if (rawBlocksInDma == m_dumpObjectsSubState) + if (m_currentSingleBlock == NULL) { - // Advance to the next DMA m_currentAllocator = m_currentAllocator->getNextDmaInList(); - m_dumpObjectsSubState = 0; - - if (m_currentAllocator == NULL) - { - // Done iterating through all the DMAs - m_dumpObjectsState = COMPLETED; - } } break; } - default: + case COMPLETED: // Done, set "no more regions to dump" values - m_dumpObjectsState = COMPLETED; - m_dumpObjectsSubState = 0; memoryBase = 0; memorySize = 0; break; + default: + DEBUG_CRASH(("Invalid object state")); + break; } } #endif From 7cc9b3cab1cf692e339e0e9d6bdea6e622ec97d1 Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Fri, 28 Nov 2025 20:51:16 +0100 Subject: [PATCH 4/8] Make state enums private, init all meory objs in DumpObjectsState_Begin --- Core/GameEngine/Include/Common/MiniDumper.h | 32 ++-- .../Source/Common/System/MiniDumper.cpp | 145 +++++++++--------- 2 files changed, 87 insertions(+), 90 deletions(-) diff --git a/Core/GameEngine/Include/Common/MiniDumper.h b/Core/GameEngine/Include/Common/MiniDumper.h index 68b8661ef30..0829e68ae31 100644 --- a/Core/GameEngine/Include/Common/MiniDumper.h +++ b/Core/GameEngine/Include/Common/MiniDumper.h @@ -31,25 +31,25 @@ enum DumpType CPP_11(: Char) DUMP_TYPE_FULL = 'F', }; -enum MiniDumperExitCode CPP_11(: Int) +class MiniDumper { - DUMPER_EXIT_SUCCESS = 0x0, - DUMPER_EXIT_FAILURE_WAIT = 0x37DA1040, - DUMPER_EXIT_FAILURE_PARAM = 0x4EA527BB, - DUMPER_EXIT_FORCED_TERMINATE = 0x158B1154, -}; + enum MiniDumperExitCode CPP_11(: Int) + { + DUMPER_EXIT_SUCCESS = 0x0, + DUMPER_EXIT_FAILURE_WAIT = 0x37DA1040, + DUMPER_EXIT_FAILURE_PARAM = 0x4EA527BB, + DUMPER_EXIT_FORCED_TERMINATE = 0x158B1154, + }; -enum DumpObjectsState CPP_11(: Int) -{ - BEGIN, - MEMORY_POOLS, - MEMORY_POOL_ALLOCATIONS, - DMA_ALLOCATIONS, - COMPLETED -}; + enum DumpObjectsState CPP_11(: Int) + { + DumpObjectsState_Begin, + DumpObjectsState_Memory_Pools, + DumpObjectsState_Memory_Pool_Allocations, + DumpObjectsState_DMA_Allocations, + DumpObjectsState_Completed + }; -class MiniDumper -{ public: MiniDumper(); Bool IsInitialized() const; diff --git a/Core/GameEngine/Source/Common/System/MiniDumper.cpp b/Core/GameEngine/Source/Common/System/MiniDumper.cpp index 9baa51984f5..c05ac00c797 100644 --- a/Core/GameEngine/Source/Common/System/MiniDumper.cpp +++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp @@ -70,7 +70,7 @@ MiniDumper::MiniDumper() m_dumpThread = NULL; m_dumpThreadId = 0; #ifndef DISABLE_GAMEMEMORY - m_dumpObjectsState = MEMORY_POOLS; + m_dumpObjectsState = DumpObjectsState_Begin; m_currentAllocator = NULL; m_currentSingleBlock = NULL; m_currentPool = NULL; @@ -373,7 +373,7 @@ void MiniDumper::CreateMiniDump(DumpType dumpType) return; } - m_dumpObjectsState = BEGIN; + m_dumpObjectsState = DumpObjectsState_Begin; PMINIDUMP_EXCEPTION_INFORMATION exceptionInfoPtr = NULL; MINIDUMP_EXCEPTION_INFORMATION exceptionInfo = { 0 }; @@ -492,11 +492,9 @@ BOOL MiniDumper::CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP break; case MemoryCallback: #ifndef DISABLE_GAMEMEMORY - do - { - // DumpMemoryObjects will set outputMemorySize to 0 once it's completed, signalling the end of memory callbacks - DumpMemoryObjects(output.MemoryBase, output.MemorySize); - } while (output.MemorySize == 0 && m_dumpObjectsState != COMPLETED); + // DumpMemoryObjects will set output.MemorySize to 0 once it's completed, + // signalling to end the memory callbacks. + DumpMemoryObjects(output.MemoryBase, output.MemorySize); #else output.MemoryBase = 0; output.MemorySize = 0; @@ -520,90 +518,89 @@ BOOL MiniDumper::CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP #ifndef DISABLE_GAMEMEMORY void MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) { - // m_dumpObjectsState is used to keep track of the current "phase" of the memory dumping process - // m_dumpObjectsSubState is used to keep track of the progress within each phase, and is reset when advancing on to the next phase - switch (m_dumpObjectsState) - { - case BEGIN: - m_dumpObjectsState = MEMORY_POOLS; - if (TheMemoryPoolFactory) - { - m_currentPool = TheMemoryPoolFactory->getFirstMemoryPool(); - } - break; - case MEMORY_POOLS: + memorySize = 0; + do { - // Dump all the MemoryPool instances in TheMemoryPoolFactory - // This only dumps the metadata, not the actual MemoryPool contents (done in the next phase). - if (m_currentPool == NULL) + // m_dumpObjectsState is used to keep track of the current "phase" of the memory dumping process + switch (m_dumpObjectsState) { - m_dumpObjectsState = MEMORY_POOL_ALLOCATIONS; + case DumpObjectsState_Begin: + m_dumpObjectsState = DumpObjectsState_Memory_Pools; if (TheMemoryPoolFactory) { + m_currentPool = TheMemoryPoolFactory->getFirstMemoryPool(); m_rangeIter = TheMemoryPoolFactory->cbegin(); + m_currentAllocator = TheDynamicMemoryAllocator; + if (m_currentAllocator) + m_currentSingleBlock = m_currentAllocator->getFirstRawBlock(); } break; - } - - memoryBase = reinterpret_cast(m_currentPool); - memorySize = sizeof(MemoryPool); - m_currentPool = m_currentPool->getNextPoolInList(); - break; - } - case MEMORY_POOL_ALLOCATIONS: - { - // Iterate through all the allocations of memory pools and containing blobs that has been done via the memory pool factory - // and include all of the storage space allocated for objects - if (m_rangeIter == TheMemoryPoolFactory->cend()) + case DumpObjectsState_Memory_Pools: { - m_dumpObjectsState = DMA_ALLOCATIONS; - m_currentAllocator = TheDynamicMemoryAllocator; - m_currentSingleBlock = m_currentAllocator->getFirstRawBlock(); + // Dump all the MemoryPool instances in TheMemoryPoolFactory + // This only dumps the metadata, not the actual MemoryPool contents (done in the next phase). + if (m_currentPool == NULL) + { + m_dumpObjectsState = DumpObjectsState_Memory_Pool_Allocations; + break; + } + + memoryBase = reinterpret_cast(m_currentPool); + memorySize = sizeof(MemoryPool); + m_currentPool = m_currentPool->getNextPoolInList(); break; } - - memoryBase = reinterpret_cast(m_rangeIter->allocationAddr); - memorySize = m_rangeIter->allocationSize; - ++m_rangeIter; - break; - } - case DMA_ALLOCATIONS: - { - // Iterate through all the direct allocations ("raw blocks") done by DMAs, as these are done outside of the - // memory pool factory allocations dumped in the previous phase. - if (m_currentAllocator == NULL) + case DumpObjectsState_Memory_Pool_Allocations: { - m_dumpObjectsState = COMPLETED; + // Iterate through all the allocations of memory pools and containing blobs that has been done via the memory pool factory + // and include all of the storage space allocated for objects + if (!TheMemoryPoolFactory || m_rangeIter == TheMemoryPoolFactory->cend()) + { + m_dumpObjectsState = DumpObjectsState_DMA_Allocations; + break; + } + + memoryBase = reinterpret_cast(m_rangeIter->allocationAddr); + memorySize = m_rangeIter->allocationSize; + ++m_rangeIter; break; } - - if (m_currentSingleBlock == NULL) + case DumpObjectsState_DMA_Allocations: { - // Iterated to a new allocator, start iterating over its blocks - m_currentSingleBlock = m_currentAllocator->getFirstRawBlock(); - } + // Iterate through all the direct allocations ("raw blocks") done by DMAs, as these are done outside of the + // memory pool factory allocations dumped in the previous phase. + if (m_currentAllocator == NULL || m_currentSingleBlock == NULL) + { + m_dumpObjectsState = DumpObjectsState_Completed; + break; + } - MemoryPoolAllocatedRange rawBlockRange = { 0 }; - m_currentAllocator->fillAllocationRangeForRawBlock(m_currentSingleBlock, rawBlockRange); - memoryBase = reinterpret_cast(rawBlockRange.allocationAddr); - memorySize = rawBlockRange.allocationSize; - m_currentSingleBlock = m_currentAllocator->getNextRawBlock(m_currentSingleBlock); + MemoryPoolAllocatedRange rawBlockRange = { 0 }; + m_currentAllocator->fillAllocationRangeForRawBlock(m_currentSingleBlock, rawBlockRange); + memoryBase = reinterpret_cast(rawBlockRange.allocationAddr); + memorySize = rawBlockRange.allocationSize; + m_currentSingleBlock = m_currentAllocator->getNextRawBlock(m_currentSingleBlock); - if (m_currentSingleBlock == NULL) - { - m_currentAllocator = m_currentAllocator->getNextDmaInList(); + if (m_currentSingleBlock == NULL) + { + m_currentAllocator = m_currentAllocator->getNextDmaInList(); + if (m_currentAllocator) + m_currentSingleBlock = m_currentAllocator->getFirstRawBlock(); + } + break; } - break; - } - case COMPLETED: - // Done, set "no more regions to dump" values - memoryBase = 0; - memorySize = 0; - break; - default: - DEBUG_CRASH(("Invalid object state")); - break; - } + case DumpObjectsState_Completed: + // Done, set "no more regions to dump" values + memoryBase = 0; + memorySize = 0; + return; + default: + DEBUG_CRASH(("Invalid object state")); + break; + } + // If memorySize is 0 we transitioned from one phase to the next. + // Go again so the memory block info gets populated in the new phase. + } while (memorySize == 0); } #endif From bcb86fe8d8e24318307e694d309c6a59fe29dce8 Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Mon, 1 Dec 2025 18:20:48 +0100 Subject: [PATCH 5/8] Include enum name in enum values, move AllocationRangeIterator implementations to cpp --- Core/GameEngine/Include/Common/GameMemory.h | 50 +++---------- Core/GameEngine/Include/Common/MiniDumper.h | 22 +++--- .../GameEngine/Source/Common/System/Debug.cpp | 4 +- .../Source/Common/System/GameMemory.cpp | 74 ++++++++++++++++++- .../Source/Common/System/MiniDumper.cpp | 40 +++++----- .../Source/WWVegas/WWLib/DbgHelpLoader.h | 10 --- Generals/Code/Main/WinMain.cpp | 4 +- GeneralsMD/Code/Main/WinMain.cpp | 4 +- cmake/config-memory.cmake | 2 +- 9 files changed, 120 insertions(+), 90 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameMemory.h b/Core/GameEngine/Include/Common/GameMemory.h index 4f3d23b6127..59acd74c5b6 100644 --- a/Core/GameEngine/Include/Common/GameMemory.h +++ b/Core/GameEngine/Include/Common/GameMemory.h @@ -509,57 +509,31 @@ class AllocationRangeIterator public: + AllocationRangeIterator(); AllocationRangeIterator(const MemoryPoolFactory* factory); - AllocationRangeIterator(MemoryPool& pool, MemoryPoolBlob& blob) - { - m_currentPool = &pool; - m_currentBlobInPool = &blob; - m_factory = NULL; - m_range = MemoryPoolAllocatedRange(); - }; - - AllocationRangeIterator(MemoryPool* pool, MemoryPoolBlob* blob) - { - m_currentPool = pool; - m_currentBlobInPool = blob; - m_factory = NULL; - m_range = MemoryPoolAllocatedRange(); - }; - - AllocationRangeIterator() - { - m_currentPool = NULL; - m_currentBlobInPool = NULL; - m_factory = NULL; - m_range = MemoryPoolAllocatedRange(); - }; + AllocationRangeIterator(MemoryPool& pool, MemoryPoolBlob& blob); + AllocationRangeIterator(MemoryPool* pool, MemoryPoolBlob* blob); - reference operator*() { UpdateRange(); return m_range; } - pointer operator->() { UpdateRange(); return &m_range; } + reference operator*() { return m_range; } + pointer operator->() { return &m_range; } // Prefix increment - AllocationRangeIterator& operator++() { MoveToNextBlob(); return *this; } + AllocationRangeIterator& operator++(); // Postfix increment - AllocationRangeIterator operator++(int) { AllocationRangeIterator tmp = *this; ++(*this); return tmp; } + AllocationRangeIterator operator++(int); - friend const bool operator== (const AllocationRangeIterator& a, const AllocationRangeIterator& b) - { - return a.m_currentBlobInPool == b.m_currentBlobInPool; - }; - - friend const bool operator!= (const AllocationRangeIterator& a, const AllocationRangeIterator& b) - { - return a.m_currentBlobInPool != b.m_currentBlobInPool; - }; + friend const bool operator== (const AllocationRangeIterator& a, const AllocationRangeIterator& b); + friend const bool operator!= (const AllocationRangeIterator& a, const AllocationRangeIterator& b); private: + + void updateRange(); + void moveToNextBlob(); const MemoryPoolFactory* m_factory; MemoryPool* m_currentPool; MemoryPoolBlob* m_currentBlobInPool; MemoryPoolAllocatedRange m_range; - void UpdateRange(); - void MoveToNextBlob(); }; #endif diff --git a/Core/GameEngine/Include/Common/MiniDumper.h b/Core/GameEngine/Include/Common/MiniDumper.h index 0829e68ae31..890b5647d1c 100644 --- a/Core/GameEngine/Include/Common/MiniDumper.h +++ b/Core/GameEngine/Include/Common/MiniDumper.h @@ -24,29 +24,29 @@ enum DumpType CPP_11(: Char) { // Smallest dump type with call stacks and some supporting variables - DUMP_TYPE_MINIMAL = 'M', - // Large dump including all memory regions allocated by the GameMemory implementaion - DUMP_TYPE_GAMEMEMORY = 'X', + DumpType_Minimal = 'M', + // Large dump including all memory regions allocated by the GameMemory implementation + DumpType_Gamememory = 'X', // Largest dump size including complete memory contents of the process - DUMP_TYPE_FULL = 'F', + DumpType_Full = 'F', }; class MiniDumper { enum MiniDumperExitCode CPP_11(: Int) { - DUMPER_EXIT_SUCCESS = 0x0, - DUMPER_EXIT_FAILURE_WAIT = 0x37DA1040, - DUMPER_EXIT_FAILURE_PARAM = 0x4EA527BB, - DUMPER_EXIT_FORCED_TERMINATE = 0x158B1154, + MiniDumperExitCode_Success = 0x0, + MiniDumperExitCode_FailureWait = 0x37DA1040, + MiniDumperExitCode_FailureParam = 0x4EA527BB, + MiniDumperExitCode_ForcedTerminate = 0x158B1154, }; enum DumpObjectsState CPP_11(: Int) { DumpObjectsState_Begin, - DumpObjectsState_Memory_Pools, - DumpObjectsState_Memory_Pool_Allocations, - DumpObjectsState_DMA_Allocations, + DumpObjectsState_MemoryPools, + DumpObjectsState_MemoryPoolAllocations, + DumpObjectsState_DMAAllocations, DumpObjectsState_Completed }; diff --git a/Core/GameEngine/Source/Common/System/Debug.cpp b/Core/GameEngine/Source/Common/System/Debug.cpp index d06e76a323c..fe20ccffe2d 100644 --- a/Core/GameEngine/Source/Common/System/Debug.cpp +++ b/Core/GameEngine/Source/Common/System/Debug.cpp @@ -737,8 +737,8 @@ static void TriggerMiniDump() if (TheMiniDumper && TheMiniDumper->IsInitialized()) { // Do dumps both with and without extended info - TheMiniDumper->TriggerMiniDump(DUMP_TYPE_MINIMAL); - TheMiniDumper->TriggerMiniDump(DUMP_TYPE_GAMEMEMORY); + TheMiniDumper->TriggerMiniDump(DumpType_Minimal); + TheMiniDumper->TriggerMiniDump(DumpType_Gamememory); } MiniDumper::shutdownMiniDumper(); diff --git a/Core/GameEngine/Source/Common/System/GameMemory.cpp b/Core/GameEngine/Source/Common/System/GameMemory.cpp index a5524e534b8..cf3a0c7281c 100644 --- a/Core/GameEngine/Source/Common/System/GameMemory.cpp +++ b/Core/GameEngine/Source/Common/System/GameMemory.cpp @@ -2606,6 +2606,7 @@ void DynamicMemoryAllocator::fillAllocationRangeForRawBlock(const MemoryPoolSing allocationRange.allocationAddr = reinterpret_cast(block); allocationRange.allocationSize = block->calcRawBlockSize(block->debugGetLogicalSize()); } + #endif //----------------------------------------------------------------------------- @@ -3273,21 +3274,70 @@ MemoryPool* MemoryPoolFactory::getFirstMemoryPool() const return m_firstPoolInFactory; } +//----------------------------------------------------------------------------- +// METHODS for AllocationRangeIterator +//----------------------------------------------------------------------------- + +AllocationRangeIterator::AllocationRangeIterator() +{ + m_currentPool = NULL; + m_currentBlobInPool = NULL; + m_factory = NULL; + m_range = MemoryPoolAllocatedRange(); +} + AllocationRangeIterator::AllocationRangeIterator(const MemoryPoolFactory* factory) { m_factory = factory; - m_currentPool = factory->m_firstPoolInFactory; - m_currentBlobInPool = m_currentPool->m_firstBlob; + if (factory) + { + m_currentPool = factory->m_firstPoolInFactory; + m_currentBlobInPool = m_currentPool ? m_currentPool->m_firstBlob : NULL; + } + else + { + m_currentPool = NULL; + m_currentBlobInPool = NULL; + } + m_range = MemoryPoolAllocatedRange(); } -void AllocationRangeIterator::UpdateRange() +AllocationRangeIterator::AllocationRangeIterator(MemoryPool& pool, MemoryPoolBlob& blob) { + m_currentPool = &pool; + m_currentBlobInPool = &blob; + m_factory = NULL; + m_range = MemoryPoolAllocatedRange(); +} + +AllocationRangeIterator::AllocationRangeIterator(MemoryPool* pool, MemoryPoolBlob* blob) +{ + m_currentPool = pool; + m_currentBlobInPool = blob; + m_factory = NULL; + m_range = MemoryPoolAllocatedRange(); +} + +void AllocationRangeIterator::updateRange() +{ + if (m_currentBlobInPool == NULL) + { + m_range.allocationAddr = nullptr; + m_range.allocationSize = 0; + return; + } + m_currentBlobInPool->fillAllocatedRange(m_range); } -void AllocationRangeIterator::MoveToNextBlob() +void AllocationRangeIterator::moveToNextBlob() { + if (m_currentBlobInPool == NULL) + { + return; + } + // Advances to the next blob, advancing to the next MemoryPool if needed. m_currentBlobInPool = m_currentBlobInPool->getNextInList(); if (m_currentBlobInPool != NULL) @@ -3308,6 +3358,22 @@ void AllocationRangeIterator::MoveToNextBlob() m_currentBlobInPool = NULL; } } + +// Prefix increment +AllocationRangeIterator& AllocationRangeIterator::operator++() { moveToNextBlob(); updateRange(); return *this; } + +// Postfix increment +AllocationRangeIterator AllocationRangeIterator::operator++(int) { AllocationRangeIterator tmp = *this; ++(*this); return tmp; } + +const bool operator== (const AllocationRangeIterator& a, const AllocationRangeIterator& b) +{ + return a.m_currentBlobInPool == b.m_currentBlobInPool; +} + +const bool operator!= (const AllocationRangeIterator& a, const AllocationRangeIterator& b) +{ + return a.m_currentBlobInPool != b.m_currentBlobInPool; +} #endif //----------------------------------------------------------------------------- diff --git a/Core/GameEngine/Source/Common/System/MiniDumper.cpp b/Core/GameEngine/Source/Common/System/MiniDumper.cpp index c05ac00c797..bbd45e0eece 100644 --- a/Core/GameEngine/Source/Common/System/MiniDumper.cpp +++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp @@ -63,7 +63,7 @@ MiniDumper::MiniDumper() { m_miniDumpInitialized = false; m_loadedDbgHelp = false; - m_requestedDumpType = DUMP_TYPE_MINIMAL; + m_requestedDumpType = DumpType_Minimal; m_dumpRequested = NULL; m_dumpComplete = NULL; m_quitting = NULL; @@ -234,9 +234,9 @@ Bool MiniDumper::InitializeDumpDirectory(const AsciiString& userDirPath) } // Clean up old files (we keep a maximum of 10 small, 2 extended and 2 full) - KeepNewestFiles(m_dumpDir, DUMP_TYPE_GAMEMEMORY, MaxExtendedFileCount); - KeepNewestFiles(m_dumpDir, DUMP_TYPE_FULL, MaxFullFileCount); - KeepNewestFiles(m_dumpDir, DUMP_TYPE_MINIMAL, MaxMiniFileCount); + KeepNewestFiles(m_dumpDir, DumpType_Gamememory, MaxExtendedFileCount); + KeepNewestFiles(m_dumpDir, DumpType_Full, MaxFullFileCount); + KeepNewestFiles(m_dumpDir, DumpType_Minimal, MaxMiniFileCount); return true; } @@ -256,7 +256,7 @@ void MiniDumper::ShutdownDumpThread() break; case WAIT_TIMEOUT: DEBUG_LOG(("MiniDumper::ShutdownDumpThread: Waiting for dumping thread to exit timed out, killing thread", waitRet)); - ::TerminateThread(m_dumpThread, DUMPER_EXIT_FORCED_TERMINATE); + ::TerminateThread(m_dumpThread, MiniDumperExitCode_ForcedTerminate); break; case WAIT_FAILED: DEBUG_LOG(("MiniDumper::ShutdownDumpThread: Waiting for minidump triggering failed: status=%u, error=%u", waitRet, ::GetLastError())); @@ -323,13 +323,13 @@ DWORD MiniDumper::ThreadProcInternal() break; case WAIT_OBJECT_0 + 1: // Quit (m_quitting) - return DUMPER_EXIT_SUCCESS; + return MiniDumperExitCode_Success; case WAIT_FAILED: DEBUG_LOG(("MiniDumper::ThreadProcInternal: Waiting for events failed: status=%u, error=%u", event, ::GetLastError())); - return DUMPER_EXIT_FAILURE_WAIT; + return MiniDumperExitCode_FailureWait; default: DEBUG_LOG(("MiniDumper::ThreadProcInternal: Waiting for events failed: status=%u", event)); - return DUMPER_EXIT_FAILURE_WAIT; + return MiniDumperExitCode_FailureWait; } } } @@ -339,7 +339,7 @@ DWORD WINAPI MiniDumper::MiniDumpThreadProc(LPVOID lpParam) if (lpParam == NULL) { DEBUG_LOG(("MiniDumper::MiniDumpThreadProc: The provided parameter was NULL, exiting thread.")); - return DUMPER_EXIT_FAILURE_PARAM; + return MiniDumperExitCode_FailureParam; } MiniDumper* dumper = static_cast(lpParam); @@ -387,7 +387,7 @@ void MiniDumper::CreateMiniDump(DumpType dumpType) PMINIDUMP_CALLBACK_INFORMATION callbackInfoPtr = NULL; MINIDUMP_CALLBACK_INFORMATION callBackInfo = { 0 }; - if (dumpType == DUMP_TYPE_GAMEMEMORY) + if (dumpType == DumpType_Gamememory) { callBackInfo.CallbackRoutine = MiniDumpCallback; callBackInfo.CallbackParam = this; @@ -397,13 +397,13 @@ void MiniDumper::CreateMiniDump(DumpType dumpType) int dumpTypeFlags = MiniDumpNormal; switch (dumpType) { - case DUMP_TYPE_FULL: + case DumpType_Full: dumpTypeFlags |= MiniDumpWithFullMemory; FALLTHROUGH; - case DUMP_TYPE_GAMEMEMORY: + case DumpType_Gamememory: dumpTypeFlags |= MiniDumpWithDataSegs | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithFullMemoryInfo | MiniDumpWithPrivateReadWriteMemory; FALLTHROUGH; - case DUMP_TYPE_MINIMAL: + case DumpType_Minimal: dumpTypeFlags |= MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory; break; } @@ -525,7 +525,7 @@ void MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) switch (m_dumpObjectsState) { case DumpObjectsState_Begin: - m_dumpObjectsState = DumpObjectsState_Memory_Pools; + m_dumpObjectsState = DumpObjectsState_MemoryPools; if (TheMemoryPoolFactory) { m_currentPool = TheMemoryPoolFactory->getFirstMemoryPool(); @@ -535,13 +535,13 @@ void MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) m_currentSingleBlock = m_currentAllocator->getFirstRawBlock(); } break; - case DumpObjectsState_Memory_Pools: + case DumpObjectsState_MemoryPools: { // Dump all the MemoryPool instances in TheMemoryPoolFactory // This only dumps the metadata, not the actual MemoryPool contents (done in the next phase). if (m_currentPool == NULL) { - m_dumpObjectsState = DumpObjectsState_Memory_Pool_Allocations; + m_dumpObjectsState = DumpObjectsState_MemoryPoolAllocations; break; } @@ -550,13 +550,13 @@ void MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) m_currentPool = m_currentPool->getNextPoolInList(); break; } - case DumpObjectsState_Memory_Pool_Allocations: + case DumpObjectsState_MemoryPoolAllocations: { // Iterate through all the allocations of memory pools and containing blobs that has been done via the memory pool factory // and include all of the storage space allocated for objects if (!TheMemoryPoolFactory || m_rangeIter == TheMemoryPoolFactory->cend()) { - m_dumpObjectsState = DumpObjectsState_DMA_Allocations; + m_dumpObjectsState = DumpObjectsState_DMAAllocations; break; } @@ -565,7 +565,7 @@ void MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) ++m_rangeIter; break; } - case DumpObjectsState_DMA_Allocations: + case DumpObjectsState_DMAAllocations: { // Iterate through all the direct allocations ("raw blocks") done by DMAs, as these are done outside of the // memory pool factory allocations dumped in the previous phase. @@ -575,7 +575,7 @@ void MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) break; } - MemoryPoolAllocatedRange rawBlockRange = { 0 }; + MemoryPoolAllocatedRange rawBlockRange; m_currentAllocator->fillAllocationRangeForRawBlock(m_currentSingleBlock, rawBlockRange); memoryBase = reinterpret_cast(rawBlockRange.allocationAddr); memorySize = rawBlockRange.allocationSize; diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h index 9bc0dce4194..3dda58f2068 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h +++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h @@ -126,16 +126,6 @@ class DbgHelpLoader private: static void freeResources(); -#ifdef RTS_ENABLE_CRASHDUMP - typedef BOOL(WINAPI* MiniDumpWriteDump_t)( - HANDLE hProcess, - DWORD ProcessId, - HANDLE hFile, - MINIDUMP_TYPE DumpType, - PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, - PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, - PMINIDUMP_CALLBACK_INFORMATION CallbackParam); -#endif typedef BOOL (WINAPI *SymInitialize_t) ( HANDLE hProcess, diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index 47e25f17494..12b0af5df8d 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -748,8 +748,8 @@ static LONG WINAPI UnHandledExceptionFilter( struct _EXCEPTION_POINTERS* e_info if (TheMiniDumper && TheMiniDumper->IsInitialized()) { // Do dumps both with and without extended info - TheMiniDumper->TriggerMiniDumpForException(e_info, DUMP_TYPE_MINIMAL); - TheMiniDumper->TriggerMiniDumpForException(e_info, DUMP_TYPE_GAMEMEMORY); + TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Minimal); + TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Gamememory); } MiniDumper::shutdownMiniDumper(); diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 4c5cd64f703..fb494dc438c 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -770,8 +770,8 @@ static LONG WINAPI UnHandledExceptionFilter( struct _EXCEPTION_POINTERS* e_info if (TheMiniDumper && TheMiniDumper->IsInitialized()) { // Do dumps both with and without extended info - TheMiniDumper->TriggerMiniDumpForException(e_info, DUMP_TYPE_MINIMAL); - TheMiniDumper->TriggerMiniDumpForException(e_info, DUMP_TYPE_GAMEMEMORY); + TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Minimal); + TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Gamememory); } MiniDumper::shutdownMiniDumper(); diff --git a/cmake/config-memory.cmake b/cmake/config-memory.cmake index a9f34038983..5daa6f110a4 100644 --- a/cmake/config-memory.cmake +++ b/cmake/config-memory.cmake @@ -41,7 +41,7 @@ add_feature_info(MemoryPoolDebugCheckBlockOwnership RTS_MEMORYPOOL_DEBUG_CHECK_B add_feature_info(MemoryPoolDebugIntenseDmaBookkeeping RTS_MEMORYPOOL_DEBUG_INTENSE_DMA_BOOKKEEPING "Build with Memory Pool intense DMA bookkeeping") # Memory dump features -add_feature_info(CrashDumpEnable "RTS_CRASHDUMP_ENABLE" "Build with Crash Dumps") +add_feature_info(CrashDumpEnable RTS_CRASHDUMP_ENABLE "Build with Crash Dumps") # Game Memory features if(NOT RTS_GAMEMEMORY_ENABLE) From c0f104564f1ca1f4ddd60629f5f5269808fc4e06 Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Tue, 2 Dec 2025 22:46:13 +0100 Subject: [PATCH 6/8] Simplify allocator and raw block navigation --- Core/GameEngine/Include/Common/GameMemory.h | 5 +-- Core/GameEngine/Include/Common/MiniDumper.h | 6 ++- .../Source/Common/System/GameMemory.cpp | 22 ++++++---- .../Source/Common/System/MiniDumper.cpp | 40 ++++++++++++++----- 4 files changed, 50 insertions(+), 23 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameMemory.h b/Core/GameEngine/Include/Common/GameMemory.h index 59acd74c5b6..9836b6c9463 100644 --- a/Core/GameEngine/Include/Common/GameMemory.h +++ b/Core/GameEngine/Include/Common/GameMemory.h @@ -490,7 +490,7 @@ class DynamicMemoryAllocator #endif // MEMORYPOOL_DEBUG #ifdef RTS_ENABLE_CRASHDUMP MemoryPoolSingleBlock* getFirstRawBlock() const; - MemoryPoolSingleBlock* getNextRawBlock(MemoryPoolSingleBlock* block) const; + MemoryPoolSingleBlock* getNextRawBlock(const MemoryPoolSingleBlock* block) const; void fillAllocationRangeForRawBlock(const MemoryPoolSingleBlock*, MemoryPoolAllocatedRange& allocationRange) const; #endif }; @@ -517,10 +517,7 @@ class AllocationRangeIterator reference operator*() { return m_range; } pointer operator->() { return &m_range; } - // Prefix increment AllocationRangeIterator& operator++(); - - // Postfix increment AllocationRangeIterator operator++(int); friend const bool operator== (const AllocationRangeIterator& a, const AllocationRangeIterator& b); diff --git a/Core/GameEngine/Include/Common/MiniDumper.h b/Core/GameEngine/Include/Common/MiniDumper.h index 890b5647d1c..057a7031c25 100644 --- a/Core/GameEngine/Include/Common/MiniDumper.h +++ b/Core/GameEngine/Include/Common/MiniDumper.h @@ -63,11 +63,15 @@ class MiniDumper void Initialize(const AsciiString& userDirPath); void ShutDown(); void CreateMiniDump(DumpType dumpType); - void DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize); void CleanupResources(); Bool IsDumpThreadStillRunning() const; void ShutdownDumpThread(); Bool ShouldWriteDataSegsForModule(const PWCHAR module) const; +#ifndef DISABLE_GAMEMEMORY + void MoveToNextAllocatorWithRawBlocks(); + void MoveToNextSingleBlock(); + void DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize); +#endif // Callbacks from dbghelp static BOOL CALLBACK MiniDumpCallback(PVOID CallbackParam, PMINIDUMP_CALLBACK_INPUT CallbackInput, PMINIDUMP_CALLBACK_OUTPUT CallbackOutput); diff --git a/Core/GameEngine/Source/Common/System/GameMemory.cpp b/Core/GameEngine/Source/Common/System/GameMemory.cpp index cf3a0c7281c..cb062ceacf3 100644 --- a/Core/GameEngine/Source/Common/System/GameMemory.cpp +++ b/Core/GameEngine/Source/Common/System/GameMemory.cpp @@ -441,7 +441,7 @@ class MemoryPoolSingleBlock MemoryPoolSingleBlock *getNextFreeBlock(); void setNextFreeBlock(MemoryPoolSingleBlock *b); - MemoryPoolSingleBlock *getNextRawBlock(); + MemoryPoolSingleBlock *getNextRawBlock() const; void setNextRawBlock(MemoryPoolSingleBlock *b); #if defined(MEMORYPOOL_DEBUG) || defined(RTS_ENABLE_CRASHDUMP) @@ -611,7 +611,7 @@ inline void MemoryPoolSingleBlock::setNextFreeBlock(MemoryPoolSingleBlock *b) return the next raw block in this dma. this call assumes that the block in question does NOT belong to a blob, and will assert if not. */ -inline MemoryPoolSingleBlock *MemoryPoolSingleBlock::getNextRawBlock() +inline MemoryPoolSingleBlock *MemoryPoolSingleBlock::getNextRawBlock() const { DEBUG_ASSERTCRASH(m_owningBlob == NULL, ("must be called on raw block")); return m_nextBlock; @@ -2596,7 +2596,7 @@ MemoryPoolSingleBlock* DynamicMemoryAllocator::getFirstRawBlock() const return m_rawBlocks; } -MemoryPoolSingleBlock* DynamicMemoryAllocator::getNextRawBlock(MemoryPoolSingleBlock* block) const +MemoryPoolSingleBlock* DynamicMemoryAllocator::getNextRawBlock(const MemoryPoolSingleBlock* block) const { return block->getNextRawBlock(); } @@ -3359,11 +3359,19 @@ void AllocationRangeIterator::moveToNextBlob() } } -// Prefix increment -AllocationRangeIterator& AllocationRangeIterator::operator++() { moveToNextBlob(); updateRange(); return *this; } +AllocationRangeIterator& AllocationRangeIterator::operator++() +{ + moveToNextBlob(); + updateRange(); + return *this; +} -// Postfix increment -AllocationRangeIterator AllocationRangeIterator::operator++(int) { AllocationRangeIterator tmp = *this; ++(*this); return tmp; } +AllocationRangeIterator AllocationRangeIterator::operator++(int) +{ + AllocationRangeIterator tmp = *this; + ++(*this); + return tmp; +} const bool operator== (const AllocationRangeIterator& a, const AllocationRangeIterator& b) { diff --git a/Core/GameEngine/Source/Common/System/MiniDumper.cpp b/Core/GameEngine/Source/Common/System/MiniDumper.cpp index bbd45e0eece..22baf488dfa 100644 --- a/Core/GameEngine/Source/Common/System/MiniDumper.cpp +++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp @@ -516,6 +516,32 @@ BOOL MiniDumper::CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP } #ifndef DISABLE_GAMEMEMORY +void MiniDumper::MoveToNextAllocatorWithRawBlocks() +{ + // Skip over DMAs that have no raw blocks + while (m_currentAllocator && !m_currentAllocator->getFirstRawBlock()) + { + m_currentAllocator = m_currentAllocator->getNextDmaInList(); + } + + if (m_currentAllocator) + { + m_currentSingleBlock = m_currentAllocator->getFirstRawBlock(); + } +} + +void MiniDumper::MoveToNextSingleBlock() +{ + DEBUG_ASSERTCRASH(m_currentAllocator != NULL, ("MiniDumper::MoveToNextSingleBlock: m_currentAllocator is NULL")); + m_currentSingleBlock = m_currentAllocator->getNextRawBlock(m_currentSingleBlock); + + if (m_currentSingleBlock == NULL) + { + m_currentAllocator = m_currentAllocator->getNextDmaInList(); + MoveToNextAllocatorWithRawBlocks(); + } +} + void MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) { memorySize = 0; @@ -531,8 +557,7 @@ void MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) m_currentPool = TheMemoryPoolFactory->getFirstMemoryPool(); m_rangeIter = TheMemoryPoolFactory->cbegin(); m_currentAllocator = TheDynamicMemoryAllocator; - if (m_currentAllocator) - m_currentSingleBlock = m_currentAllocator->getFirstRawBlock(); + MoveToNextAllocatorWithRawBlocks(); } break; case DumpObjectsState_MemoryPools: @@ -569,7 +594,7 @@ void MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) { // Iterate through all the direct allocations ("raw blocks") done by DMAs, as these are done outside of the // memory pool factory allocations dumped in the previous phase. - if (m_currentAllocator == NULL || m_currentSingleBlock == NULL) + if (m_currentAllocator == NULL) { m_dumpObjectsState = DumpObjectsState_Completed; break; @@ -579,14 +604,7 @@ void MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) m_currentAllocator->fillAllocationRangeForRawBlock(m_currentSingleBlock, rawBlockRange); memoryBase = reinterpret_cast(rawBlockRange.allocationAddr); memorySize = rawBlockRange.allocationSize; - m_currentSingleBlock = m_currentAllocator->getNextRawBlock(m_currentSingleBlock); - - if (m_currentSingleBlock == NULL) - { - m_currentAllocator = m_currentAllocator->getNextDmaInList(); - if (m_currentAllocator) - m_currentSingleBlock = m_currentAllocator->getFirstRawBlock(); - } + MoveToNextSingleBlock(); break; } case DumpObjectsState_Completed: From 810371ebe530aa225b069223d135a43b9c7f5347 Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Sat, 6 Dec 2025 13:13:00 +0100 Subject: [PATCH 7/8] Remove integration with GameMemory, offer Small and Full dump types --- Core/GameEngine/Include/Common/GameMemory.h | 67 ----- Core/GameEngine/Include/Common/MiniDumper.h | 31 --- .../GameEngine/Source/Common/System/Debug.cpp | 4 +- .../Source/Common/System/GameMemory.cpp | 164 +------------ .../Source/Common/System/MiniDumper.cpp | 231 +----------------- Generals/Code/Main/WinMain.cpp | 4 +- GeneralsMD/Code/Main/WinMain.cpp | 4 +- 7 files changed, 17 insertions(+), 488 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameMemory.h b/Core/GameEngine/Include/Common/GameMemory.h index 9836b6c9463..8d173afec41 100644 --- a/Core/GameEngine/Include/Common/GameMemory.h +++ b/Core/GameEngine/Include/Common/GameMemory.h @@ -223,9 +223,6 @@ class MemoryPool; class MemoryPoolFactory; class DynamicMemoryAllocator; class BlockCheckpointInfo; -#ifdef RTS_ENABLE_CRASHDUMP -class AllocationRangeIterator; -#endif // TYPE DEFINES /////////////////////////////////////////////////////////////// @@ -282,14 +279,6 @@ class Checkpointable }; #endif -#ifdef RTS_ENABLE_CRASHDUMP -struct MemoryPoolAllocatedRange -{ - const char* allocationAddr; - size_t allocationSize; -}; -#endif - // ---------------------------------------------------------------------------- /** A MemoryPool provides a way to efficiently allocate objects of the same (or similar) @@ -395,9 +384,6 @@ class MemoryPool /// return true iff this block was allocated by this pool. Bool debugIsBlockInPool(void *pBlock); #endif -#ifdef RTS_ENABLE_CRASHDUMP - friend class AllocationRangeIterator; -#endif }; // ---------------------------------------------------------------------------- @@ -488,11 +474,6 @@ class DynamicMemoryAllocator Bool debugIsPoolInDma(MemoryPool *pool); #endif // MEMORYPOOL_DEBUG -#ifdef RTS_ENABLE_CRASHDUMP - MemoryPoolSingleBlock* getFirstRawBlock() const; - MemoryPoolSingleBlock* getNextRawBlock(const MemoryPoolSingleBlock* block) const; - void fillAllocationRangeForRawBlock(const MemoryPoolSingleBlock*, MemoryPoolAllocatedRange& allocationRange) const; -#endif }; // ---------------------------------------------------------------------------- @@ -500,40 +481,6 @@ class DynamicMemoryAllocator enum { MAX_SPECIAL_USED = 256 }; #endif -#ifdef RTS_ENABLE_CRASHDUMP -class AllocationRangeIterator -{ - typedef const MemoryPoolAllocatedRange value_type; - typedef const MemoryPoolAllocatedRange* pointer; - typedef const MemoryPoolAllocatedRange& reference; - -public: - - AllocationRangeIterator(); - AllocationRangeIterator(const MemoryPoolFactory* factory); - AllocationRangeIterator(MemoryPool& pool, MemoryPoolBlob& blob); - AllocationRangeIterator(MemoryPool* pool, MemoryPoolBlob* blob); - - reference operator*() { return m_range; } - pointer operator->() { return &m_range; } - - AllocationRangeIterator& operator++(); - AllocationRangeIterator operator++(int); - - friend const bool operator== (const AllocationRangeIterator& a, const AllocationRangeIterator& b); - friend const bool operator!= (const AllocationRangeIterator& a, const AllocationRangeIterator& b); - -private: - - void updateRange(); - void moveToNextBlob(); - const MemoryPoolFactory* m_factory; - MemoryPool* m_currentPool; - MemoryPoolBlob* m_currentBlobInPool; - MemoryPoolAllocatedRange m_range; -}; -#endif - // ---------------------------------------------------------------------------- /** The class that manages all the MemoryPools and DynamicMemoryAllocators. @@ -626,20 +573,6 @@ class MemoryPoolFactory void debugResetCheckpoints(); #endif -#ifdef RTS_ENABLE_CRASHDUMP - AllocationRangeIterator cbegin() const - { - return AllocationRangeIterator(this); - } - - AllocationRangeIterator cend() const - { - return AllocationRangeIterator(NULL, NULL); - } - - MemoryPool* getFirstMemoryPool() const; - friend class AllocationRangeIterator; -#endif }; // how many bytes are we allowed to 'waste' per pool allocation before the debug code starts yelling at us... diff --git a/Core/GameEngine/Include/Common/MiniDumper.h b/Core/GameEngine/Include/Common/MiniDumper.h index 057a7031c25..44c03e96ef1 100644 --- a/Core/GameEngine/Include/Common/MiniDumper.h +++ b/Core/GameEngine/Include/Common/MiniDumper.h @@ -25,8 +25,6 @@ enum DumpType CPP_11(: Char) { // Smallest dump type with call stacks and some supporting variables DumpType_Minimal = 'M', - // Large dump including all memory regions allocated by the GameMemory implementation - DumpType_Gamememory = 'X', // Largest dump size including complete memory contents of the process DumpType_Full = 'F', }; @@ -41,15 +39,6 @@ class MiniDumper MiniDumperExitCode_ForcedTerminate = 0x158B1154, }; - enum DumpObjectsState CPP_11(: Int) - { - DumpObjectsState_Begin, - DumpObjectsState_MemoryPools, - DumpObjectsState_MemoryPoolAllocations, - DumpObjectsState_DMAAllocations, - DumpObjectsState_Completed - }; - public: MiniDumper(); Bool IsInitialized() const; @@ -66,16 +55,6 @@ class MiniDumper void CleanupResources(); Bool IsDumpThreadStillRunning() const; void ShutdownDumpThread(); - Bool ShouldWriteDataSegsForModule(const PWCHAR module) const; -#ifndef DISABLE_GAMEMEMORY - void MoveToNextAllocatorWithRawBlocks(); - void MoveToNextSingleBlock(); - void DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize); -#endif - - // Callbacks from dbghelp - static BOOL CALLBACK MiniDumpCallback(PVOID CallbackParam, PMINIDUMP_CALLBACK_INPUT CallbackInput, PMINIDUMP_CALLBACK_OUTPUT CallbackOutput); - BOOL CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP_CALLBACK_OUTPUT& output); // Thread procs static DWORD WINAPI MiniDumpThreadProc(LPVOID lpParam); @@ -112,16 +91,6 @@ class MiniDumper // Thread handles HANDLE m_dumpThread; DWORD m_dumpThreadId; - -#ifndef DISABLE_GAMEMEMORY - // Internal memory dumping progress state - DumpObjectsState m_dumpObjectsState; - DynamicMemoryAllocator* m_currentAllocator; - MemoryPool* m_currentPool; - MemoryPoolSingleBlock* m_currentSingleBlock; - - AllocationRangeIterator m_rangeIter; -#endif }; extern MiniDumper* TheMiniDumper; diff --git a/Core/GameEngine/Source/Common/System/Debug.cpp b/Core/GameEngine/Source/Common/System/Debug.cpp index fe20ccffe2d..33ddbff4606 100644 --- a/Core/GameEngine/Source/Common/System/Debug.cpp +++ b/Core/GameEngine/Source/Common/System/Debug.cpp @@ -736,9 +736,9 @@ static void TriggerMiniDump() #ifdef RTS_ENABLE_CRASHDUMP if (TheMiniDumper && TheMiniDumper->IsInitialized()) { - // Do dumps both with and without extended info + // Create both minimal and full memory dumps TheMiniDumper->TriggerMiniDump(DumpType_Minimal); - TheMiniDumper->TriggerMiniDump(DumpType_Gamememory); + TheMiniDumper->TriggerMiniDump(DumpType_Full); } MiniDumper::shutdownMiniDumper(); diff --git a/Core/GameEngine/Source/Common/System/GameMemory.cpp b/Core/GameEngine/Source/Common/System/GameMemory.cpp index cb062ceacf3..78a855c00dc 100644 --- a/Core/GameEngine/Source/Common/System/GameMemory.cpp +++ b/Core/GameEngine/Source/Common/System/GameMemory.cpp @@ -408,11 +408,9 @@ class MemoryPoolSingleBlock #ifdef MEMORYPOOL_BOUNDINGWALL Int m_wallPattern; ///< unique seed value for the bounding-walls for this block #endif -#if defined(MEMORYPOOL_DEBUG) || defined(RTS_ENABLE_CRASHDUMP) - Int m_logicalSize; ///< logical size of block (not including overhead, walls, etc.) -#endif #ifdef MEMORYPOOL_DEBUG const char *m_debugLiteralTagString; ///< ptr to the tagstring for this block. + Int m_logicalSize; ///< logical size of block (not including overhead, walls, etc.) Int m_wastedSize; ///< if allocated via DMA, the "wasted" bytes Short m_magicCookie; ///< magic value used to verify that the block is one of ours (as opposed to random pointer) Short m_debugFlags; ///< misc flags @@ -441,15 +439,13 @@ class MemoryPoolSingleBlock MemoryPoolSingleBlock *getNextFreeBlock(); void setNextFreeBlock(MemoryPoolSingleBlock *b); - MemoryPoolSingleBlock *getNextRawBlock() const; + MemoryPoolSingleBlock *getNextRawBlock(); void setNextRawBlock(MemoryPoolSingleBlock *b); -#if defined(MEMORYPOOL_DEBUG) || defined(RTS_ENABLE_CRASHDUMP) - Int debugGetLogicalSize() const; -#endif #ifdef MEMORYPOOL_DEBUG void debugIgnoreLeaksForThisBlock(); const char *debugGetLiteralTagString(); + Int debugGetLogicalSize(); Int debugGetWastedSize(); void debugSetWastedSize(Int waste); void debugVerifyBlock(); @@ -505,9 +501,6 @@ class MemoryPoolBlob #ifdef MEMORYPOOL_CHECKPOINTING void debugResetCheckpoints(); #endif -#ifdef RTS_ENABLE_CRASHDUMP - void fillAllocatedRange(MemoryPoolAllocatedRange& range); -#endif }; @@ -611,7 +604,7 @@ inline void MemoryPoolSingleBlock::setNextFreeBlock(MemoryPoolSingleBlock *b) return the next raw block in this dma. this call assumes that the block in question does NOT belong to a blob, and will assert if not. */ -inline MemoryPoolSingleBlock *MemoryPoolSingleBlock::getNextRawBlock() const +inline MemoryPoolSingleBlock *MemoryPoolSingleBlock::getNextRawBlock() { DEBUG_ASSERTCRASH(m_owningBlob == NULL, ("must be called on raw block")); return m_nextBlock; @@ -647,11 +640,11 @@ inline const char *MemoryPoolSingleBlock::debugGetLiteralTagString() } #endif -#if defined(MEMORYPOOL_DEBUG) || defined(RTS_ENABLE_CRASHDUMP) +#ifdef MEMORYPOOL_DEBUG /** accessor */ -inline Int MemoryPoolSingleBlock::debugGetLogicalSize() const +inline Int MemoryPoolSingleBlock::debugGetLogicalSize() { //USE_PERF_TIMER(MemoryPoolDebugging) not worth it return m_logicalSize; @@ -882,8 +875,6 @@ void MemoryPoolSingleBlock::initBlock(Int logicalSize, MemoryPoolBlob *owningBlo } #endif } -#elif defined(RTS_ENABLE_CRASHDUMP) - m_logicalSize = logicalSize; #endif // MEMORYPOOL_DEBUG #ifdef MEMORYPOOL_CHECKPOINTING @@ -1391,14 +1382,6 @@ void MemoryPoolBlob::debugResetCheckpoints() } #endif -#ifdef RTS_ENABLE_CRASHDUMP -void MemoryPoolBlob::fillAllocatedRange(MemoryPoolAllocatedRange& range) -{ - range.allocationAddr = m_blockData; - range.allocationSize = m_totalBlocksInBlob * MemoryPoolSingleBlock::calcRawBlockSize(m_owningPool->getAllocationSize()); -} -#endif - //----------------------------------------------------------------------------- // METHODS for Checkpointable //----------------------------------------------------------------------------- @@ -2590,25 +2573,6 @@ void DynamicMemoryAllocator::debugDmaInfoReport( FILE *fp ) } #endif -#ifdef RTS_ENABLE_CRASHDUMP -MemoryPoolSingleBlock* DynamicMemoryAllocator::getFirstRawBlock() const -{ - return m_rawBlocks; -} - -MemoryPoolSingleBlock* DynamicMemoryAllocator::getNextRawBlock(const MemoryPoolSingleBlock* block) const -{ - return block->getNextRawBlock(); -} - -void DynamicMemoryAllocator::fillAllocationRangeForRawBlock(const MemoryPoolSingleBlock* block, MemoryPoolAllocatedRange& allocationRange) const -{ - allocationRange.allocationAddr = reinterpret_cast(block); - allocationRange.allocationSize = block->calcRawBlockSize(block->debugGetLogicalSize()); -} - -#endif - //----------------------------------------------------------------------------- // METHODS for MemoryPoolFactory //----------------------------------------------------------------------------- @@ -3268,122 +3232,6 @@ void MemoryPoolFactory::debugMemoryReport(Int flags, Int startCheckpoint, Int en } #endif -#ifdef RTS_ENABLE_CRASHDUMP -MemoryPool* MemoryPoolFactory::getFirstMemoryPool() const -{ - return m_firstPoolInFactory; -} - -//----------------------------------------------------------------------------- -// METHODS for AllocationRangeIterator -//----------------------------------------------------------------------------- - -AllocationRangeIterator::AllocationRangeIterator() -{ - m_currentPool = NULL; - m_currentBlobInPool = NULL; - m_factory = NULL; - m_range = MemoryPoolAllocatedRange(); -} - -AllocationRangeIterator::AllocationRangeIterator(const MemoryPoolFactory* factory) -{ - m_factory = factory; - if (factory) - { - m_currentPool = factory->m_firstPoolInFactory; - m_currentBlobInPool = m_currentPool ? m_currentPool->m_firstBlob : NULL; - } - else - { - m_currentPool = NULL; - m_currentBlobInPool = NULL; - } - - m_range = MemoryPoolAllocatedRange(); -} - -AllocationRangeIterator::AllocationRangeIterator(MemoryPool& pool, MemoryPoolBlob& blob) -{ - m_currentPool = &pool; - m_currentBlobInPool = &blob; - m_factory = NULL; - m_range = MemoryPoolAllocatedRange(); -} - -AllocationRangeIterator::AllocationRangeIterator(MemoryPool* pool, MemoryPoolBlob* blob) -{ - m_currentPool = pool; - m_currentBlobInPool = blob; - m_factory = NULL; - m_range = MemoryPoolAllocatedRange(); -} - -void AllocationRangeIterator::updateRange() -{ - if (m_currentBlobInPool == NULL) - { - m_range.allocationAddr = nullptr; - m_range.allocationSize = 0; - return; - } - - m_currentBlobInPool->fillAllocatedRange(m_range); -} - -void AllocationRangeIterator::moveToNextBlob() -{ - if (m_currentBlobInPool == NULL) - { - return; - } - - // Advances to the next blob, advancing to the next MemoryPool if needed. - m_currentBlobInPool = m_currentBlobInPool->getNextInList(); - if (m_currentBlobInPool != NULL) - { - return; - } - do - { - m_currentPool = m_currentPool->getNextPoolInList(); - } while (m_currentPool != NULL && m_currentPool->m_firstBlob == NULL); - - if (m_currentPool != NULL) - { - m_currentBlobInPool = m_currentPool->m_firstBlob; - } - else - { - m_currentBlobInPool = NULL; - } -} - -AllocationRangeIterator& AllocationRangeIterator::operator++() -{ - moveToNextBlob(); - updateRange(); - return *this; -} - -AllocationRangeIterator AllocationRangeIterator::operator++(int) -{ - AllocationRangeIterator tmp = *this; - ++(*this); - return tmp; -} - -const bool operator== (const AllocationRangeIterator& a, const AllocationRangeIterator& b) -{ - return a.m_currentBlobInPool == b.m_currentBlobInPool; -} - -const bool operator!= (const AllocationRangeIterator& a, const AllocationRangeIterator& b) -{ - return a.m_currentBlobInPool != b.m_currentBlobInPool; -} -#endif - //----------------------------------------------------------------------------- // GLOBAL FUNCTIONS //----------------------------------------------------------------------------- diff --git a/Core/GameEngine/Source/Common/System/MiniDumper.cpp b/Core/GameEngine/Source/Common/System/MiniDumper.cpp index 22baf488dfa..0f461cb6e48 100644 --- a/Core/GameEngine/Source/Common/System/MiniDumper.cpp +++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp @@ -21,7 +21,6 @@ #ifdef RTS_ENABLE_CRASHDUMP #include "Common/MiniDumper.h" #include -#include "Common/GameMemory.h" #include "gitinfo.h" // Globals for storing the pointer to the exception @@ -69,12 +68,6 @@ MiniDumper::MiniDumper() m_quitting = NULL; m_dumpThread = NULL; m_dumpThreadId = 0; -#ifndef DISABLE_GAMEMEMORY - m_dumpObjectsState = DumpObjectsState_Begin; - m_currentAllocator = NULL; - m_currentSingleBlock = NULL; - m_currentPool = NULL; -#endif m_dumpDir[0] = 0; m_dumpFile[0] = 0; m_executablePath[0] = 0; @@ -122,13 +115,6 @@ void MiniDumper::TriggerMiniDumpForException(_EXCEPTION_POINTERS* e_info, DumpTy g_dumpException = e_info; g_dumpExceptionThreadId = ::GetCurrentThreadId(); m_requestedDumpType = dumpType; -#ifdef DISABLE_GAMEMEMORY - if (m_requestedDumpType == DUMP_TYPE_GAMEMEMORY) - { - // Dump the whole process if the game memory implementation is turned off - m_requestedDumpType = DUMP_TYPE_FULL; - } -#endif DEBUG_ASSERTCRASH(IsDumpThreadStillRunning(), ("MiniDumper::TriggerMiniDumpForException: Dumping thread has exited.")); ::SetEvent(m_dumpRequested); @@ -218,7 +204,6 @@ Bool MiniDumper::IsDumpThreadStillRunning() const Bool MiniDumper::InitializeDumpDirectory(const AsciiString& userDirPath) { - constexpr const Int MaxExtendedFileCount = 2; constexpr const Int MaxFullFileCount = 2; constexpr const Int MaxMiniFileCount = 10; @@ -233,8 +218,7 @@ Bool MiniDumper::InitializeDumpDirectory(const AsciiString& userDirPath) } } - // Clean up old files (we keep a maximum of 10 small, 2 extended and 2 full) - KeepNewestFiles(m_dumpDir, DumpType_Gamememory, MaxExtendedFileCount); + // Clean up old files (we keep a maximum of 10 small, 2 full) KeepNewestFiles(m_dumpDir, DumpType_Full, MaxFullFileCount); KeepNewestFiles(m_dumpDir, DumpType_Minimal, MaxMiniFileCount); @@ -349,7 +333,7 @@ DWORD WINAPI MiniDumper::MiniDumpThreadProc(LPVOID lpParam) void MiniDumper::CreateMiniDump(DumpType dumpType) { - // Create a unique dump file name, using the path from m_dumpDir, in m_dumpFile + // Create a unique dump file name, using the path from m_dumpDir, in m_dumpFile SYSTEMTIME sysTime; ::GetLocalTime(&sysTime); #if RTS_GENERALS @@ -373,8 +357,6 @@ void MiniDumper::CreateMiniDump(DumpType dumpType) return; } - m_dumpObjectsState = DumpObjectsState_Begin; - PMINIDUMP_EXCEPTION_INFORMATION exceptionInfoPtr = NULL; MINIDUMP_EXCEPTION_INFORMATION exceptionInfo = { 0 }; if (g_dumpException != NULL) @@ -385,23 +367,12 @@ void MiniDumper::CreateMiniDump(DumpType dumpType) exceptionInfoPtr = &exceptionInfo; } - PMINIDUMP_CALLBACK_INFORMATION callbackInfoPtr = NULL; - MINIDUMP_CALLBACK_INFORMATION callBackInfo = { 0 }; - if (dumpType == DumpType_Gamememory) - { - callBackInfo.CallbackRoutine = MiniDumpCallback; - callBackInfo.CallbackParam = this; - callbackInfoPtr = &callBackInfo; - } - int dumpTypeFlags = MiniDumpNormal; switch (dumpType) { case DumpType_Full: - dumpTypeFlags |= MiniDumpWithFullMemory; - FALLTHROUGH; - case DumpType_Gamememory: - dumpTypeFlags |= MiniDumpWithDataSegs | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithFullMemoryInfo | MiniDumpWithPrivateReadWriteMemory; + dumpTypeFlags |= MiniDumpWithFullMemory | MiniDumpWithDataSegs | MiniDumpWithHandleData | + MiniDumpWithThreadInfo | MiniDumpWithFullMemoryInfo | MiniDumpWithPrivateReadWriteMemory; FALLTHROUGH; case DumpType_Minimal: dumpTypeFlags |= MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory; @@ -416,7 +387,7 @@ void MiniDumper::CreateMiniDump(DumpType dumpType) miniDumpType, exceptionInfoPtr, NULL, - callbackInfoPtr); + NULL); if (!success) { @@ -430,198 +401,6 @@ void MiniDumper::CreateMiniDump(DumpType dumpType) ::CloseHandle(dumpFile); } -BOOL CALLBACK MiniDumper::MiniDumpCallback(PVOID CallbackParam, PMINIDUMP_CALLBACK_INPUT CallbackInput, PMINIDUMP_CALLBACK_OUTPUT CallbackOutput) -{ - if (CallbackParam == NULL || CallbackInput == NULL || CallbackOutput == NULL) - { - DEBUG_LOG(("MiniDumper::MiniDumpCallback: Required parameters were null; CallbackParam=%p, CallbackInput=%p, CallbackOutput=%p.", - CallbackParam, CallbackInput, CallbackOutput)); - return false; - } - - MiniDumper* dumper = static_cast(CallbackParam); - return dumper->CallbackInternal(*CallbackInput, *CallbackOutput); -} - -Bool MiniDumper::ShouldWriteDataSegsForModule(const PWCHAR module) const -{ - // Only include data segments for the game, ntdll and kernel32 modules to keep dump size low - static constexpr const WideChar* wanted_modules[] = { L"ntdll.dll", L"kernel32.dll"}; - if (endsWithNoCase(module, m_executablePath)) - { - return true; - } - - for (size_t i = 0; i < ARRAY_SIZE(wanted_modules); ++i) - { - if (endsWithNoCase(module, wanted_modules[i])) - { - return true; - } - } - - return false; -} - -// This is where the memory regions and things are being filtered -BOOL MiniDumper::CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP_CALLBACK_OUTPUT& output) -{ - BOOL success = TRUE; - switch (input.CallbackType) - { - case IncludeModuleCallback: - case ThreadCallback: - case ThreadExCallback: - break; - case ModuleCallback: - if (output.ModuleWriteFlags & ModuleWriteDataSeg) - { - if (!ShouldWriteDataSegsForModule(input.Module.FullPath)) - { - // Exclude data segments for the module - output.ModuleWriteFlags &= (~ModuleWriteDataSeg); - } - } - break; - case IncludeThreadCallback: - // We want all threads except the dumping thread - if (input.IncludeThread.ThreadId == m_dumpThreadId) - { - output.ThreadWriteFlags &= (~ThreadWriteThread); - } - break; - case MemoryCallback: -#ifndef DISABLE_GAMEMEMORY - // DumpMemoryObjects will set output.MemorySize to 0 once it's completed, - // signalling to end the memory callbacks. - DumpMemoryObjects(output.MemoryBase, output.MemorySize); -#else - output.MemoryBase = 0; - output.MemorySize = 0; -#endif - break; - case ReadMemoryFailureCallback: - DEBUG_LOG(("MiniDumper::CallbackInternal: ReadMemoryFailure with MemoryBase=%llu, MemorySize=%lu: error=%u", - input.ReadMemoryFailure.Offset, input.ReadMemoryFailure.Bytes, input.ReadMemoryFailure.FailureStatus)); - break; - case CancelCallback: - // The cancel callback checks if the current minidump should be cancelled. - // Set output to never wish to cancel, nor to receive cancel callbacks in the future. - output.Cancel = FALSE; - output.CheckCancel = FALSE; - break; - } - - return success; -} - -#ifndef DISABLE_GAMEMEMORY -void MiniDumper::MoveToNextAllocatorWithRawBlocks() -{ - // Skip over DMAs that have no raw blocks - while (m_currentAllocator && !m_currentAllocator->getFirstRawBlock()) - { - m_currentAllocator = m_currentAllocator->getNextDmaInList(); - } - - if (m_currentAllocator) - { - m_currentSingleBlock = m_currentAllocator->getFirstRawBlock(); - } -} - -void MiniDumper::MoveToNextSingleBlock() -{ - DEBUG_ASSERTCRASH(m_currentAllocator != NULL, ("MiniDumper::MoveToNextSingleBlock: m_currentAllocator is NULL")); - m_currentSingleBlock = m_currentAllocator->getNextRawBlock(m_currentSingleBlock); - - if (m_currentSingleBlock == NULL) - { - m_currentAllocator = m_currentAllocator->getNextDmaInList(); - MoveToNextAllocatorWithRawBlocks(); - } -} - -void MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) -{ - memorySize = 0; - do - { - // m_dumpObjectsState is used to keep track of the current "phase" of the memory dumping process - switch (m_dumpObjectsState) - { - case DumpObjectsState_Begin: - m_dumpObjectsState = DumpObjectsState_MemoryPools; - if (TheMemoryPoolFactory) - { - m_currentPool = TheMemoryPoolFactory->getFirstMemoryPool(); - m_rangeIter = TheMemoryPoolFactory->cbegin(); - m_currentAllocator = TheDynamicMemoryAllocator; - MoveToNextAllocatorWithRawBlocks(); - } - break; - case DumpObjectsState_MemoryPools: - { - // Dump all the MemoryPool instances in TheMemoryPoolFactory - // This only dumps the metadata, not the actual MemoryPool contents (done in the next phase). - if (m_currentPool == NULL) - { - m_dumpObjectsState = DumpObjectsState_MemoryPoolAllocations; - break; - } - - memoryBase = reinterpret_cast(m_currentPool); - memorySize = sizeof(MemoryPool); - m_currentPool = m_currentPool->getNextPoolInList(); - break; - } - case DumpObjectsState_MemoryPoolAllocations: - { - // Iterate through all the allocations of memory pools and containing blobs that has been done via the memory pool factory - // and include all of the storage space allocated for objects - if (!TheMemoryPoolFactory || m_rangeIter == TheMemoryPoolFactory->cend()) - { - m_dumpObjectsState = DumpObjectsState_DMAAllocations; - break; - } - - memoryBase = reinterpret_cast(m_rangeIter->allocationAddr); - memorySize = m_rangeIter->allocationSize; - ++m_rangeIter; - break; - } - case DumpObjectsState_DMAAllocations: - { - // Iterate through all the direct allocations ("raw blocks") done by DMAs, as these are done outside of the - // memory pool factory allocations dumped in the previous phase. - if (m_currentAllocator == NULL) - { - m_dumpObjectsState = DumpObjectsState_Completed; - break; - } - - MemoryPoolAllocatedRange rawBlockRange; - m_currentAllocator->fillAllocationRangeForRawBlock(m_currentSingleBlock, rawBlockRange); - memoryBase = reinterpret_cast(rawBlockRange.allocationAddr); - memorySize = rawBlockRange.allocationSize; - MoveToNextSingleBlock(); - break; - } - case DumpObjectsState_Completed: - // Done, set "no more regions to dump" values - memoryBase = 0; - memorySize = 0; - return; - default: - DEBUG_CRASH(("Invalid object state")); - break; - } - // If memorySize is 0 we transitioned from one phase to the next. - // Go again so the memory block info gets populated in the new phase. - } while (memorySize == 0); -} -#endif - // Comparator for sorting files by last modified time (newest first) bool MiniDumper::CompareByLastWriteTime(const FileInfo& a, const FileInfo& b) { diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index 12b0af5df8d..47412511028 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -747,9 +747,9 @@ static LONG WINAPI UnHandledExceptionFilter( struct _EXCEPTION_POINTERS* e_info #ifdef RTS_ENABLE_CRASHDUMP if (TheMiniDumper && TheMiniDumper->IsInitialized()) { - // Do dumps both with and without extended info + // Create both minimal and full memory dumps TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Minimal); - TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Gamememory); + TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Full); } MiniDumper::shutdownMiniDumper(); diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index fb494dc438c..a92a799932f 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -769,9 +769,9 @@ static LONG WINAPI UnHandledExceptionFilter( struct _EXCEPTION_POINTERS* e_info #ifdef RTS_ENABLE_CRASHDUMP if (TheMiniDumper && TheMiniDumper->IsInitialized()) { - // Do dumps both with and without extended info + // Create both minimal and full memory dumps TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Minimal); - TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Gamememory); + TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Full); } MiniDumper::shutdownMiniDumper(); From 7ce2363282bfd84ac19392acdcdb2499fdb84e1e Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Sat, 6 Dec 2025 16:43:10 +0100 Subject: [PATCH 8/8] Already enough trailing whitespace, no need to add more --- Core/GameEngine/Source/Common/System/MiniDumper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/GameEngine/Source/Common/System/MiniDumper.cpp b/Core/GameEngine/Source/Common/System/MiniDumper.cpp index 0f461cb6e48..21ab80e578a 100644 --- a/Core/GameEngine/Source/Common/System/MiniDumper.cpp +++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp @@ -333,7 +333,7 @@ DWORD WINAPI MiniDumper::MiniDumpThreadProc(LPVOID lpParam) void MiniDumper::CreateMiniDump(DumpType dumpType) { - // Create a unique dump file name, using the path from m_dumpDir, in m_dumpFile + // Create a unique dump file name, using the path from m_dumpDir, in m_dumpFile SYSTEMTIME sysTime; ::GetLocalTime(&sysTime); #if RTS_GENERALS