/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** ** 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 . */ /////////////////////////////////////////////////////////////////////////EA-V1 // $File: //depot/GeneralsMD/Staging/code/Libraries/Source/debug/debug_debug.h $ // $Author: mhoffe $ // $Revision: #1 $ // $DateTime: 2003/07/03 11:55:26 $ // // ©2003 Electronic Arts // // main Debug object (singleton) ////////////////////////////////////////////////////////////////////////////// #ifdef _MSC_VER # pragma once #endif #ifndef DEBUG_DEBUG_H // Include guard #define DEBUG_DEBUG_H // this makes sure that whenever this header is included // the accompanying OBJ file is linked in as well #pragma comment(linker,"/include:___DebugIncludeInLink1") /** \class Debug debug.h \brief Debug module main class (singleton). */ class Debug { // necessary because all debug commands operate directly on this class friend class DebugCmdInterfaceDebug; // necessary because exception handler needs direct access friend class DebugExceptionhandler; public: enum { /// maximum number of times a check can be hit before it is turned off MAX_CHECK_HITS = 20 }; /** \brief HRESULT translator callback function type. See \ref Debug::AddHResultTranslator for more information. \param debug Debug instance where info can be written to \param hresult current HRESULT value \param user user value passed into AddHResultTranslator \return true if value translated and no more translators should be called, false if next translator should be tried */ typedef bool (*HResultTranslator)(Debug &debug, long hresult, void *user); /** \class MemDump debug.h \brief Helper class for performing a raw memory dump. Can be used directly in the output stream, e.g. \code DLOG( "This is 16 bytes of memory:\n" << Debug::MemDump::Raw(&somePointer,16) ); \endcode */ class MemDump { // necessary because Debug needs access to the following private members friend Debug; const unsigned char *m_startPtr; ///< start dumping with this address unsigned m_numItems; ///< dump the given number of items unsigned m_bytePerItem; ///< determines the number of bytes per item (1. 2 or 4) bool m_absAddr; ///< show absolute addresses (true) or relative addresses (false) bool m_withChars; ///< show printable characters on right side of dump (true) or not (false) // constructor is private on purpose so that nobody can // create instances of this class except the static functions // provided herein MemDump(const void *ptr, unsigned num, unsigned bpi, bool absAddr, bool withChars): m_startPtr((const unsigned char *)ptr), m_numItems(num), m_bytePerItem(bpi), m_absAddr(absAddr), m_withChars(withChars) {} public: /** Creates a memory dump descriptor. \param startPtr address to start dump from \param numItems number of items (usually bytes) to dump \param bytePerItem number of bytes per item (usually 1) */ static MemDump Raw(const void *startPtr, unsigned numItems, unsigned bytePerItem=1) { return MemDump(startPtr,numItems,bytePerItem,true,false); } /** Creates a memory dump descriptor with relative addresses. \param startPtr address to start dump from \param numItems number of items (usually bytes) to dump \param bytePerItem number of bytes per item (usually 1) */ static MemDump RawRel(const void *startPtr, unsigned numItems, unsigned bytePerItem=1) { return MemDump(startPtr,numItems,bytePerItem,false,false); } /** Creates a memory dump descriptor that dumps out ASCII chars as well. \param startPtr address to start dump from \param numItems number of items (usually bytes) to dump \param bytePerItem number of bytes per item (usually 1) */ static MemDump Char(const void *startPtr, unsigned numItems, unsigned bytePerItem=1) { return MemDump(startPtr,numItems,bytePerItem,true,true); } /** Creates a memory dump descriptor with relative addresses that dumps out ASCII chars as well. \param startPtr address to start dump from \param numItems number of items (usually bytes) to dump \param bytePerItem number of bytes per item (usually 1) */ static MemDump CharRel(const void *startPtr, unsigned numItems, unsigned bytePerItem=1) { return MemDump(startPtr,numItems,bytePerItem,false,true); } }; /** \class HResult debug.h \brief Helper class for writing HRESULTs to the debug stream. Can be used directly in the output stream, e.g. \code DLOG( "My HResult is: " << Debug::HResult(SomeHRESULTValue) << "\n" ); \endcode The most interesting thing about HRESULTs is that custom HRESULT translators can be added or removed during runtime, see \ref Debug::AddHResultTranslator for more information. */ class HResult { // necessary because Debug needs access to the following private members friend Debug; long m_hresult; ///< HRESULT value public: /** Creates a HRESULT descriptor. \param hresult HRESULT value (checked, Windows declares HRESULT as typedef long) */ explicit HResult(long hresult): m_hresult(hresult) {} }; /** \internal \brief Helper class for adding log group descriptions. */ class LogDescription { // sorry, no copies or assignments LogDescription(const LogDescription&); LogDescription& operator=(const LogDescription&); public: /** \internal Adds a description for a logging file/group. \param fileOrGroup filename or logging group (see \ref Debug::LogBegin) \param description descriptive text for logging file/group */ LogDescription(const char *fileOrGroup, const char *description); }; /** \brief Switches integer output to hexadecimal format. */ class Hex {}; /// \internal Performs actual switch to hexadecimal format. Debug& operator<<(Hex &) { SetPrefixAndRadix("0x",16); return *this; } /** \brief Switches integer output to decimal format. */ class Dec {}; /// \internal Performs actuals switch to decimal format Debug& operator<<(Dec &) { SetPrefixAndRadix("",10); return *this; } /** \brief Switches integer output to binary format. */ class Bin {}; /// \internal Performs actuals switch to binary format Debug& operator<<(Bin &) { SetPrefixAndRadix("%",2); return *this; } /** \brief Sets output width for the next insertion. */ class Width { // necessary because Debug needs access to the following private members friend Debug; int m_width; ///< output width public: /// \brief Sets new output width (next insertion only). explicit Width(int width): m_width(width) {} }; /// \internal Performs actuals width switch Debug& operator<<(Width &w) { m_width=w.m_width; return *this; } /** \brief Sets new fill character. */ class FillChar { // necessary because Debug needs access to the following private members friend Debug; char m_fill; ///< fill character public: /// \brief Sets new fill character. explicit FillChar(char ch=' '): m_fill(ch) {} }; /// \internal Performs actuals setting of fill char Debug& operator<<(FillChar &c) { m_fillChar=c.m_fill; return *this; } /** \brief Repeats a given character N times. */ class RepeatChar { // necessary because Debug needs access to the following private members friend Debug; char m_char; ///< character int m_count; ///< repeat count public: /// \brief Repeats a given character N times explicit RepeatChar(char ch, int count): m_char(ch), m_count(count) {} }; /// \internal Performs actuals repeating of char Debug& operator<<(RepeatChar &c); /** \brief Old printf style formatting. \note Do not use this helper class for new code. It is mainly here to get the old code base adapted to the new debug module more quickly. */ class Format { // necessary because Debug needs access to the following private members friend Debug; // no CC, AOp Format(const Format &); Format& operator=(const Format&); char m_buffer[512]; ///< this contains the string to write \note Fixed size buffer! public: /// \brief Old printf style formatting. explicit Format(const char *format, ...); }; /// \internal Writes printf style formatted string to debug log. Debug& operator<<(const Format &f) { operator<<(f.m_buffer); return *this; } // this is necessary because LogDescription needs to call AddLogGroup friend class LogDescription; /** \internal \brief Performs logical cleanup. */ ~Debug(); /** \brief Installs exception handler for current thread. For the main thread this is already done, but for any additional threads being created this function must be called. */ static void InstallExceptionHandler(void); /** \internal \brief Helper function for skipping over disabled asserts and logs. This function simply records the address it has been called from. If an assert or other function is disabled this function returns true, false otherwise. @todo_opt Change so that instead of returning true the call to this function is directly removed from the calling code \return true if next assert/log should be skipped, false otherwise */ static bool SkipNext(void); /** \internal \brief Helper function which gets called if an assertion fails. Starts building the assert string which will then be send to the active output destinations. SkipNext must be called before calling this function since this function also associates the most recent SkipNext call with the current assertion. \param file file that contains DASSERT or DASSERT_MSG macro \param line line where assert macro can be found \param expr expression that triggered the assertion, NULL for 'general failure' (\ref DFAIL) \return reference to Debug instance */ static Debug &AssertBegin(const char *file, int line, const char *expr); /** \internal \brief Displays assertion window. Depending on the user feedback (abort, retry, ignore) the program then either aborts or continues. This function is to be used as the final call in a debug message stream. \return false (always) */ bool AssertDone(void); /** \internal \brief Helper function which gets called if a check fails. Starts building the assert string which will then be send to the active output destinations. SkipNext must be called before calling this function since this function also associates the most recent SkipNext call with the current check. \param file file that contains DCHECK or DCHECK_MSG macro \param line line where check macro can be found \param expr expression that triggered the assertion \return reference to Debug instance */ static Debug &CheckBegin(const char *file, int line, const char *expr); /** \internal \brief Flushes current 'check' message. This function is to be used as the final call in a debug message stream. \return false (always) */ bool CheckDone(void); /** \internal \brief Helper function which gets when writing data to the output log. Starts building the log string which will then be send to the active output destinations. SkipNext must be called before calling this function since this function also associates the most recent SkipNext call with the current log group. \param fileOrGroup current file or group the following log data is for \return reference to Debug instance */ static Debug &LogBegin(const char *fileOrGroup); /** \internal \brief Flushes current 'log' message. This function is to be used as the final call in a debug message stream. \return false (always) */ bool LogDone(void); /** \internal \brief Helper function which gets called on crash. Starts building the crash string which will then be send to the active output destinations. \param file file that contains DCRASH or DCRASH_RELEASE macro, if NULL then no file info is given (used by DCRASH_RELEASE in release builds) \param line line where crash macro can be found, 0 if no line info should be given \return reference to Debug instance */ static Debug &CrashBegin(const char *file, int line); /** \internal \brief Exits program with a 'crash' message. This function is to be used as the final call in a debug message stream. \param die true if module should exit after displaying message, false if the user should have the choice \return false (always) */ bool CrashDone(bool die); /** \internal \brief Write string to output log. \param str string to write \return *this */ Debug& operator<<(const char *str); /** \internal \brief Define prefix and radix for integer output. \param prefix prefix to use (typically "" or "0x") \param radix radix to use (typically 10 or 16) */ void SetPrefixAndRadix(const char *prefix, int radix); /** \internal \brief Write signed integer to output log. \param val signed integer \return *this */ Debug& operator<<(int val); /** \internal \brief Write unsigned integer to output log. \param val unsigned integer \return *this */ Debug& operator<<(unsigned val); /** \internal \brief Write signed long to output log. \param val signed long \return *this */ Debug& operator<<(long val); /** \internal \brief Write unsigned long to output log. \param val unsigned long \return *this */ Debug& operator<<(unsigned long val); /** \internal \brief Write boolean value to output log. \param val bool \return *this */ Debug& operator<<(bool val); /** \internal \brief Write float value to output log. \param val float \return *this */ Debug& operator<<(float val); /** \internal \brief Write double precision float to output log. \param val double \return *this */ Debug& operator<<(double val); /** \internal \brief Write signed short integer to output log. \param val signed short integer \return *this */ Debug& operator<<(short val); /** \internal \brief Write unsigned short integer to output log. \param val unsigned short integer \return *this */ Debug& operator<<(unsigned short val); /** \internal \brief Write signed 64 bit integer to output log. \param val signed 64 bit integer \return *this */ Debug& operator<<(__int64 val); /** \internal \brief Write unsigned 64 bit integer to output log. \param val unsigned 64 bit integer \return *this */ Debug& operator<<(unsigned __int64 val); /** \internal \brief Write pointer address to output log. \param ptr pointer address \return *this */ Debug& operator<<(const void *ptr); /** \internal \brief Write memory dump to output log. \param dump MemDump descriptor, defines what range of memory to dump \return *this */ Debug& operator<<(const MemDump &dump); /** \internal \brief Write HRESULT value to output log. \param hres HResult descriptor \return *this */ Debug& operator<<(HResult hres); /** \internal \brief Determines if a log file/group is active or not. \param fileOrGroup Name of source file or group to check for. If the string contains any forward or backslashes then anything before them is ignored. If the then remaining string contains any dots anything beyond the first dot is ignored as well. \return true if logging is enabled, false if not */ static bool IsLogEnabled(const char *fileOrGroup); /** \brief Adds a HRESULT translator. A HRESULT translator is called whenever a HResult descriptor is passed into the Debug log stream. Such a translator can translate the numeric value into something more meaningful. Translators are differentiated by both function address and user pointer. There is however no harm in adding the same translator/user pointer pair twice. \param prio priority, translators with a higher priority get called first \param func translator address \param user optional user pointer which will be passed to the given translator \see RemoveHResultTranslator */ static void AddHResultTranslator(unsigned prio, HResultTranslator func, void *user=0); /** \brief Removes a HRESULT translator. If the translator/user pointer pair does not exist nothing is done. \param func translator address \param user optional user pointer \see AddHResultTranslator */ static void RemoveHResultTranslator(HResultTranslator func, void *user=0); /** \brief Registers a new I/O class factory function. This is typically used internally by the DEBUG_IMPLEMENT_IO_INTERFACE macro. If there is already an I/O class with the same name registered the new one overwrites the old. \param io_id name of I/O class as it should be registered with Debug module \param descr short class description \param func factory function \return true (so function can be used in static initializers) */ static bool AddIOFactory(const char *io_id, const char *descr, DebugIOInterface* (*func)(void)); /** \brief Adds a new command group. If the command group already exists the new command interface is added at the end of the list. A command issued for that group is first passed to the 'old' command interface. If that DebugCmdInterface::Execute call fails the command is passed on to the next interface within the same command group. The only exception is the 'help' command. Here all Execute functions are always called. Ownership of the interface pointer is passed to the Debug module i.e. the interface is destroyed when the Debug module shuts down (unless the interface is removed using RemoveCommands). \param cmdgroup command group this interface implements \param cmdif command group interface instance \return true (so function can be used in static initializers) */ static bool AddCommands(const char *cmdgroup, DebugCmdInterface *cmdif); /** \brief Removes a command group. \param cmdif command group interface that will be removed */ static void RemoveCommands(DebugCmdInterface *cmdif); /** \brief Issues a debug command. \param cmd command to execute */ static void Command(const char *cmd); /** \brief Update method, must be called on a regular basis. Scans I/O classes for new command input and processes it. */ static void Update(void); /** \internal \brief Simple recursive pattern matcher. \param str string to match \param pattern pattern, only wildcard valid is '*' \return true if string matches pattern, false if not */ static bool SimpleMatch(const char *str, const char *pattern); /** \brief Tell debug module about build info. All these strings are free form and can be up to 63 chars long. \param version official version \param internalVersion internal version \param buildDate build date & time */ static void SetBuildInfo(const char *version, const char *internalVersion, const char *buildDate); /** \brief Write build information into log. */ void WriteBuildInfo(void); private: // no assignment, no copy constructor Debug(const Debug&); Debug& operator=(const Debug&); /** \internal Undocumented default constructor. Initializes debugging library. We can make this private as well so nobody accidently tries to create a Debug instance. Actually this function does not do anything - initialization is rather performed by PreStaticInit() and PostStaticInit(). */ Debug(void); /** \internal This function gets called before any static C++ symbols are initialized. Code herein must be extremely careful because all global C++ instances are not initialized yet. */ static void PreStaticInit(void); /** \internal This function gets called after all static C++ symbols have been initialized. */ static void PostStaticInit(void); /** \internal This function gets called if the program exists. Use this function for any cleanup purposes (not the destructor, it might get called too early). */ static void StaticExit(void); /** \internal The only debug instance. Actually not used for anything except as magic first parameter for overloaded stream operators. */ static Debug Instance; /** \internal Helper variable for putting PreStaticInit() into the MSVC startup list. */ static void *PreStatic; /** \internal Helper variable for putting PostStaticInit() into the MSVC startup list. */ static void *PostStatic; /// \internal HResult translator vector entry struct HResultTranslatorEntry { /// priority unsigned prio; /// translator function HResultTranslator func; /// user pointer void *user; }; /// \internal HResult translator vector HResultTranslatorEntry *hrTranslators; /// \internal number of HResult translators unsigned numHrTranslators; /// \internal I/O class/factory list entry struct IOFactoryListEntry { /// pointer to next entry in list IOFactoryListEntry *next; /// I/O ID const char *ioID; /// I/O description const char *descr; /// factory function DebugIOInterface* (*factory)(void); /// I/O interface (may be NULL) DebugIOInterface *io; /// input buffer char *input; /// used size of input buffer unsigned inputUsed; /// allocated size of input buffer unsigned inputAlloc; }; /** \internal First I/O factory list entry. A singly linked list is okay for this because looking up I/O IDs is not time critical. */ IOFactoryListEntry *firstIOFactory; /// \internal command interface list entry struct CmdInterfaceListEntry { /// pointer to next entry in list CmdInterfaceListEntry *next; /// command group const char *group; /// interface pointer DebugCmdInterface *cmdif; }; /** \internal First command interface list entry. A singly linked list is okay for this because looking up command groups is not time critical. */ CmdInterfaceListEntry *firstCmdGroup; /// \internal current stack frame (used by SkipNext) static unsigned curStackFrame; /** \internal Bit mask for frame entry types. Implemented as a bit mask since it is also used for the inclusion/exclusion list where a single pattern can apply to more than one type. */ enum { /// assert FrameTypeAssert = 0x00000001, /// check FrameTypeCheck = 0x00000002, /// log FrameTypeLog = 0x00000004 }; /** \internal List of possible statuses for a frame hash entry. */ enum FrameStatus { /// unknown, must check Unknown = 0, /// skip this frame Skip, /// do not skip this frame NoSkip }; /** \internal \brief Hash table entry for mapping stack frame addresses to asserts/checks/logs. */ struct FrameHashEntry { /// pointer to next entry with same hash FrameHashEntry *next; /// frame address unsigned frameAddr; /// frame type (FrameTypeAssert, FrameTypeCheck, or FrameTypeLog) unsigned frameType; /// file (or group if log) const char *fileOrGroup; /// line number (undefined for logs) int line; /// number of times this frame has been hit int hits; /// frame status FrameStatus status; }; /// \internal initial frame hash size (prime number) enum { FRAME_HASH_SIZE = 10007 }; /// \internal frame hash pointers FrameHashEntry *frameHash[FRAME_HASH_SIZE]; /// \internal number of FrameHashEntry structures to allocate at one time enum { FRAME_HASH_ALLOC_COUNT = 100 }; /// \internal next unused FrameHashEntry structure FrameHashEntry *nextUnusedFrameHash; /// \internal number of available FrameHashEntry structures unsigned numAvailableFrameHash; /** \internal Look up given frame address. \param addr frame address \return FrameHashEntry found or 0 if nothing found */ __forceinline FrameHashEntry *LookupFrame(unsigned addr) { for (FrameHashEntry *e=frameHash[addr%FRAME_HASH_SIZE];e;e=e->next) if (e->frameAddr==addr) return e; return 0; } /** \internal Add frame address entry (entry must not exist yet). If a log frame is added then a corresponding entry is added to the list of known log groups as well. \param addr frame address \param type frame type \param fileOrGroup file (or group name) \param line line number \return the entry just added */ FrameHashEntry *AddFrameEntry(unsigned addr, unsigned type, const char *fileOrGroup, int line); /** \internal Updates the frames status variable depending on the current pattern list. \param entry entry that needs to be updated */ void UpdateFrameStatus(FrameHashEntry &entry); /** \internal Combines LookupFrame, AddFrameEntry and UpdateFrameStatus. \param addr frame address \param type frame type \param fileOrGroup file (or group name) \param line line number \return the entry just added (or the already existing entry) */ FrameHashEntry *GetFrameEntry(unsigned addr, unsigned type, const char *fileOrGroup, int line) { FrameHashEntry *e=LookupFrame(addr); if (!e) e=AddFrameEntry(addr,type,fileOrGroup,line); if (e->status==Unknown) UpdateFrameStatus(*e); return e; } /** \internal \brief List of known logs. A singly linked list is okay for this because looking up log groups happens only if a new stack frame entry is added (which happens only once for each log command). */ struct KnownLogGroupList { /// next entry KnownLogGroupList *next; /// name of log group (dynamically allocated memory) char *nameGroup; /// log description (if any) const char *descr; }; /// \internal first log group list entry KnownLogGroupList *firstLogGroup; /** \internal Adds a log group (and optionally description) to the list of known groups. If a group with that name already exists nothing is done. Takes care of stripping path and extension of the passed in group name (if it's a file name). Returns translated group name. \param fileOrGroup file or log group \param descr description, may be NULL \return translated log group name */ const char *AddLogGroup(const char *fileOrGroup, const char *descr); /// \internal I/O buffers for all I/O string types struct { /// buffer char *buffer; /// used buffer size unsigned used; /// allocated buffer size unsigned alloc; /// has last character been CR? bool lastWasCR; } ioBuffer[DebugIOInterface::StringType::MAX]; /// \internal current I/O string type we're writing DebugIOInterface::StringType curType; /// \internal current I/O string source (fixed size, careful!) char curSource[256]; /// \internal used to enable/disable all asserts/checks/logs int disableAssertsEtc; /** \internal Starts new output stream with the given type and source. \param type string type \param fmt wsprintf format string */ void StartOutput(DebugIOInterface::StringType type, const char *fmt, ...); /** \internal Adds the given string to the currently active output buffer. \param str string \param len string length */ void AddOutput(const char *str, unsigned len); /** \internal Flushes current I/O buffer to all output handlers. \param defaultLog if true and no I/O class is active then data is written to default.log */ void FlushOutput(bool defaultLog=true); /// \internal pointer to currently active frame FrameHashEntry *curFrameEntry; /// \internal pattern list entry struct PatternListEntry { /// next entry PatternListEntry *next; /// frame type(s) unsigned frameTypes; /// active (true) or inactive (false)? bool isActive; /// pattern itself (dynamic allocated memory) char *pattern; }; /** \internal First pattern list list entry. A singly linked list is okay for this because checking patterns is a costly operation anyway and is therefore cached. */ PatternListEntry *firstPatternEntry; /// \internal last pattern list entry for fast additions to list at end PatternListEntry *lastPatternEntry; /** \internal Adds a new pattern list entry. This function does not check if a matching pattern entry exists already. \param types frame type(s) \param isActive active (true) or inactive (false)? \param pattern pattern itself */ void AddPatternEntry(unsigned types, bool isActive, const char *pattern); /** \internal Executes the given command. \param cmdstart start of command \param cmdend end of command (not including this character) */ void ExecCommand(const char *cmdstart, const char *cmdend); /** \internal \brief Checks if main program is running windowed or not. If the decisison can not be made an windowed program is assumed. \return true if windowed, false if full screen */ bool IsWindowed(void); /// \internal name of current command group char curCommandGroup[100]; /// \internal if true then always flush after each line written bool alwaysFlush; /// \internal if true then put timestamps before each new line bool timeStamp; /// \internal the one and only stack walker DebugStackwalk m_stackWalk; /// \internal current integer prefix to use char m_prefix[16]; /// \internal current integer radix to use int m_radix; /// \internal official version char m_version[64]; /// \internal internal version char m_intVersion[64]; /// \internal build date/time char m_buildDate[64]; /// \internal output width int m_width; /// \internal fill char char m_fillChar; /// \internal <0 if fullscreen, >0 if windowed, ==0 if not checked yet char m_isWindowed; }; /// \addtogroup debug_fn Debugging functions ///@{ /** \brief Determines default commands to be executed at startup. This function returns a list of default commands which are executed on startup if no .dbgcmd file is read. Currently this function returns: \code "debug.io flat add" \endcode In order to provide a different set of commands simply put another version of DebugGetDefaultCommands in a CPP file. This is basically identical to having a .dbgcmd file in the current directory. For some situations (e.g. passing EXEs off to QA) it is however safer to have these startup commands build into the EXE itself rather than relying on QA having the right .dbgcmd file in place. As an example a new version of this function might return: \code "debug.io flat add\nio flat copy q:\logfiles\n" \endcode \return list of commands, separated by \\n \note This function is executed after all static variables have been initialized. */ const char *DebugGetDefaultCommands(void); ///@} end of debug_fn group #endif // DEBUG_DEBUG_H