From 7e85db6e0f1d1424dbd91282324e24f366d4c7f8 Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Thu, 18 Sep 2025 10:55:16 +0200 Subject: [PATCH 1/6] feat(crashdump): Add crash dump functionality --- Core/GameEngine/CMakeLists.txt | 3 + Core/GameEngine/Include/Common/GameMemory.h | 84 +++ Core/GameEngine/Include/Common/MiniDumper.h | 120 ++++ .../Include/Common/MiniDumper_compat.h | 254 +++++++ .../GameEngine/Source/Common/System/Debug.cpp | 25 + .../Source/Common/System/GameMemory.cpp | 112 ++- .../Source/Common/System/MiniDumper.cpp | 635 ++++++++++++++++++ Generals/Code/Main/WinMain.cpp | 22 + GeneralsMD/Code/Main/WinMain.cpp | 22 + cmake/config-memory.cmake | 12 + 10 files changed, 1286 insertions(+), 3 deletions(-) create mode 100644 Core/GameEngine/Include/Common/MiniDumper.h create mode 100644 Core/GameEngine/Include/Common/MiniDumper_compat.h create mode 100644 Core/GameEngine/Source/Common/System/MiniDumper.cpp diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index 0a58dfc431..0cb1d51746 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -71,6 +71,8 @@ set(GAMEENGINE_SRC # Include/Common/MapObject.h # Include/Common/MapReaderWriterInfo.h # Include/Common/MessageStream.h + Include/Common/MiniDumper.h + Include/Common/MiniDumper_compat.h # Include/Common/MiniLog.h Include/Common/MiscAudio.h # Include/Common/MissionStats.h @@ -658,6 +660,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 8d173afec4..9897ff6338 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,59 @@ 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 bool operator== (const AllocationRangeIterator& a, const AllocationRangeIterator& b) { return a.m_currentBlobInPool == b.m_currentBlobInPool; }; + friend 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 +644,19 @@ 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 0000000000..929756133b --- /dev/null +++ b/Core/GameEngine/Include/Common/MiniDumper.h @@ -0,0 +1,120 @@ +/* +** 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 +#include "Common/MiniDumper_compat.h" + +class MiniDumper +{ +public: + MiniDumper() + { + m_miniDumpInitialized = false; + m_extendedInfoRequested = false; + m_dbgHlp = NULL; + m_pMiniDumpWriteDump = NULL; + m_dumpRequested = NULL; + m_dumpComplete = NULL; + m_quitting = NULL; + m_dumpThread = NULL; + m_dumpThreadId = 0; + m_dumpObjectsState = 0; + m_dumpObjectsSubState = 0; + m_dmaRawBlockIndex = 0; + memset(m_dumpDir, 0, ARRAY_SIZE(m_dumpDir)); + memset(m_dumpFile, 0, ARRAY_SIZE(m_dumpFile)); + memset(m_sysDbgHelpPath, 0, ARRAY_SIZE(m_sysDbgHelpPath)); + }; + + void Initialize(const AsciiString& userDirPath); + Bool IsInitialized() const; + void TriggerMiniDump(Bool extendedInfo = false); + void TriggerMiniDumpForException(struct _EXCEPTION_POINTERS* e_info, Bool extendedInfo = false); + void ShutDown(); + static LONG WINAPI DumpingExceptionFilter(struct _EXCEPTION_POINTERS* e_info); +private: + void CreateMiniDump(Bool extendedInfo); + BOOL DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize); + void CleanupResources(); + + // 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 std::string& fileWildcard, 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_extendedInfoRequested; + + // Path buffers + Char m_dumpDir[MAX_PATH]; + Char m_dumpFile[MAX_PATH]; + Char m_sysDbgHelpPath[MAX_PATH]; + + // Module handles + HMODULE m_dbgHlp; + + // Event handles + HANDLE m_dumpRequested; + HANDLE m_dumpComplete; + HANDLE m_quitting; + + // Thread handles + HANDLE m_dumpThread; + DWORD m_dumpThreadId; + + // Internal memory dumping progress state + int m_dumpObjectsState; + int m_dumpObjectsSubState; + int m_dmaRawBlockIndex; + + AllocationRangeIterator m_RangeIter; + AllocationRangeIterator m_endRangeIter; + + // Function pointer to MiniDumpWriteDump in dbghelp.dll + 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 + ); + + MiniDumpWriteDump_t m_pMiniDumpWriteDump; +}; +#endif diff --git a/Core/GameEngine/Include/Common/MiniDumper_compat.h b/Core/GameEngine/Include/Common/MiniDumper_compat.h new file mode 100644 index 0000000000..ab28931418 --- /dev/null +++ b/Core/GameEngine/Include/Common/MiniDumper_compat.h @@ -0,0 +1,254 @@ +#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 +#pragma pack(push, 4) + +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; + +#pragma pack(pop) +#endif +#endif diff --git a/Core/GameEngine/Source/Common/System/Debug.cpp b/Core/GameEngine/Source/Common/System/Debug.cpp index 55dcdcd65f..0f283edf23 100644 --- a/Core/GameEngine/Source/Common/System/Debug.cpp +++ b/Core/GameEngine/Source/Common/System/Debug.cpp @@ -70,6 +70,11 @@ #if defined(DEBUG_STACKTRACE) || defined(IG_DEBUG_STACKTRACE) #include "Common/StackDump.h" #endif +#ifdef RTS_ENABLE_CRASHDUMP +#include "Common/MiniDumper.h" + +MiniDumper TheMiniDumper = MiniDumper(); +#endif // Horrible reference, but we really, really need to know if we are windowed. extern bool DX8Wrapper_IsWindowed; @@ -729,6 +734,16 @@ void ReleaseCrash(const char *reason) } } +#ifdef RTS_ENABLE_CRASHDUMP + if (TheMiniDumper.IsInitialized()) + { + // Do dumps both with and without extended info + TheMiniDumper.TriggerMiniDump(false); + TheMiniDumper.TriggerMiniDump(true); + TheMiniDumper.ShutDown(); + } +#endif + char prevbuf[ _MAX_PATH ]; char curbuf[ _MAX_PATH ]; @@ -794,6 +809,16 @@ void ReleaseCrashLocalized(const AsciiString& p, const AsciiString& m) return; } +#ifdef RTS_ENABLE_CRASHDUMP + if (TheMiniDumper.IsInitialized()) + { + // Do dumps both with and without extended info + TheMiniDumper.TriggerMiniDump(false); + TheMiniDumper.TriggerMiniDump(true); + TheMiniDumper.ShutDown(); + } +#endif + 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 8fcf71b138..484469dcce 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,30 @@ 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 +3273,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 0000000000..a948fe4805 --- /dev/null +++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp @@ -0,0 +1,635 @@ +/* +** 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 "Common/GameMemory.h" +#include "gitinfo.h" + +// Globals for storing the pointer to the exception +_EXCEPTION_POINTERS* g_dumpException = NULL; +DWORD g_dumpExceptionThreadId = 0; + +// 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 }; + + +LONG WINAPI MiniDumper::DumpingExceptionFilter(struct _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(Bool extendedInfo) +{ + 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, extendedInfo); + } +} + +void MiniDumper::TriggerMiniDumpForException(struct _EXCEPTION_POINTERS* e_info, Bool extendedInfo) +{ + if (!m_miniDumpInitialized) + { + DEBUG_LOG(("MiniDumper::TriggerMiniDumpForException: Attempted to use an uninitialized instance.")); + return; + } + + g_dumpException = e_info; + g_dumpExceptionThreadId = GetCurrentThreadId(); + m_extendedInfoRequested = extendedInfo; + + 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) +{ + // Find the full path to the dbghelp.dll file in the system32 dir + GetSystemDirectory(m_sysDbgHelpPath, MAX_PATH); + strlcat(m_sysDbgHelpPath, "\\dbghelp.dll", MAX_PATH); + + // We want to only use the dbghelp.dll from the OS installation, as the one bundled with the game does not support MiniDump functionality + Bool loadedDbgHelp = false; + HMODULE m_dbgHlp = GetModuleHandle(m_sysDbgHelpPath); + if (m_dbgHlp == NULL) + { + // Load the dbghelp library from the system folder + m_dbgHlp = LoadLibrary(m_sysDbgHelpPath); + if (m_dbgHlp == NULL) + { + DEBUG_LOG(("MiniDumper::Initialize: Unable to load system-provided dbghelp.dll from '%s', error code=%u", m_sysDbgHelpPath, GetLastError())); + return; + } + + loadedDbgHelp = true; + } + + m_pMiniDumpWriteDump = (MiniDumpWriteDump_t)GetProcAddress(m_dbgHlp, "MiniDumpWriteDump"); + if (m_pMiniDumpWriteDump == NULL) + { + if (loadedDbgHelp) + { + FreeLibrary(m_dbgHlp); + m_dbgHlp = NULL; + } + + DEBUG_LOG(("MiniDumper::Initialize: Could not get address of proc MiniDumpWriteDump from '%s'!", m_sysDbgHelpPath)); + 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())); + CleanupResources(); + 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())); + CleanupResources(); + return; + } + + if (!ResumeThread(m_dumpThread)) + { + DEBUG_LOG(("MiniDumper::Initialize: Unable to resume thread: error=%u", GetLastError())); + CleanupResources(); + 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::InitializeDumpDirectory(const AsciiString& userDirPath) +{ + constexpr Int MaxExtendedFileCount = 2; + constexpr 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': %u", m_dumpDir, GetLastError())); + return false; + } + } + + // Clean up old files (we keep a maximum of 10 small and 2 extended) + KeepNewestFiles(m_dumpDir, "CrashX*", MaxExtendedFileCount); + KeepNewestFiles(m_dumpDir, "CrashM*", MaxMiniFileCount); + + return true; +} + +void MiniDumper::CleanupResources() +{ + // NOTE: This method should not be called unless the dump thread is confirmed to not be running anymore. + if (m_dumpThread != NULL) + { + CloseHandle(m_dumpThread); + m_dumpThread = NULL; + } + + if (m_dumpComplete != NULL) + { + CloseHandle(m_dumpComplete); + m_dumpComplete = NULL; + } + + if (m_dumpRequested != NULL) + { + CloseHandle(m_dumpRequested); + m_dumpRequested = NULL; + } + + if (m_quitting != NULL) + { + CloseHandle(m_quitting); + m_quitting = NULL; + } + + if (m_dbgHlp != NULL) + { + FreeModule(m_dbgHlp); + m_dbgHlp = NULL; + } +} + +void MiniDumper::ShutDown() +{ + if (!m_miniDumpInitialized) + { + return; + } + + SetEvent(m_quitting); + DWORD waitRet = WaitForSingleObject(m_dumpThread, 3000); + if (waitRet != WAIT_OBJECT_0) + { + if (waitRet == WAIT_TIMEOUT) + { + DEBUG_LOG(("MiniDumper::ShutDown: Waiting for dumping thread to exit timed out, killing thread", waitRet)); + TerminateThread(m_dumpThread, 2); + } + else if (waitRet == WAIT_FAILED) + { + DEBUG_LOG(("MiniDumper::ShutDown: Waiting for minidump triggering failed, status=%u, error=%u", waitRet, GetLastError())); + } + else + { + DEBUG_LOG(("MiniDumper::ShutDown: Waiting for minidump triggering failed, status=%u", waitRet)); + } + return; + } + + CleanupResources(); + m_miniDumpInitialized = false; +} + +DWORD MiniDumper::ThreadProcInternal() +{ + while (true) + { + HANDLE waitEvents[2] = { m_dumpRequested, m_quitting }; + DWORD event = WaitForMultipleObjects(ARRAY_SIZE(waitEvents), waitEvents, FALSE, INFINITE); + if (event == WAIT_OBJECT_0 + 0) + { + // A dump is requested (m_dumpRequested) + ResetEvent(m_dumpComplete); + CreateMiniDump(m_extendedInfoRequested); + ResetEvent(m_dumpRequested); + SetEvent(m_dumpComplete); + } + else if (event == WAIT_OBJECT_0 + 1) + { + // Quit (m_quitting) + return 0; + } + else + { + if (event == WAIT_FAILED) + { + DEBUG_LOG(("MiniDumper::ThreadProcInternal: Waiting for events failed, status=%u, error=%u", event, GetLastError())); + } + else + { + DEBUG_LOG(("MiniDumper::ThreadProcInternal: Waiting for events failed, status=%u", event)); + } + return 1; + } + } +} + +DWORD WINAPI MiniDumper::MiniDumpThreadProc(LPVOID lpParam) +{ + if (lpParam == NULL) + { + DEBUG_LOG(("MiniDumper::MiniDumpThreadProc: The provided parameter was NULL, exiting thread.")); + return -1; + } + + MiniDumper* dumper = static_cast(lpParam); + return dumper->ThreadProcInternal(); +} + + +void MiniDumper::CreateMiniDump(Bool extendedInfo) +{ + // Create a unique dump file name, using the path from m_dumpDir, in m_dumpFile + SYSTEMTIME sysTime; + GetLocalTime(&sysTime); +#if RTS_ZEROHOUR + Char product = 'Z'; +#else + Char product = 'G'; +#endif + Char dumpTypeSpecifier = extendedInfo ? 'X' : 'M'; + DWORD currentProcessId = GetCurrentProcessId(); + DWORD currentThreadId = GetCurrentThreadId(); + + // m_dumpDir is stored with trailing backslash in Initialize + snprintf(m_dumpFile, ARRAY_SIZE(m_dumpFile), "%sCrash%c%c-%04d%02d%02d-%02d%02d%02d-%s-%ld-%ld.dmp", + m_dumpDir, dumpTypeSpecifier, product, sysTime.wYear, sysTime.wMonth, + sysTime.wDay, sysTime.wHour, sysTime.wMinute, sysTime.wSecond, + GitShortSHA1, currentProcessId, currentThreadId); + + 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 (extendedInfo) + { + callBackInfo.CallbackRoutine = MiniDumpCallback; + callBackInfo.CallbackParam = this; + callbackInfoPtr = &callBackInfo; + } + + MINIDUMP_TYPE dumpType = static_cast(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory); + if (extendedInfo) + { + dumpType = static_cast(MiniDumpWithDataSegs | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpScanMemory | MiniDumpWithIndirectlyReferencedMemory | MiniDumpWithFullMemoryInfo); + } + + BOOL success = m_pMiniDumpWriteDump( + GetCurrentProcess(), + currentProcessId, + dumpFile, + dumpType, + 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); +} + +// 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: + { + // Only include data segments for the game and ntdll modules to keep dump size low + if (output.ModuleWriteFlags & ModuleWriteDataSeg) + { + if (!StrStrIW(input.Module.FullPath, L"generalszh.exe") && !StrStrIW(input.Module.FullPath, L"generalsv.exe") && !StrStrIW(input.Module.FullPath, L"ntdll.dll")) + { + // 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: + { + 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); + 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; +} + +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_endRangeIter = TheMemoryPoolFactory->cend(); + ++m_dumpObjectsSubState; + } + + // m_RangeIter should != m_endRangeIter, 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 == m_endRangeIter) + { + ++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; +} + +// 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 std::string& fileWildcard, const Int keepCount) +{ + // directory already contains trailing backslash + std::string searchPath = directory + fileWildcard; + 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': %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/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index 3eacc83fdf..16a02fbcf2 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 //////////////////////////////////////////////////////////////////// @@ -71,6 +74,9 @@ HINSTANCE ApplicationHInstance = NULL; ///< our application instance HWND ApplicationHWnd = NULL; ///< our application window handle Win32Mouse *TheWin32Mouse= NULL; ///< for the WndProc() only DWORD TheMessageTime = 0; ///< For getting the time that a message was posted from Windows. +#ifdef RTS_ENABLE_CRASHDUMP +extern MiniDumper TheMiniDumper; +#endif const Char *g_strFile = "data\\Generals.str"; const Char *g_csfFile = "data\\%s\\Generals.csf"; @@ -741,6 +747,15 @@ 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.IsInitialized()) + { + // Do dumps both with and without extended info + TheMiniDumper.TriggerMiniDumpForException(e_info, false); + TheMiniDumper.TriggerMiniDumpForException(e_info, true); + TheMiniDumper.ShutDown(); + } +#endif return EXCEPTION_EXECUTE_HANDLER; } @@ -807,6 +822,10 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, CommandLine::parseCommandLineForStartup(); +#ifdef RTS_ENABLE_CRASHDUMP + // Initialize minidump facilities - requires TheGlobalData so performed after parseCommandLineForStartup + TheMiniDumper.Initialize(TheGlobalData->getPath_UserData()); +#endif // register windows class and create application window if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) return exitcode; @@ -874,6 +893,9 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, } +#ifdef RTS_ENABLE_CRASHDUMP + TheMiniDumper.ShutDown(); +#endif TheAsciiStringCriticalSection = NULL; TheUnicodeStringCriticalSection = NULL; TheDmaCriticalSection = NULL; diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index e23d4e4b44..0d58c2339c 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 //////////////////////////////////////////////////////////////////// @@ -74,6 +77,9 @@ HINSTANCE ApplicationHInstance = NULL; ///< our application instance HWND ApplicationHWnd = NULL; ///< our application window handle Win32Mouse *TheWin32Mouse= NULL; ///< for the WndProc() only DWORD TheMessageTime = 0; ///< For getting the time that a message was posted from Windows. +#ifdef RTS_ENABLE_CRASHDUMP +extern MiniDumper TheMiniDumper; +#endif const Char *g_strFile = "data\\Generals.str"; const Char *g_csfFile = "data\\%s\\Generals.csf"; @@ -763,6 +769,15 @@ 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.IsInitialized()) + { + // Do dumps both with and without extended info + TheMiniDumper.TriggerMiniDumpForException(e_info, false); + TheMiniDumper.TriggerMiniDumpForException(e_info, true); + TheMiniDumper.ShutDown(); + } +#endif return EXCEPTION_EXECUTE_HANDLER; } @@ -853,6 +868,10 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, #endif CommandLine::parseCommandLineForStartup(); +#ifdef RTS_ENABLE_CRASHDUMP + // Initialize minidump facilities - requires TheGlobalData so performed after parseCommandLineForStartup + TheMiniDumper.Initialize(TheGlobalData->getPath_UserData()); +#endif // register windows class and create application window if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) @@ -922,6 +941,9 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, } +#ifdef RTS_ENABLE_CRASHDUMP + TheMiniDumper.ShutDown(); +#endif TheUnicodeStringCriticalSection = NULL; TheDmaCriticalSection = NULL; TheMemoryPoolCriticalSection = NULL; diff --git a/cmake/config-memory.cmake b/cmake/config-memory.cmake index 72a28d1c94..ae5f61d0f8 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 (depends on original game memory implementation) +cmake_dependent_option(RTS_CRASHDUMP_ENABLE "Enables writing crash dumps on unhandled exceptions or release crash failures." ON RTS_GAMEMEMORY_ENABLE OFF) + # 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(WARNING "Crash Dumps will be significantly less useful in VC6 builds without full debug info enabled") + endif() +endif() From 68aa823c2bc2ce8e10e40d16860e3636f6f5234c Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Tue, 14 Oct 2025 21:50:32 +0200 Subject: [PATCH 2/6] Address review feedback --- Core/GameEngine/CMakeLists.txt | 1 + Core/GameEngine/Include/Common/GameMemory.h | 24 +- Core/GameEngine/Include/Common/MiniDumper.h | 63 +++-- .../GameEngine/Source/Common/System/Debug.cpp | 18 +- .../Source/Common/System/GameMemory.cpp | 1 - .../Source/Common/System/MiniDumper.cpp | 264 ++++++++++++------ Generals/Code/Main/WinMain.cpp | 15 +- GeneralsMD/Code/Main/WinMain.cpp | 15 +- cmake/config-memory.cmake | 4 +- 9 files changed, 251 insertions(+), 154 deletions(-) diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index 0cb1d51746..d586b8db2c 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -1174,6 +1174,7 @@ target_include_directories(corei_gameengine_private INTERFACE target_link_libraries(corei_gameengine_private INTERFACE corei_always + $<$,$>:shlwapi.lib> ) target_compile_definitions(corei_gameengine_private INTERFACE diff --git a/Core/GameEngine/Include/Common/GameMemory.h b/Core/GameEngine/Include/Common/GameMemory.h index 9897ff6338..3a953de835 100644 --- a/Core/GameEngine/Include/Common/GameMemory.h +++ b/Core/GameEngine/Include/Common/GameMemory.h @@ -500,14 +500,17 @@ enum { MAX_SPECIAL_USED = 256 }; #endif #ifdef RTS_ENABLE_CRASHDUMP -class AllocationRangeIterator { +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) { + AllocationRangeIterator(MemoryPool& pool, MemoryPoolBlob& blob) + { m_currentPool = &pool; m_currentBlobInPool = &blob; m_factory = NULL; @@ -539,8 +542,15 @@ class AllocationRangeIterator { // Postfix increment AllocationRangeIterator operator++(int) { AllocationRangeIterator tmp = *this; ++(*this); return tmp; } - friend bool operator== (const AllocationRangeIterator& a, const AllocationRangeIterator& b) { return a.m_currentBlobInPool == b.m_currentBlobInPool; }; - friend 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) + { + return a.m_currentBlobInPool != b.m_currentBlobInPool; + }; private: const MemoryPoolFactory* m_factory; @@ -645,11 +655,13 @@ class MemoryPoolFactory #endif #ifdef RTS_ENABLE_CRASHDUMP - AllocationRangeIterator cbegin() const { + AllocationRangeIterator cbegin() const + { return AllocationRangeIterator(this); } - AllocationRangeIterator cend() const { + AllocationRangeIterator cend() const + { return AllocationRangeIterator(NULL, NULL); } diff --git a/Core/GameEngine/Include/Common/MiniDumper.h b/Core/GameEngine/Include/Common/MiniDumper.h index 929756133b..9a519acb73 100644 --- a/Core/GameEngine/Include/Common/MiniDumper.h +++ b/Core/GameEngine/Include/Common/MiniDumper.h @@ -22,38 +22,42 @@ #include #include "Common/MiniDumper_compat.h" +enum DumpType CPP_11(: Int) +{ + // Smallest dump type with call stacks and some supporting variables + DUMP_TYPE_MINIMAL, + // Large dump including all memory regions allocated by the GameMemory implementaion + DUMP_TYPE_GAMEMEMORY, + // Largest dump size including complete memory contents of the process + DUMP_TYPE_FULL, +}; + +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() - { - m_miniDumpInitialized = false; - m_extendedInfoRequested = false; - m_dbgHlp = NULL; - m_pMiniDumpWriteDump = NULL; - m_dumpRequested = NULL; - m_dumpComplete = NULL; - m_quitting = NULL; - m_dumpThread = NULL; - m_dumpThreadId = 0; - m_dumpObjectsState = 0; - m_dumpObjectsSubState = 0; - m_dmaRawBlockIndex = 0; - memset(m_dumpDir, 0, ARRAY_SIZE(m_dumpDir)); - memset(m_dumpFile, 0, ARRAY_SIZE(m_dumpFile)); - memset(m_sysDbgHelpPath, 0, ARRAY_SIZE(m_sysDbgHelpPath)); - }; - - void Initialize(const AsciiString& userDirPath); + MiniDumper(); Bool IsInitialized() const; - void TriggerMiniDump(Bool extendedInfo = false); - void TriggerMiniDumpForException(struct _EXCEPTION_POINTERS* e_info, Bool extendedInfo = false); - void ShutDown(); + void TriggerMiniDump(DumpType dumpType); + void TriggerMiniDumpForException(struct _EXCEPTION_POINTERS* e_info, DumpType dumpType); + static void initMiniDumper(const AsciiString& userDirPath); + static void shutdownMiniDumper(); static LONG WINAPI DumpingExceptionFilter(struct _EXCEPTION_POINTERS* e_info); + private: - void CreateMiniDump(Bool extendedInfo); + void Initialize(const AsciiString& userDirPath); + void ShutDown(); + void CreateMiniDump(DumpType dumpType); BOOL DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize); void CleanupResources(); + Bool IsDumpThreadStillRunning() const; // Callbacks from dbghelp static BOOL CALLBACK MiniDumpCallback(PVOID CallbackParam, PMINIDUMP_CALLBACK_INPUT CallbackInput, PMINIDUMP_CALLBACK_OUTPUT CallbackOutput); @@ -68,7 +72,8 @@ class MiniDumper static void KeepNewestFiles(const std::string& directory, const std::string& fileWildcard, const Int keepCount); // Struct to hold file information - struct FileInfo { + struct FileInfo + { std::string name; FILETIME lastWriteTime; }; @@ -77,12 +82,13 @@ class MiniDumper private: Bool m_miniDumpInitialized; - Bool m_extendedInfoRequested; + DumpType m_requestedDumpType; // Path buffers Char m_dumpDir[MAX_PATH]; Char m_dumpFile[MAX_PATH]; Char m_sysDbgHelpPath[MAX_PATH]; + WideChar m_executablePath[MAX_PATH]; // Module handles HMODULE m_dbgHlp; @@ -101,8 +107,7 @@ class MiniDumper int m_dumpObjectsSubState; int m_dmaRawBlockIndex; - AllocationRangeIterator m_RangeIter; - AllocationRangeIterator m_endRangeIter; + AllocationRangeIterator m_rangeIter; // Function pointer to MiniDumpWriteDump in dbghelp.dll typedef BOOL(WINAPI* MiniDumpWriteDump_t)( @@ -117,4 +122,6 @@ class MiniDumper MiniDumpWriteDump_t m_pMiniDumpWriteDump; }; + +extern MiniDumper* TheMiniDumper; #endif diff --git a/Core/GameEngine/Source/Common/System/Debug.cpp b/Core/GameEngine/Source/Common/System/Debug.cpp index 0f283edf23..8f2eaf4a8c 100644 --- a/Core/GameEngine/Source/Common/System/Debug.cpp +++ b/Core/GameEngine/Source/Common/System/Debug.cpp @@ -72,8 +72,6 @@ #endif #ifdef RTS_ENABLE_CRASHDUMP #include "Common/MiniDumper.h" - -MiniDumper TheMiniDumper = MiniDumper(); #endif // Horrible reference, but we really, really need to know if we are windowed. @@ -735,12 +733,12 @@ void ReleaseCrash(const char *reason) } #ifdef RTS_ENABLE_CRASHDUMP - if (TheMiniDumper.IsInitialized()) + if (TheMiniDumper && TheMiniDumper->IsInitialized()) { // Do dumps both with and without extended info - TheMiniDumper.TriggerMiniDump(false); - TheMiniDumper.TriggerMiniDump(true); - TheMiniDumper.ShutDown(); + TheMiniDumper->TriggerMiniDump(DUMP_TYPE_MINIMAL); + TheMiniDumper->TriggerMiniDump(DUMP_TYPE_GAMEMEMORY); + MiniDumper::shutdownMiniDumper(); } #endif @@ -810,12 +808,12 @@ void ReleaseCrashLocalized(const AsciiString& p, const AsciiString& m) } #ifdef RTS_ENABLE_CRASHDUMP - if (TheMiniDumper.IsInitialized()) + if (TheMiniDumper && TheMiniDumper->IsInitialized()) { // Do dumps both with and without extended info - TheMiniDumper.TriggerMiniDump(false); - TheMiniDumper.TriggerMiniDump(true); - TheMiniDumper.ShutDown(); + TheMiniDumper->TriggerMiniDump(DUMP_TYPE_MINIMAL); + TheMiniDumper->TriggerMiniDump(DUMP_TYPE_GAMEMEMORY); + MiniDumper::shutdownMiniDumper(); } #endif diff --git a/Core/GameEngine/Source/Common/System/GameMemory.cpp b/Core/GameEngine/Source/Common/System/GameMemory.cpp index 484469dcce..ff8a29ba04 100644 --- a/Core/GameEngine/Source/Common/System/GameMemory.cpp +++ b/Core/GameEngine/Source/Common/System/GameMemory.cpp @@ -2603,7 +2603,6 @@ Int DynamicMemoryAllocator::getRawBlockCount() const } void DynamicMemoryAllocator::fillAllocationRangeForRawBlockN(const Int n, MemoryPoolAllocatedRange& allocationRange) const { - MemoryPoolSingleBlock* block = m_rawBlocks; for (int i = 0; i < n; ++i) { diff --git a/Core/GameEngine/Source/Common/System/MiniDumper.cpp b/Core/GameEngine/Source/Common/System/MiniDumper.cpp index a948fe4805..0bf978b097 100644 --- a/Core/GameEngine/Source/Common/System/MiniDumper.cpp +++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp @@ -27,12 +27,54 @@ _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 }; +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_requestedDumpType = DUMP_TYPE_MINIMAL; + m_dbgHlp = NULL; + m_pMiniDumpWriteDump = NULL; + m_dumpRequested = NULL; + m_dumpComplete = NULL; + m_quitting = NULL; + m_dumpThread = NULL; + m_dumpThreadId = 0; + m_dumpObjectsState = 0; + m_dumpObjectsSubState = 0; + m_dmaRawBlockIndex = 0; + memset(m_dumpDir, 0, ARRAY_SIZE(m_dumpDir)); + memset(m_dumpFile, 0, ARRAY_SIZE(m_dumpFile)); + memset(m_sysDbgHelpPath, 0, ARRAY_SIZE(m_sysDbgHelpPath)); + memset(m_executablePath, 0, ARRAY_SIZE(m_executablePath)); +}; LONG WINAPI MiniDumper::DumpingExceptionFilter(struct _EXCEPTION_POINTERS* e_info) { @@ -46,7 +88,7 @@ LONG WINAPI MiniDumper::DumpingExceptionFilter(struct _EXCEPTION_POINTERS* e_inf return EXCEPTION_EXECUTE_HANDLER; } -void MiniDumper::TriggerMiniDump(Bool extendedInfo) +void MiniDumper::TriggerMiniDump(DumpType dumpType) { if (!m_miniDumpInitialized) { @@ -57,15 +99,15 @@ void MiniDumper::TriggerMiniDump(Bool extendedInfo) __try { // Use DebugBreak to raise an exception that can be caught in the __except block - DebugBreak(); + ::DebugBreak(); } __except (DumpingExceptionFilter(GetExceptionInformation())) { - TriggerMiniDumpForException(g_dumpException, extendedInfo); + TriggerMiniDumpForException(g_dumpException, dumpType); } } -void MiniDumper::TriggerMiniDumpForException(struct _EXCEPTION_POINTERS* e_info, Bool extendedInfo) +void MiniDumper::TriggerMiniDumpForException(struct _EXCEPTION_POINTERS* e_info, DumpType dumpType) { if (!m_miniDumpInitialized) { @@ -74,54 +116,62 @@ void MiniDumper::TriggerMiniDumpForException(struct _EXCEPTION_POINTERS* e_info, } g_dumpException = e_info; - g_dumpExceptionThreadId = GetCurrentThreadId(); - m_extendedInfoRequested = extendedInfo; + 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 - SetEvent(m_dumpRequested); - DWORD wait = WaitForSingleObject(m_dumpComplete, INFINITE); + 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())); + 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)); + DEBUG_LOG(("MiniDumper::TriggerMiniDumpForException: Waiting for minidump triggering failed: status=%u", wait)); } } - ResetEvent(m_dumpComplete); + ::ResetEvent(m_dumpComplete); } void MiniDumper::Initialize(const AsciiString& userDirPath) { // Find the full path to the dbghelp.dll file in the system32 dir - GetSystemDirectory(m_sysDbgHelpPath, MAX_PATH); + ::GetSystemDirectory(m_sysDbgHelpPath, MAX_PATH); strlcat(m_sysDbgHelpPath, "\\dbghelp.dll", MAX_PATH); // We want to only use the dbghelp.dll from the OS installation, as the one bundled with the game does not support MiniDump functionality Bool loadedDbgHelp = false; - HMODULE m_dbgHlp = GetModuleHandle(m_sysDbgHelpPath); + HMODULE m_dbgHlp = ::GetModuleHandle(m_sysDbgHelpPath); if (m_dbgHlp == NULL) { // Load the dbghelp library from the system folder - m_dbgHlp = LoadLibrary(m_sysDbgHelpPath); + m_dbgHlp = ::LoadLibrary(m_sysDbgHelpPath); if (m_dbgHlp == NULL) { - DEBUG_LOG(("MiniDumper::Initialize: Unable to load system-provided dbghelp.dll from '%s', error code=%u", m_sysDbgHelpPath, GetLastError())); + DEBUG_LOG(("MiniDumper::Initialize: Unable to load system-provided dbghelp.dll from '%s': error=%u", m_sysDbgHelpPath, ::GetLastError())); return; } loadedDbgHelp = true; } - m_pMiniDumpWriteDump = (MiniDumpWriteDump_t)GetProcAddress(m_dbgHlp, "MiniDumpWriteDump"); + m_pMiniDumpWriteDump = reinterpret_cast(::GetProcAddress(m_dbgHlp, "MiniDumpWriteDump")); if (m_pMiniDumpWriteDump == NULL) { if (loadedDbgHelp) { - FreeLibrary(m_dbgHlp); + ::FreeLibrary(m_dbgHlp); m_dbgHlp = NULL; } @@ -129,6 +179,13 @@ void MiniDumper::Initialize(const AsciiString& userDirPath) 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)) { @@ -142,22 +199,22 @@ void MiniDumper::Initialize(const AsciiString& userDirPath) 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())); + DEBUG_LOG(("MiniDumper::Initialize: Unable to create events: error=%u", ::GetLastError())); CleanupResources(); return; } - m_dumpThread = CreateThread(NULL, 0, MiniDumpThreadProc, this, CREATE_SUSPENDED, &m_dumpThreadId); + 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())); + DEBUG_LOG(("MiniDumper::Initialize: Unable to create thread: error=%u", ::GetLastError())); CleanupResources(); return; } - if (!ResumeThread(m_dumpThread)) + if (!::ResumeThread(m_dumpThread)) { - DEBUG_LOG(("MiniDumper::Initialize: Unable to resume thread: error=%u", GetLastError())); + DEBUG_LOG(("MiniDumper::Initialize: Unable to resume thread: error=%u", ::GetLastError())); CleanupResources(); return; } @@ -171,6 +228,17 @@ Bool MiniDumper::IsInitialized() const return m_miniDumpInitialized; } +Bool MiniDumper::IsDumpThreadStillRunning() const +{ + DWORD exitCode; + if (::GetExitCodeThread(m_dumpThread, &exitCode) && exitCode == STILL_ACTIVE) + { + return true; + } + + return false; +} + Bool MiniDumper::InitializeDumpDirectory(const AsciiString& userDirPath) { constexpr Int MaxExtendedFileCount = 2; @@ -178,11 +246,11 @@ Bool MiniDumper::InitializeDumpDirectory(const AsciiString& userDirPath) strlcpy(m_dumpDir, userDirPath.str(), ARRAY_SIZE(m_dumpDir)); strlcat(m_dumpDir, "CrashDumps\\", ARRAY_SIZE(m_dumpDir)); - if (_access(m_dumpDir, 0) != 0) + if (::_access(m_dumpDir, 0) != 0) { - if (!CreateDirectory(m_dumpDir, NULL)) + if (!::CreateDirectory(m_dumpDir, NULL)) { - DEBUG_LOG(("MiniDumper::Initialize: Unable to create path for crash dumps at '%s': %u", m_dumpDir, GetLastError())); + DEBUG_LOG(("MiniDumper::Initialize: Unable to create path for crash dumps at '%s': error=%u", m_dumpDir, ::GetLastError())); return false; } } @@ -196,34 +264,34 @@ Bool MiniDumper::InitializeDumpDirectory(const AsciiString& userDirPath) void MiniDumper::CleanupResources() { - // NOTE: This method should not be called unless the dump thread is confirmed to not be running anymore. if (m_dumpThread != NULL) { - CloseHandle(m_dumpThread); + DEBUG_ASSERTCRASH(!IsDumpThreadStillRunning(), ("MiniDumper::CleanupResources() called while Dump thread still active")); + ::CloseHandle(m_dumpThread); m_dumpThread = NULL; } if (m_dumpComplete != NULL) { - CloseHandle(m_dumpComplete); + ::CloseHandle(m_dumpComplete); m_dumpComplete = NULL; } if (m_dumpRequested != NULL) { - CloseHandle(m_dumpRequested); + ::CloseHandle(m_dumpRequested); m_dumpRequested = NULL; } if (m_quitting != NULL) { - CloseHandle(m_quitting); + ::CloseHandle(m_quitting); m_quitting = NULL; } if (m_dbgHlp != NULL) { - FreeModule(m_dbgHlp); + ::FreeModule(m_dbgHlp); m_dbgHlp = NULL; } } @@ -235,23 +303,24 @@ void MiniDumper::ShutDown() return; } - SetEvent(m_quitting); - DWORD waitRet = WaitForSingleObject(m_dumpThread, 3000); + ::SetEvent(m_quitting); + DWORD waitRet = ::WaitForSingleObject(m_dumpThread, 3000); if (waitRet != WAIT_OBJECT_0) { if (waitRet == WAIT_TIMEOUT) { DEBUG_LOG(("MiniDumper::ShutDown: Waiting for dumping thread to exit timed out, killing thread", waitRet)); - TerminateThread(m_dumpThread, 2); + ::TerminateThread(m_dumpThread, DUMPER_EXIT_FORCED_TERMINATE); } else if (waitRet == WAIT_FAILED) { - DEBUG_LOG(("MiniDumper::ShutDown: Waiting for minidump triggering failed, status=%u, error=%u", waitRet, GetLastError())); + DEBUG_LOG(("MiniDumper::ShutDown: Waiting for minidump triggering failed: status=%u, error=%u", waitRet, ::GetLastError())); } else { - DEBUG_LOG(("MiniDumper::ShutDown: Waiting for minidump triggering failed, status=%u", waitRet)); + DEBUG_LOG(("MiniDumper::ShutDown: Waiting for minidump triggering failed: status=%u", waitRet)); } + return; } @@ -264,31 +333,32 @@ DWORD MiniDumper::ThreadProcInternal() while (true) { HANDLE waitEvents[2] = { m_dumpRequested, m_quitting }; - DWORD event = WaitForMultipleObjects(ARRAY_SIZE(waitEvents), waitEvents, FALSE, INFINITE); + DWORD event = ::WaitForMultipleObjects(ARRAY_SIZE(waitEvents), waitEvents, FALSE, INFINITE); if (event == WAIT_OBJECT_0 + 0) { // A dump is requested (m_dumpRequested) - ResetEvent(m_dumpComplete); - CreateMiniDump(m_extendedInfoRequested); - ResetEvent(m_dumpRequested); - SetEvent(m_dumpComplete); + ::ResetEvent(m_dumpComplete); + CreateMiniDump(m_requestedDumpType); + ::ResetEvent(m_dumpRequested); + ::SetEvent(m_dumpComplete); } else if (event == WAIT_OBJECT_0 + 1) { // Quit (m_quitting) - return 0; + return DUMPER_EXIT_SUCCESS; } else { if (event == WAIT_FAILED) { - DEBUG_LOG(("MiniDumper::ThreadProcInternal: Waiting for events failed, status=%u, error=%u", event, GetLastError())); + DEBUG_LOG(("MiniDumper::ThreadProcInternal: Waiting for events failed: status=%u, error=%u", event, ::GetLastError())); } else { - DEBUG_LOG(("MiniDumper::ThreadProcInternal: Waiting for events failed, status=%u", event)); + DEBUG_LOG(("MiniDumper::ThreadProcInternal: Waiting for events failed: status=%u", event)); } - return 1; + + return DUMPER_EXIT_FAILURE_WAIT; } } } @@ -298,7 +368,7 @@ DWORD WINAPI MiniDumper::MiniDumpThreadProc(LPVOID lpParam) if (lpParam == NULL) { DEBUG_LOG(("MiniDumper::MiniDumpThreadProc: The provided parameter was NULL, exiting thread.")); - return -1; + return DUMPER_EXIT_FAILURE_PARAM; } MiniDumper* dumper = static_cast(lpParam); @@ -306,30 +376,29 @@ DWORD WINAPI MiniDumper::MiniDumpThreadProc(LPVOID lpParam) } -void MiniDumper::CreateMiniDump(Bool extendedInfo) +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_ZEROHOUR - Char product = 'Z'; -#else + ::GetLocalTime(&sysTime); +#if RTS_GENERALS Char product = 'G'; +#elif RTS_ZEROHOUR + Char product = 'Z'; #endif - Char dumpTypeSpecifier = extendedInfo ? 'X' : 'M'; - DWORD currentProcessId = GetCurrentProcessId(); - DWORD currentThreadId = GetCurrentThreadId(); + Char dumpTypeSpecifier = dumpType == DUMP_TYPE_MINIMAL ? 'M' : 'X'; + DWORD currentProcessId = ::GetCurrentProcessId(); // m_dumpDir is stored with trailing backslash in Initialize - snprintf(m_dumpFile, ARRAY_SIZE(m_dumpFile), "%sCrash%c%c-%04d%02d%02d-%02d%02d%02d-%s-%ld-%ld.dmp", + snprintf(m_dumpFile, ARRAY_SIZE(m_dumpFile), "%sCrash%c%c-%04d%02d%02d-%02d%02d%02d-%s-pid%ld.dmp", m_dumpDir, dumpTypeSpecifier, product, sysTime.wYear, sysTime.wMonth, sysTime.wDay, sysTime.wHour, sysTime.wMinute, sysTime.wSecond, - GitShortSHA1, currentProcessId, currentThreadId); + 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())); + DEBUG_LOG(("MiniDumper::CreateMiniDump: Unable to create dump file '%s': error=%u", m_dumpFile, ::GetLastError())); return; } @@ -345,45 +414,51 @@ void MiniDumper::CreateMiniDump(Bool extendedInfo) PMINIDUMP_CALLBACK_INFORMATION callbackInfoPtr = NULL; MINIDUMP_CALLBACK_INFORMATION callBackInfo = { 0 }; - if (extendedInfo) + if (dumpType == DUMP_TYPE_GAMEMEMORY) { callBackInfo.CallbackRoutine = MiniDumpCallback; callBackInfo.CallbackParam = this; callbackInfoPtr = &callBackInfo; } - MINIDUMP_TYPE dumpType = static_cast(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory); - if (extendedInfo) + int dumpTypeFlags = MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory; + if (dumpType != DUMP_TYPE_MINIMAL) { - dumpType = static_cast(MiniDumpWithDataSegs | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpScanMemory | MiniDumpWithIndirectlyReferencedMemory | MiniDumpWithFullMemoryInfo); + dumpTypeFlags |= MiniDumpWithDataSegs | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithFullMemoryInfo; + if (dumpType == DUMP_TYPE_FULL) + { + dumpTypeFlags |= MiniDumpWithFullMemory; + } } + MINIDUMP_TYPE miniDumpType = static_cast(dumpTypeFlags); BOOL success = m_pMiniDumpWriteDump( - GetCurrentProcess(), + ::GetCurrentProcess(), currentProcessId, dumpFile, - dumpType, + miniDumpType, exceptionInfoPtr, NULL, callbackInfoPtr); if (!success) { - DEBUG_LOG(("MiniDumper::CreateMiniDump: Unable to write minidump file '%s', error=%u", m_dumpFile, GetLastError())); + 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); + ::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)); + DEBUG_LOG(("MiniDumper::MiniDumpCallback: Required parameters were null; CallbackParam=%p, CallbackInput=%p, CallbackOutput=%p.", + CallbackParam, CallbackInput, CallbackOutput)); return false; } @@ -405,7 +480,7 @@ BOOL MiniDumper::CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP // Only include data segments for the game and ntdll modules to keep dump size low if (output.ModuleWriteFlags & ModuleWriteDataSeg) { - if (!StrStrIW(input.Module.FullPath, L"generalszh.exe") && !StrStrIW(input.Module.FullPath, L"generalsv.exe") && !StrStrIW(input.Module.FullPath, L"ntdll.dll")) + if (::StrCmpIW(input.Module.FullPath, m_executablePath) != 0 && !::StrStrIW(input.Module.FullPath, L"ntdll.dll")) { // Exclude data segments for the module output.ModuleWriteFlags &= (~ModuleWriteDataSeg); @@ -430,16 +505,21 @@ BOOL MiniDumper::CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP 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)); + DEBUG_LOG(("MiniDumper::CallbackInternal: ReadMemoryFailure with MemoryBase=%llu, MemorySize=%lu: error=%u", + input.ReadMemoryFailure.Offset, input.ReadMemoryFailure.Bytes, input.ReadMemoryFailure.FailureStatus)); retVal = TRUE; break; } @@ -507,18 +587,17 @@ BOOL MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) //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_endRangeIter = TheMemoryPoolFactory->cend(); + m_rangeIter = TheMemoryPoolFactory->cbegin(); ++m_dumpObjectsSubState; } - // m_RangeIter should != m_endRangeIter, unless the memory pool factory is corrupted (or has 0 entries) - memoryBase = reinterpret_cast(m_RangeIter->allocationAddr); - memorySize = m_RangeIter->allocationSize; + // 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; + ++m_rangeIter; - if (m_RangeIter == m_endRangeIter) + if (m_rangeIter == TheMemoryPoolFactory->cend()) { ++m_dumpObjectsState; m_dumpObjectsSubState = 0; @@ -583,8 +662,9 @@ BOOL MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) } // 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; +bool MiniDumper::CompareByLastWriteTime(const FileInfo& a, const FileInfo& b) +{ + return ::CompareFileTime(&a.lastWriteTime, &b.lastWriteTime) > 0; } void MiniDumper::KeepNewestFiles(const std::string& directory, const std::string& fileWildcard, const Int keepCount) @@ -592,20 +672,23 @@ void MiniDumper::KeepNewestFiles(const std::string& directory, const std::string // directory already contains trailing backslash std::string searchPath = directory + fileWildcard; WIN32_FIND_DATA findData; - HANDLE hFind = FindFirstFile(searchPath.c_str(), &findData); + HANDLE hFind = ::FindFirstFile(searchPath.c_str(), &findData); - if (hFind == INVALID_HANDLE_VALUE) { - if (GetLastError() != ERROR_FILE_NOT_FOUND) + if (hFind == INVALID_HANDLE_VALUE) + { + if (::GetLastError() != ERROR_FILE_NOT_FOUND) { - DEBUG_LOG(("MiniDumper::KeepNewestFiles: Unable to find files in directory '%s': %u", searchPath.c_str(), GetLastError())); + 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) { + do + { + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { continue; } @@ -615,20 +698,23 @@ void MiniDumper::KeepNewestFiles(const std::string& directory, const std::string fileInfo.lastWriteTime = findData.ftLastWriteTime; files.push_back(fileInfo); - } while (FindNextFile(hFind, &findData)); + } while (::FindNextFile(hFind, &findData)); - FindClose(hFind); + ::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())) { + 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())); + else + { + DEBUG_LOG(("MiniDumper::KeepNewestFiles: Failed to delete file '%s': error=%u", files[i].name.c_str(), ::GetLastError())); } } } diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index 16a02fbcf2..88c13cb6ef 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -74,9 +74,6 @@ HINSTANCE ApplicationHInstance = NULL; ///< our application instance HWND ApplicationHWnd = NULL; ///< our application window handle Win32Mouse *TheWin32Mouse= NULL; ///< for the WndProc() only DWORD TheMessageTime = 0; ///< For getting the time that a message was posted from Windows. -#ifdef RTS_ENABLE_CRASHDUMP -extern MiniDumper TheMiniDumper; -#endif const Char *g_strFile = "data\\Generals.str"; const Char *g_csfFile = "data\\%s\\Generals.csf"; @@ -748,12 +745,12 @@ static LONG WINAPI UnHandledExceptionFilter( struct _EXCEPTION_POINTERS* e_info { DumpExceptionInfo( e_info->ExceptionRecord->ExceptionCode, e_info ); #ifdef RTS_ENABLE_CRASHDUMP - if (TheMiniDumper.IsInitialized()) + if (TheMiniDumper && TheMiniDumper->IsInitialized()) { // Do dumps both with and without extended info - TheMiniDumper.TriggerMiniDumpForException(e_info, false); - TheMiniDumper.TriggerMiniDumpForException(e_info, true); - TheMiniDumper.ShutDown(); + TheMiniDumper->TriggerMiniDumpForException(e_info, DUMP_TYPE_MINIMAL); + TheMiniDumper->TriggerMiniDumpForException(e_info, DUMP_TYPE_GAMEMEMORY); + MiniDumper::shutdownMiniDumper(); } #endif return EXCEPTION_EXECUTE_HANDLER; @@ -824,7 +821,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, #ifdef RTS_ENABLE_CRASHDUMP // Initialize minidump facilities - requires TheGlobalData so performed after parseCommandLineForStartup - TheMiniDumper.Initialize(TheGlobalData->getPath_UserData()); + MiniDumper::initMiniDumper(TheGlobalData->getPath_UserData()); #endif // register windows class and create application window if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) @@ -894,7 +891,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, } #ifdef RTS_ENABLE_CRASHDUMP - TheMiniDumper.ShutDown(); + MiniDumper::shutdownMiniDumper(); #endif TheAsciiStringCriticalSection = NULL; TheUnicodeStringCriticalSection = NULL; diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 0d58c2339c..3a86816a19 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -77,9 +77,6 @@ HINSTANCE ApplicationHInstance = NULL; ///< our application instance HWND ApplicationHWnd = NULL; ///< our application window handle Win32Mouse *TheWin32Mouse= NULL; ///< for the WndProc() only DWORD TheMessageTime = 0; ///< For getting the time that a message was posted from Windows. -#ifdef RTS_ENABLE_CRASHDUMP -extern MiniDumper TheMiniDumper; -#endif const Char *g_strFile = "data\\Generals.str"; const Char *g_csfFile = "data\\%s\\Generals.csf"; @@ -770,12 +767,12 @@ static LONG WINAPI UnHandledExceptionFilter( struct _EXCEPTION_POINTERS* e_info { DumpExceptionInfo( e_info->ExceptionRecord->ExceptionCode, e_info ); #ifdef RTS_ENABLE_CRASHDUMP - if (TheMiniDumper.IsInitialized()) + if (TheMiniDumper && TheMiniDumper->IsInitialized()) { // Do dumps both with and without extended info - TheMiniDumper.TriggerMiniDumpForException(e_info, false); - TheMiniDumper.TriggerMiniDumpForException(e_info, true); - TheMiniDumper.ShutDown(); + TheMiniDumper->TriggerMiniDumpForException(e_info, DUMP_TYPE_MINIMAL); + TheMiniDumper->TriggerMiniDumpForException(e_info, DUMP_TYPE_GAMEMEMORY); + MiniDumper::shutdownMiniDumper(); } #endif return EXCEPTION_EXECUTE_HANDLER; @@ -870,7 +867,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, CommandLine::parseCommandLineForStartup(); #ifdef RTS_ENABLE_CRASHDUMP // Initialize minidump facilities - requires TheGlobalData so performed after parseCommandLineForStartup - TheMiniDumper.Initialize(TheGlobalData->getPath_UserData()); + MiniDumper::initMiniDumper(TheGlobalData->getPath_UserData()); #endif // register windows class and create application window @@ -942,7 +939,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, } #ifdef RTS_ENABLE_CRASHDUMP - TheMiniDumper.ShutDown(); + MiniDumper::shutdownMiniDumper(); #endif TheUnicodeStringCriticalSection = NULL; TheDmaCriticalSection = NULL; diff --git a/cmake/config-memory.cmake b/cmake/config-memory.cmake index ae5f61d0f8..09d8732e8b 100644 --- a/cmake/config-memory.cmake +++ b/cmake/config-memory.cmake @@ -20,8 +20,8 @@ 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 (depends on original game memory implementation) -cmake_dependent_option(RTS_CRASHDUMP_ENABLE "Enables writing crash dumps on unhandled exceptions or release crash failures." ON RTS_GAMEMEMORY_ENABLE 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") From 0216b551b1b56fdd921097a2cc1e76b565ce6c67 Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Wed, 15 Oct 2025 11:04:46 +0200 Subject: [PATCH 3/6] Fix build break when game memory is disabled --- Core/GameEngine/Include/Common/MiniDumper.h | 2 ++ Core/GameEngine/Source/Common/System/MiniDumper.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Core/GameEngine/Include/Common/MiniDumper.h b/Core/GameEngine/Include/Common/MiniDumper.h index 9a519acb73..5a7b21a56b 100644 --- a/Core/GameEngine/Include/Common/MiniDumper.h +++ b/Core/GameEngine/Include/Common/MiniDumper.h @@ -102,12 +102,14 @@ class MiniDumper 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 // Function pointer to MiniDumpWriteDump in dbghelp.dll typedef BOOL(WINAPI* MiniDumpWriteDump_t)( diff --git a/Core/GameEngine/Source/Common/System/MiniDumper.cpp b/Core/GameEngine/Source/Common/System/MiniDumper.cpp index 0bf978b097..cee264a893 100644 --- a/Core/GameEngine/Source/Common/System/MiniDumper.cpp +++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp @@ -67,9 +67,11 @@ MiniDumper::MiniDumper() m_quitting = NULL; m_dumpThread = NULL; m_dumpThreadId = 0; +#ifndef DISABLE_GAMEMEMORY m_dumpObjectsState = 0; m_dumpObjectsSubState = 0; m_dmaRawBlockIndex = 0; +#endif memset(m_dumpDir, 0, ARRAY_SIZE(m_dumpDir)); memset(m_dumpFile, 0, ARRAY_SIZE(m_dumpFile)); memset(m_sysDbgHelpPath, 0, ARRAY_SIZE(m_sysDbgHelpPath)); @@ -533,6 +535,7 @@ BOOL MiniDumper::CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP return retVal; } +#ifndef DISABLE_GAMEMEMORY BOOL MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) { BOOL moreToDo = TRUE; @@ -660,6 +663,7 @@ BOOL MiniDumper::DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize) return moreToDo; } +#endif // Comparator for sorting files by last modified time (newest first) bool MiniDumper::CompareByLastWriteTime(const FileInfo& a, const FileInfo& b) From a5a241951a166d4eff98c207344a38897dfdcc29 Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Mon, 3 Nov 2025 20:45:42 +0100 Subject: [PATCH 4/6] Review feedback --- .../Source/Common/System/MiniDumper.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Core/GameEngine/Source/Common/System/MiniDumper.cpp b/Core/GameEngine/Source/Common/System/MiniDumper.cpp index cee264a893..a1d72fbffc 100644 --- a/Core/GameEngine/Source/Common/System/MiniDumper.cpp +++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp @@ -423,14 +423,18 @@ void MiniDumper::CreateMiniDump(DumpType dumpType) callbackInfoPtr = &callBackInfo; } - int dumpTypeFlags = MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory; - if (dumpType != DUMP_TYPE_MINIMAL) + int dumpTypeFlags = MiniDumpNormal; + switch (dumpType) { + case DUMP_TYPE_FULL: + dumpTypeFlags |= MiniDumpWithFullMemory; + FALLTHROUGH; + case DUMP_TYPE_GAMEMEMORY: dumpTypeFlags |= MiniDumpWithDataSegs | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithFullMemoryInfo; - if (dumpType == DUMP_TYPE_FULL) - { - dumpTypeFlags |= MiniDumpWithFullMemory; - } + FALLTHROUGH; + case DUMP_TYPE_MINIMAL: + dumpTypeFlags |= MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory; + break; } MINIDUMP_TYPE miniDumpType = static_cast(dumpTypeFlags); @@ -482,7 +486,7 @@ BOOL MiniDumper::CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP // Only include data segments for the game and ntdll modules to keep dump size low if (output.ModuleWriteFlags & ModuleWriteDataSeg) { - if (::StrCmpIW(input.Module.FullPath, m_executablePath) != 0 && !::StrStrIW(input.Module.FullPath, L"ntdll.dll")) + if (!::StrCmpIW(input.Module.FullPath, m_executablePath) && !::StrStrIW(input.Module.FullPath, L"ntdll.dll")) { // Exclude data segments for the module output.ModuleWriteFlags &= (~ModuleWriteDataSeg); From 90a0b491bd2efa89a9deb54441633b355e1d1023 Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Tue, 4 Nov 2025 20:38:19 +0100 Subject: [PATCH 5/6] Moved dbghelp interaction to DbgHelpLoader --- Core/GameEngine/CMakeLists.txt | 1 - Core/GameEngine/Include/Common/MiniDumper.h | 20 +-------- .../Source/Common/System/MiniDumper.cpp | 41 +++---------------- .../Source/WWVegas/WWLib/CMakeLists.txt | 1 + .../Source/WWVegas/WWLib/DbgHelpLoader.cpp | 21 +++++++++- .../Source/WWVegas/WWLib/DbgHelpLoader.h | 28 +++++++++++++ .../WWVegas/WWLib/DbgHelpLoader_minidump.h} | 5 ++- 7 files changed, 58 insertions(+), 59 deletions(-) rename Core/{GameEngine/Include/Common/MiniDumper_compat.h => Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h} (99%) diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index d586b8db2c..cb003b39a0 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -72,7 +72,6 @@ set(GAMEENGINE_SRC # Include/Common/MapReaderWriterInfo.h # Include/Common/MessageStream.h Include/Common/MiniDumper.h - Include/Common/MiniDumper_compat.h # Include/Common/MiniLog.h Include/Common/MiscAudio.h # Include/Common/MissionStats.h diff --git a/Core/GameEngine/Include/Common/MiniDumper.h b/Core/GameEngine/Include/Common/MiniDumper.h index 5a7b21a56b..821ffefa9f 100644 --- a/Core/GameEngine/Include/Common/MiniDumper.h +++ b/Core/GameEngine/Include/Common/MiniDumper.h @@ -19,8 +19,7 @@ #pragma once #ifdef RTS_ENABLE_CRASHDUMP -#include -#include "Common/MiniDumper_compat.h" +#include "DbgHelpLoader.h" enum DumpType CPP_11(: Int) { @@ -87,12 +86,8 @@ class MiniDumper // Path buffers Char m_dumpDir[MAX_PATH]; Char m_dumpFile[MAX_PATH]; - Char m_sysDbgHelpPath[MAX_PATH]; WideChar m_executablePath[MAX_PATH]; - // Module handles - HMODULE m_dbgHlp; - // Event handles HANDLE m_dumpRequested; HANDLE m_dumpComplete; @@ -110,19 +105,6 @@ class MiniDumper AllocationRangeIterator m_rangeIter; #endif - - // Function pointer to MiniDumpWriteDump in dbghelp.dll - 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 - ); - - MiniDumpWriteDump_t m_pMiniDumpWriteDump; }; extern MiniDumper* TheMiniDumper; diff --git a/Core/GameEngine/Source/Common/System/MiniDumper.cpp b/Core/GameEngine/Source/Common/System/MiniDumper.cpp index a1d72fbffc..f8e675ea91 100644 --- a/Core/GameEngine/Source/Common/System/MiniDumper.cpp +++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp @@ -60,8 +60,6 @@ MiniDumper::MiniDumper() { m_miniDumpInitialized = false; m_requestedDumpType = DUMP_TYPE_MINIMAL; - m_dbgHlp = NULL; - m_pMiniDumpWriteDump = NULL; m_dumpRequested = NULL; m_dumpComplete = NULL; m_quitting = NULL; @@ -74,7 +72,6 @@ MiniDumper::MiniDumper() #endif memset(m_dumpDir, 0, ARRAY_SIZE(m_dumpDir)); memset(m_dumpFile, 0, ARRAY_SIZE(m_dumpFile)); - memset(m_sysDbgHelpPath, 0, ARRAY_SIZE(m_sysDbgHelpPath)); memset(m_executablePath, 0, ARRAY_SIZE(m_executablePath)); }; @@ -148,36 +145,12 @@ void MiniDumper::TriggerMiniDumpForException(struct _EXCEPTION_POINTERS* e_info, void MiniDumper::Initialize(const AsciiString& userDirPath) { - // Find the full path to the dbghelp.dll file in the system32 dir - ::GetSystemDirectory(m_sysDbgHelpPath, MAX_PATH); - strlcat(m_sysDbgHelpPath, "\\dbghelp.dll", MAX_PATH); + bool success = 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 - Bool loadedDbgHelp = false; - HMODULE m_dbgHlp = ::GetModuleHandle(m_sysDbgHelpPath); - if (m_dbgHlp == NULL) + if (!(success && DbgHelpLoader::isLoadedFromSystem())) { - // Load the dbghelp library from the system folder - m_dbgHlp = ::LoadLibrary(m_sysDbgHelpPath); - if (m_dbgHlp == NULL) - { - DEBUG_LOG(("MiniDumper::Initialize: Unable to load system-provided dbghelp.dll from '%s': error=%u", m_sysDbgHelpPath, ::GetLastError())); - return; - } - - loadedDbgHelp = true; - } - - m_pMiniDumpWriteDump = reinterpret_cast(::GetProcAddress(m_dbgHlp, "MiniDumpWriteDump")); - if (m_pMiniDumpWriteDump == NULL) - { - if (loadedDbgHelp) - { - ::FreeLibrary(m_dbgHlp); - m_dbgHlp = NULL; - } - - DEBUG_LOG(("MiniDumper::Initialize: Could not get address of proc MiniDumpWriteDump from '%s'!", m_sysDbgHelpPath)); + DEBUG_LOG(("MiniDumper::Initialize: Unable to load system-provided dbghelp.dll, minidump functionality disabled.")); return; } @@ -291,11 +264,7 @@ void MiniDumper::CleanupResources() m_quitting = NULL; } - if (m_dbgHlp != NULL) - { - ::FreeModule(m_dbgHlp); - m_dbgHlp = NULL; - } + DbgHelpLoader::unload(); } void MiniDumper::ShutDown() @@ -438,7 +407,7 @@ void MiniDumper::CreateMiniDump(DumpType dumpType) } MINIDUMP_TYPE miniDumpType = static_cast(dumpTypeFlags); - BOOL success = m_pMiniDumpWriteDump( + BOOL success = DbgHelpLoader::miniDumpWriteDump( ::GetCurrentProcess(), currentProcessId, dumpFile, diff --git a/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt index 3eec2f42f8..997a6c0242 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 b5bcef62f4..cf272f4d49 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp +++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp @@ -22,7 +22,8 @@ DbgHelpLoader* DbgHelpLoader::Inst = NULL; DbgHelpLoader::DbgHelpLoader() - : m_symInitialize(NULL) + : m_miniDumpWriteDump(NULL) + , m_symInitialize(NULL) , m_symCleanup(NULL) , m_symLoadModule(NULL) , m_symUnloadModule(NULL) @@ -86,6 +87,7 @@ bool DbgHelpLoader::load() Inst->m_loadedFromSystem = true; } + Inst->m_miniDumpWriteDump = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "MiniDumpWriteDump")); Inst->m_symInitialize = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymInitialize")); Inst->m_symCleanup = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymCleanup")); Inst->m_symLoadModule = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymLoadModule")); @@ -134,6 +136,23 @@ void DbgHelpLoader::unload() Inst = NULL; } +#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) +{ + if (Inst != NULL && Inst->m_miniDumpWriteDump) + return Inst->m_miniDumpWriteDump(hProcess, ProcessId, hFile, DumpType, ExceptionParam, UserStreamParam, CallbackParam); + + return FALSE; +} +#endif + BOOL DbgHelpLoader::symInitialize( HANDLE hProcess, LPSTR UserSearchPath, diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h index 7d83da31e5..defbfc692c 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 "SystemAllocator.h" @@ -50,6 +53,17 @@ class DbgHelpLoader static bool reload(); static void unload(); +#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 + static BOOL WINAPI symInitialize( HANDLE hProcess, LPSTR UserSearchPath, @@ -106,6 +120,17 @@ class DbgHelpLoader private: +#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, LPSTR UserSearchPath, @@ -160,6 +185,9 @@ class DbgHelpLoader PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE TranslateAddress); +#ifdef RTS_ENABLE_CRASHDUMP + MiniDumpWriteDump_t m_miniDumpWriteDump; +#endif SymInitialize_t m_symInitialize; SymCleanup_t m_symCleanup; SymLoadModule_t m_symLoadModule; diff --git a/Core/GameEngine/Include/Common/MiniDumper_compat.h b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h similarity index 99% rename from Core/GameEngine/Include/Common/MiniDumper_compat.h rename to Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h index ab28931418..a882dbd670 100644 --- a/Core/GameEngine/Include/Common/MiniDumper_compat.h +++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h @@ -4,8 +4,9 @@ // Backported defines from minidumpapiset.h for VC6. // minidumpapiset.h is Copyright (C) Microsoft Corporation. All rights reserved. + #if defined(_MSC_VER) && _MSC_VER < 1300 -#pragma pack(push, 4) +#include typedef enum _MINIDUMP_CALLBACK_TYPE { ModuleCallback, @@ -249,6 +250,6 @@ typedef enum _MODULE_WRITE_FLAGS { ModuleWriteCodeSegs = 0x0040, } MODULE_WRITE_FLAGS; -#pragma pack(pop) +#include #endif #endif From f96f2d4bc01a01f29de40ea2303e78026fdc3cc3 Mon Sep 17 00:00:00 2001 From: Slurmlord Date: Tue, 4 Nov 2025 21:34:59 +0100 Subject: [PATCH 6/6] Fix build break when crashdumps are disabled --- Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp index cf272f4d49..f17b74f07b 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp +++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp @@ -22,8 +22,10 @@ DbgHelpLoader* DbgHelpLoader::Inst = NULL; DbgHelpLoader::DbgHelpLoader() - : m_miniDumpWriteDump(NULL) - , m_symInitialize(NULL) + : m_symInitialize(NULL) +#ifdef RTS_ENABLE_CRASHDUMP + , m_miniDumpWriteDump(NULL) +#endif , m_symCleanup(NULL) , m_symLoadModule(NULL) , m_symUnloadModule(NULL) @@ -87,7 +89,9 @@ bool DbgHelpLoader::load() Inst->m_loadedFromSystem = true; } +#ifdef RTS_ENABLE_CRASHDUMP Inst->m_miniDumpWriteDump = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "MiniDumpWriteDump")); +#endif Inst->m_symInitialize = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymInitialize")); Inst->m_symCleanup = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymCleanup")); Inst->m_symLoadModule = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymLoadModule"));