Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Core/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ set(GAMEENGINE_SRC
# Include/Common/MapObject.h
# Include/Common/MapReaderWriterInfo.h
# Include/Common/MessageStream.h
Include/Common/MiniDumper.h
# Include/Common/MiniLog.h
Include/Common/MiscAudio.h
# Include/Common/MissionStats.h
Expand Down Expand Up @@ -660,6 +661,7 @@ set(GAMEENGINE_SRC
# Source/Common/System/List.cpp
Source/Common/System/LocalFile.cpp
Source/Common/System/LocalFileSystem.cpp
Source/Common/System/MiniDumper.cpp
Source/Common/System/ObjectStatusTypes.cpp
# Source/Common/System/QuotedPrintable.cpp
Source/Common/System/Radar.cpp
Expand Down
96 changes: 96 additions & 0 deletions Core/GameEngine/Include/Common/GameMemory.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ class MemoryPool;
class MemoryPoolFactory;
class DynamicMemoryAllocator;
class BlockCheckpointInfo;
#ifdef RTS_ENABLE_CRASHDUMP
class AllocationRangeIterator;
#endif

// TYPE DEFINES ///////////////////////////////////////////////////////////////

Expand Down Expand Up @@ -279,6 +282,14 @@ class Checkpointable
};
#endif

#ifdef RTS_ENABLE_CRASHDUMP
struct MemoryPoolAllocatedRange
{
const char* allocationAddr;
size_t allocationSize;
};
#endif

// ----------------------------------------------------------------------------
/**
A MemoryPool provides a way to efficiently allocate objects of the same (or similar)
Expand Down Expand Up @@ -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
};

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -474,13 +488,81 @@ class DynamicMemoryAllocator
Bool debugIsPoolInDma(MemoryPool *pool);

#endif // MEMORYPOOL_DEBUG
#ifdef RTS_ENABLE_CRASHDUMP
MemoryPoolSingleBlock* getFirstRawBlock() const;
MemoryPoolSingleBlock* getNextRawBlock(MemoryPoolSingleBlock* block) const;
void fillAllocationRangeForRawBlock(const MemoryPoolSingleBlock*, MemoryPoolAllocatedRange& allocationRange) const;
#endif
};

// ----------------------------------------------------------------------------
#ifdef MEMORYPOOL_DEBUG
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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this function definition in the CPP and the others are in the Header?

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; }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like UpdateRange() should happen elsewhere, for example in the increment. Because dereferencing should not update anything. It should just give reference.


// Prefix increment
AllocationRangeIterator& operator++() { MoveToNextBlob(); return *this; }

// Postfix increment
AllocationRangeIterator operator++(int) { AllocationRangeIterator tmp = *this; ++(*this); return tmp; }

friend const bool operator== (const AllocationRangeIterator& a, const AllocationRangeIterator& b)
{
return a.m_currentBlobInPool == b.m_currentBlobInPool;
};

friend const bool operator!= (const AllocationRangeIterator& a, const AllocationRangeIterator& b)
{
return a.m_currentBlobInPool != b.m_currentBlobInPool;
};

private:
const MemoryPoolFactory* m_factory;
MemoryPool* m_currentPool;
MemoryPoolBlob* m_currentBlobInPool;
MemoryPoolAllocatedRange m_range;
void UpdateRange();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer put class functions before variables.

void MoveToNextBlob();
};
#endif

// ----------------------------------------------------------------------------
/**
The class that manages all the MemoryPools and DynamicMemoryAllocators.
Expand Down Expand Up @@ -573,6 +655,20 @@ class MemoryPoolFactory
void debugResetCheckpoints();

#endif
#ifdef RTS_ENABLE_CRASHDUMP
AllocationRangeIterator cbegin() const
{
return AllocationRangeIterator(this);
}

AllocationRangeIterator cend() const
{
return AllocationRangeIterator(NULL, NULL);
}

MemoryPool* getFirstMemoryPool() const;
friend class AllocationRangeIterator;
#endif
};

// how many bytes are we allowed to 'waste' per pool allocation before the debug code starts yelling at us...
Expand Down
124 changes: 124 additions & 0 deletions Core/GameEngine/Include/Common/MiniDumper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/

#pragma once

#ifdef RTS_ENABLE_CRASHDUMP
#include "DbgHelpLoader.h"

enum DumpType CPP_11(: Char)
{
// Smallest dump type with call stacks and some supporting variables
DUMP_TYPE_MINIMAL = 'M',
// Large dump including all memory regions allocated by the GameMemory implementaion
DUMP_TYPE_GAMEMEMORY = 'X',
// Largest dump size including complete memory contents of the process
DUMP_TYPE_FULL = 'F',
};

class MiniDumper
{
enum MiniDumperExitCode CPP_11(: Int)
{
DUMPER_EXIT_SUCCESS = 0x0,
DUMPER_EXIT_FAILURE_WAIT = 0x37DA1040,
DUMPER_EXIT_FAILURE_PARAM = 0x4EA527BB,
DUMPER_EXIT_FORCED_TERMINATE = 0x158B1154,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using EnumName_ValueName name also for other new enums. These are good to avoid conflicts with MACRO_CASE and also help see name relationship between enum name and value names.

For example DUMPER_EXIT_SUCCESS looks not obvious to belong to MiniDumperExitCode but MiniDumperExitCode_Success does.

};

enum DumpObjectsState CPP_11(: Int)
{
DumpObjectsState_Begin,
DumpObjectsState_Memory_Pools,
DumpObjectsState_Memory_Pool_Allocations,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Prefer DumpObjectsState_MemoryPoolAllocations, where underscore only separates enum type name from value name.

DumpObjectsState_DMA_Allocations,
DumpObjectsState_Completed
};

public:
MiniDumper();
Bool IsInitialized() const;
void TriggerMiniDump(DumpType dumpType);
void TriggerMiniDumpForException(_EXCEPTION_POINTERS* e_info, DumpType dumpType);
static void initMiniDumper(const AsciiString& userDirPath);
static void shutdownMiniDumper();
static LONG WINAPI DumpingExceptionFilter(_EXCEPTION_POINTERS* e_info);

private:
void Initialize(const AsciiString& userDirPath);
void ShutDown();
void CreateMiniDump(DumpType dumpType);
void DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize);
void CleanupResources();
Bool IsDumpThreadStillRunning() const;
void ShutdownDumpThread();
Bool ShouldWriteDataSegsForModule(const PWCHAR module) const;

// Callbacks from dbghelp
static BOOL CALLBACK MiniDumpCallback(PVOID CallbackParam, PMINIDUMP_CALLBACK_INPUT CallbackInput, PMINIDUMP_CALLBACK_OUTPUT CallbackOutput);
BOOL CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP_CALLBACK_OUTPUT& output);

// Thread procs
static DWORD WINAPI MiniDumpThreadProc(LPVOID lpParam);
DWORD ThreadProcInternal();

// Dump file directory bookkeeping
Bool InitializeDumpDirectory(const AsciiString& userDirPath);
static void KeepNewestFiles(const std::string& directory, const DumpType dumpType, const Int keepCount);

// Struct to hold file information
struct FileInfo
{
std::string name;
FILETIME lastWriteTime;
};

static bool CompareByLastWriteTime(const FileInfo& a, const FileInfo& b);

private:
Bool m_miniDumpInitialized;
Bool m_loadedDbgHelp;
DumpType m_requestedDumpType;

// Path buffers
Char m_dumpDir[MAX_PATH];
Char m_dumpFile[MAX_PATH];
WideChar m_executablePath[MAX_PATH];

// Event handles
HANDLE m_dumpRequested;
HANDLE m_dumpComplete;
HANDLE m_quitting;

// Thread handles
HANDLE m_dumpThread;
DWORD m_dumpThreadId;

#ifndef DISABLE_GAMEMEMORY
// Internal memory dumping progress state
DumpObjectsState m_dumpObjectsState;
DynamicMemoryAllocator* m_currentAllocator;
MemoryPool* m_currentPool;
MemoryPoolSingleBlock* m_currentSingleBlock;

AllocationRangeIterator m_rangeIter;
#endif
};

extern MiniDumper* TheMiniDumper;
#endif
23 changes: 23 additions & 0 deletions Core/GameEngine/Source/Common/System/Debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@
#if defined(DEBUG_STACKTRACE) || defined(IG_DEBUG_STACKTRACE)
#include "Common/StackDump.h"
#endif
#ifdef RTS_ENABLE_CRASHDUMP
#include "Common/MiniDumper.h"
#endif

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


static void TriggerMiniDump()
{
#ifdef RTS_ENABLE_CRASHDUMP
if (TheMiniDumper && TheMiniDumper->IsInitialized())
{
// Do dumps both with and without extended info
TheMiniDumper->TriggerMiniDump(DUMP_TYPE_MINIMAL);
TheMiniDumper->TriggerMiniDump(DUMP_TYPE_GAMEMEMORY);
}

MiniDumper::shutdownMiniDumper();
#endif
}


void ReleaseCrash(const char *reason)
{
/// do additional reporting on the crash, if possible
Expand All @@ -737,6 +756,8 @@ void ReleaseCrash(const char *reason)
}
}

TriggerMiniDump();

char prevbuf[ _MAX_PATH ];
char curbuf[ _MAX_PATH ];

Expand Down Expand Up @@ -813,6 +834,8 @@ void ReleaseCrashLocalized(const AsciiString& p, const AsciiString& m)
return;
}

TriggerMiniDump();

UnicodeString prompt = TheGameText->fetch(p);
UnicodeString mesg = TheGameText->fetch(m);

Expand Down
Loading
Loading