Skip to content

Commit a241b80

Browse files
committed
feat(crashdump): Add crash dump functionality
1 parent dd51b7e commit a241b80

File tree

13 files changed

+1427
-4
lines changed

13 files changed

+1427
-4
lines changed

Core/GameEngine/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ set(GAMEENGINE_SRC
7272
# Include/Common/MapObject.h
7373
# Include/Common/MapReaderWriterInfo.h
7474
# Include/Common/MessageStream.h
75+
Include/Common/MiniDumper.h
7576
# Include/Common/MiniLog.h
7677
Include/Common/MiscAudio.h
7778
# Include/Common/MissionStats.h
@@ -660,6 +661,7 @@ set(GAMEENGINE_SRC
660661
# Source/Common/System/List.cpp
661662
Source/Common/System/LocalFile.cpp
662663
Source/Common/System/LocalFileSystem.cpp
664+
Source/Common/System/MiniDumper.cpp
663665
Source/Common/System/ObjectStatusTypes.cpp
664666
# Source/Common/System/QuotedPrintable.cpp
665667
Source/Common/System/Radar.cpp

Core/GameEngine/Include/Common/GameMemory.h

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ class MemoryPool;
223223
class MemoryPoolFactory;
224224
class DynamicMemoryAllocator;
225225
class BlockCheckpointInfo;
226+
#ifdef RTS_ENABLE_CRASHDUMP
227+
class AllocationRangeIterator;
228+
#endif
226229

227230
// TYPE DEFINES ///////////////////////////////////////////////////////////////
228231

@@ -279,6 +282,14 @@ class Checkpointable
279282
};
280283
#endif
281284

285+
#ifdef RTS_ENABLE_CRASHDUMP
286+
struct MemoryPoolAllocatedRange
287+
{
288+
char* allocationAddr;
289+
size_t allocationSize;
290+
};
291+
#endif
292+
282293
// ----------------------------------------------------------------------------
283294
/**
284295
A MemoryPool provides a way to efficiently allocate objects of the same (or similar)
@@ -384,6 +395,9 @@ class MemoryPool
384395
/// return true iff this block was allocated by this pool.
385396
Bool debugIsBlockInPool(void *pBlock);
386397
#endif
398+
#ifdef RTS_ENABLE_CRASHDUMP
399+
friend class AllocationRangeIterator;
400+
#endif
387401
};
388402

389403
// ----------------------------------------------------------------------------
@@ -474,13 +488,80 @@ class DynamicMemoryAllocator
474488
Bool debugIsPoolInDma(MemoryPool *pool);
475489

476490
#endif // MEMORYPOOL_DEBUG
491+
#ifdef RTS_ENABLE_CRASHDUMP
492+
Int getRawBlockCount() const;
493+
void fillAllocationRangeForRawBlockN(const Int n, MemoryPoolAllocatedRange& allocationRange) const;
494+
#endif
477495
};
478496

479497
// ----------------------------------------------------------------------------
480498
#ifdef MEMORYPOOL_DEBUG
481499
enum { MAX_SPECIAL_USED = 256 };
482500
#endif
483501

502+
#ifdef RTS_ENABLE_CRASHDUMP
503+
class AllocationRangeIterator
504+
{
505+
typedef const MemoryPoolAllocatedRange value_type;
506+
typedef const MemoryPoolAllocatedRange* pointer;
507+
typedef const MemoryPoolAllocatedRange& reference;
508+
509+
public:
510+
511+
AllocationRangeIterator(const MemoryPoolFactory* factory);
512+
AllocationRangeIterator(MemoryPool& pool, MemoryPoolBlob& blob)
513+
{
514+
m_currentPool = &pool;
515+
m_currentBlobInPool = &blob;
516+
m_factory = NULL;
517+
m_range = MemoryPoolAllocatedRange();
518+
};
519+
520+
AllocationRangeIterator(MemoryPool* pool, MemoryPoolBlob* blob)
521+
{
522+
m_currentPool = pool;
523+
m_currentBlobInPool = blob;
524+
m_factory = NULL;
525+
m_range = MemoryPoolAllocatedRange();
526+
};
527+
528+
AllocationRangeIterator()
529+
{
530+
m_currentPool = NULL;
531+
m_currentBlobInPool = NULL;
532+
m_factory = NULL;
533+
m_range = MemoryPoolAllocatedRange();
534+
};
535+
536+
reference operator*() { UpdateRange(); return m_range; }
537+
pointer operator->() { UpdateRange(); return &m_range; }
538+
539+
// Prefix increment
540+
AllocationRangeIterator& operator++() { MoveToNextBlob(); return *this; }
541+
542+
// Postfix increment
543+
AllocationRangeIterator operator++(int) { AllocationRangeIterator tmp = *this; ++(*this); return tmp; }
544+
545+
friend const bool operator== (const AllocationRangeIterator& a, const AllocationRangeIterator& b)
546+
{
547+
return a.m_currentBlobInPool == b.m_currentBlobInPool;
548+
};
549+
550+
friend const bool operator!= (const AllocationRangeIterator& a, const AllocationRangeIterator& b)
551+
{
552+
return a.m_currentBlobInPool != b.m_currentBlobInPool;
553+
};
554+
555+
private:
556+
const MemoryPoolFactory* m_factory;
557+
MemoryPool* m_currentPool;
558+
MemoryPoolBlob* m_currentBlobInPool;
559+
MemoryPoolAllocatedRange m_range;
560+
void UpdateRange();
561+
void MoveToNextBlob();
562+
};
563+
#endif
564+
484565
// ----------------------------------------------------------------------------
485566
/**
486567
The class that manages all the MemoryPools and DynamicMemoryAllocators.
@@ -573,6 +654,21 @@ class MemoryPoolFactory
573654
void debugResetCheckpoints();
574655

575656
#endif
657+
#ifdef RTS_ENABLE_CRASHDUMP
658+
AllocationRangeIterator cbegin() const
659+
{
660+
return AllocationRangeIterator(this);
661+
}
662+
663+
AllocationRangeIterator cend() const
664+
{
665+
return AllocationRangeIterator(NULL, NULL);
666+
}
667+
668+
Int getMemoryPoolCount() const;
669+
MemoryPool* getMemoryPoolN(const Int n) const;
670+
friend class AllocationRangeIterator;
671+
#endif
576672
};
577673

578674
// how many bytes are we allowed to 'waste' per pool allocation before the debug code starts yelling at us...
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 TheSuperHackers
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#pragma once
20+
21+
#ifdef RTS_ENABLE_CRASHDUMP
22+
#include "DbgHelpLoader.h"
23+
24+
enum DumpType CPP_11(: Char)
25+
{
26+
// Smallest dump type with call stacks and some supporting variables
27+
DUMP_TYPE_MINIMAL = 'M',
28+
// Large dump including all memory regions allocated by the GameMemory implementaion
29+
DUMP_TYPE_GAMEMEMORY = 'X',
30+
// Largest dump size including complete memory contents of the process
31+
DUMP_TYPE_FULL = 'F',
32+
};
33+
34+
enum MiniDumperExitCode CPP_11(: Int)
35+
{
36+
DUMPER_EXIT_SUCCESS = 0x0,
37+
DUMPER_EXIT_FAILURE_WAIT = 0x37DA1040,
38+
DUMPER_EXIT_FAILURE_PARAM = 0x4EA527BB,
39+
DUMPER_EXIT_FORCED_TERMINATE = 0x158B1154,
40+
};
41+
42+
class MiniDumper
43+
{
44+
public:
45+
MiniDumper();
46+
Bool IsInitialized() const;
47+
void TriggerMiniDump(DumpType dumpType);
48+
void TriggerMiniDumpForException(_EXCEPTION_POINTERS* e_info, DumpType dumpType);
49+
static void initMiniDumper(const AsciiString& userDirPath);
50+
static void shutdownMiniDumper();
51+
static LONG WINAPI DumpingExceptionFilter(_EXCEPTION_POINTERS* e_info);
52+
53+
private:
54+
void Initialize(const AsciiString& userDirPath);
55+
void ShutDown();
56+
void CreateMiniDump(DumpType dumpType);
57+
BOOL DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize);
58+
void CleanupResources();
59+
Bool IsDumpThreadStillRunning() const;
60+
void ShutdownDumpThread();
61+
Bool ShouldWriteDataSegsForModule(const PWCHAR module) const;
62+
63+
// Callbacks from dbghelp
64+
static BOOL CALLBACK MiniDumpCallback(PVOID CallbackParam, PMINIDUMP_CALLBACK_INPUT CallbackInput, PMINIDUMP_CALLBACK_OUTPUT CallbackOutput);
65+
BOOL CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP_CALLBACK_OUTPUT& output);
66+
67+
// Thread procs
68+
static DWORD WINAPI MiniDumpThreadProc(LPVOID lpParam);
69+
DWORD ThreadProcInternal();
70+
71+
// Dump file directory bookkeeping
72+
Bool InitializeDumpDirectory(const AsciiString& userDirPath);
73+
static void KeepNewestFiles(const std::string& directory, const DumpType dumpType, const Int keepCount);
74+
75+
// Struct to hold file information
76+
struct FileInfo
77+
{
78+
std::string name;
79+
FILETIME lastWriteTime;
80+
};
81+
82+
static bool CompareByLastWriteTime(const FileInfo& a, const FileInfo& b);
83+
84+
private:
85+
Bool m_miniDumpInitialized;
86+
Bool m_loadedDbgHelp;
87+
DumpType m_requestedDumpType;
88+
89+
// Path buffers
90+
Char m_dumpDir[MAX_PATH];
91+
Char m_dumpFile[MAX_PATH];
92+
WideChar m_executablePath[MAX_PATH];
93+
94+
// Event handles
95+
HANDLE m_dumpRequested;
96+
HANDLE m_dumpComplete;
97+
HANDLE m_quitting;
98+
99+
// Thread handles
100+
HANDLE m_dumpThread;
101+
DWORD m_dumpThreadId;
102+
103+
#ifndef DISABLE_GAMEMEMORY
104+
// Internal memory dumping progress state
105+
int m_dumpObjectsState;
106+
int m_dumpObjectsSubState;
107+
int m_dmaRawBlockIndex;
108+
109+
AllocationRangeIterator m_rangeIter;
110+
#endif
111+
};
112+
113+
extern MiniDumper* TheMiniDumper;
114+
#endif

Core/GameEngine/Source/Common/System/Debug.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@
7070
#if defined(DEBUG_STACKTRACE) || defined(IG_DEBUG_STACKTRACE)
7171
#include "Common/StackDump.h"
7272
#endif
73+
#ifdef RTS_ENABLE_CRASHDUMP
74+
#include "Common/MiniDumper.h"
75+
#endif
7376

7477
// Horrible reference, but we really, really need to know if we are windowed.
7578
extern bool DX8Wrapper_IsWindowed;
@@ -727,6 +730,22 @@ double SimpleProfiler::getAverageTime()
727730
}
728731
}
729732

733+
734+
static void TriggerMiniDump()
735+
{
736+
#ifdef RTS_ENABLE_CRASHDUMP
737+
if (TheMiniDumper && TheMiniDumper->IsInitialized())
738+
{
739+
// Do dumps both with and without extended info
740+
TheMiniDumper->TriggerMiniDump(DUMP_TYPE_MINIMAL);
741+
TheMiniDumper->TriggerMiniDump(DUMP_TYPE_GAMEMEMORY);
742+
}
743+
744+
MiniDumper::shutdownMiniDumper();
745+
#endif
746+
}
747+
748+
730749
void ReleaseCrash(const char *reason)
731750
{
732751
/// do additional reporting on the crash, if possible
@@ -737,6 +756,8 @@ void ReleaseCrash(const char *reason)
737756
}
738757
}
739758

759+
TriggerMiniDump();
760+
740761
char prevbuf[ _MAX_PATH ];
741762
char curbuf[ _MAX_PATH ];
742763

@@ -813,6 +834,8 @@ void ReleaseCrashLocalized(const AsciiString& p, const AsciiString& m)
813834
return;
814835
}
815836

837+
TriggerMiniDump();
838+
816839
UnicodeString prompt = TheGameText->fetch(p);
817840
UnicodeString mesg = TheGameText->fetch(m);
818841

0 commit comments

Comments
 (0)