Pyrogenesis HEAD
Pyrogenesis, a RTS Engine
debug.h File Reference
#include "lib/alignment.h"
#include "lib/code_annotation.h"
#include "lib/code_generation.h"
#include "lib/status.h"
#include "lib/types.h"
#include <atomic>
Include dependency graph for debug.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Namespaces

namespace  ERR
 
namespace  INFO
 

Macros

#define DEBUG_DISPLAY_ERROR_IMPL(description, flags)
 
#define DEBUG_DISPLAY_ERROR(description)   DEBUG_DISPLAY_ERROR_IMPL(description, 0)
 
#define DEBUG_DISPLAY_FATAL_ERROR(description)   DEBUG_DISPLAY_ERROR_IMPL(description, DE_NO_CONTINUE)
 
#define ENSURE(expr)
 ensure the expression <expr> evaluates to non-zero. More...
 
#define ASSERT(expr)   ENSURE(expr)
 same as ENSURE in debug mode, does nothing in release mode. More...
 
#define debug_warn(expr)   ENSURE(0 && (expr))
 display the error dialog with the given text. More...
 
#define DEBUG_WARN_ERR(status)
 display the error dialog with text corresponding to the given error code. More...
 

Enumerations

enum  DebugDisplayErrorFlags { DE_NO_CONTINUE = 1 , DE_ALLOW_SUPPRESS = 2 , DE_MANUAL_BREAK = 4 , DE_NO_DEBUG_INFO = 8 }
 flags to customize debug_DisplayError behavior More...
 
enum  ErrorReaction { ER_CONTINUE = 1 , ER_BREAK }
 choices offered by the error dialog that are returned by debug_DisplayError. More...
 
enum  ErrorReactionInternal {
  ERI_CONTINUE = ER_CONTINUE , ERI_BREAK = ER_BREAK , ERI_SUPPRESS , ERI_EXIT ,
  ERI_NOT_IMPLEMENTED
}
 all choices offered by the error dialog. More...
 

Functions

void debug_break ()
 trigger a breakpoint when reached/"called". More...
 
void debug_printf (const char *fmt,...) PRINTF_ARGS(1)
 write a formatted string to the debug channel, subject to filtering (see below). More...
 
void debug_DisplayMessage (const wchar_t *caption, const wchar_t *msg)
 translates and displays the given strings in a dialog. More...
 
ErrorReaction debug_DisplayError (const wchar_t *description, size_t flags, void *context, const wchar_t *lastFuncToSkip, const wchar_t *file, int line, const char *func, std::atomic< bool > *suppress)
 display an error dialog with a message and stack trace. More...
 
void debug_filter_add (const char *tag)
 debug output is very useful, but "too much of a good thing can kill you". More...
 
void debug_filter_remove (const char *tag)
 in future, discard output with the given tag. More...
 
void debug_filter_clear ()
 clear all filter state; equivalent to debug_filter_remove for each tag that was debug_filter_add-ed. More...
 
bool debug_filter_allows (const char *text)
 indicate if the given text would be printed. More...
 
void debug_puts_filtered (const char *text)
 call debug_puts if debug_filter_allows allows the string. More...
 
Status debug_WriteCrashlog (const char *text)
 write an error description and all logs into crashlog.txt (in unicode format). More...
 
ErrorReaction debug_OnAssertionFailure (const wchar_t *assert_expr, std::atomic< bool > *suppress, const wchar_t *file, int line, const char *func) ANALYZER_NORETURN
 called when a ENSURE/ASSERT fails; notifies the user via debug_DisplayError. More...
 
ErrorReaction debug_OnError (Status err, std::atomic< bool > *suppress, const wchar_t *file, int line, const char *func) ANALYZER_NORETURN
 called when a DEBUG_WARN_ERR indicates an error occurred; notifies the user via debug_DisplayError. More...
 
void debug_SkipErrors (Status err)
 suppress (prevent from showing) the error dialog from subsequent debug_OnError for the given Status. More...
 
size_t debug_StopSkippingErrors ()
 
Status debug_ResolveSymbol (void *ptr_of_interest, wchar_t *sym_name, wchar_t *file, int *line)
 read and return symbol information for the given address. More...
 
Status debug_CaptureContext (void *context)
 
Status debug_DumpStack (wchar_t *buf, size_t maxChars, void *context, const wchar_t *lastFuncToSkip)
 write a complete stack trace (including values of local variables) into the specified buffer. More...
 
void debug_puts (const char *text)
 [system-dependent] write a string to the debug channel. More...
 
void * debug_GetCaller (void *context, const wchar_t *lastFuncToSkip)
 return the caller of a certain function on the call stack. More...
 
int debug_IsPointerBogus (const void *p)
 check if a pointer appears to be totally invalid. More...
 
bool debug_IsCodePointer (void *p)
 does the given pointer appear to point to code? More...
 
bool debug_IsStackPointer (void *p)
 does the given pointer appear to point to the stack? More...
 
void debug_SetThreadName (const char *name)
 inform the debugger of the current thread's name. More...
 
const wchar_t * debug_BuildErrorMessage (const wchar_t *description, const wchar_t *fn_only, int line, const char *func, void *context, const wchar_t *lastFuncToSkip)
 build a string describing the given error. More...
 

Variables

const Status ERR::SYM_NO_STACK_FRAMES_FOUND = -100400
 
const Status ERR::SYM_UNRETRIEVABLE_STATIC = -100401
 
const Status ERR::SYM_UNRETRIEVABLE = -100402
 
const Status ERR::SYM_TYPE_INFO_UNAVAILABLE = -100403
 
const Status ERR::SYM_INTERNAL_ERROR = -100404
 
const Status ERR::SYM_UNSUPPORTED = -100405
 
const Status ERR::SYM_CHILD_NOT_FOUND = -100406
 
const Status ERR::SYM_NESTING_LIMIT = -100407
 
const Status ERR::SYM_SINGLE_SYMBOL_LIMIT = -100408
 
const Status INFO::SYM_SUPPRESS_OUTPUT = +100409
 
static const size_t DEBUG_SYMBOL_CHARS = 1000
 Maximum number of characters (including null terminator) written to user's buffers by debug_ResolveSymbol. More...
 
static const size_t DEBUG_FILE_CHARS = 100
 
static const size_t DEBUG_CONTEXT_SIZE = 2048
 

Macro Definition Documentation

◆ ASSERT

#define ASSERT (   expr)    ENSURE(expr)

same as ENSURE in debug mode, does nothing in release mode.

(we don't override the ‘assert’ macro because users may inadvertently include <assert.h> afterwards) (we do not provide an MFC-style VERIFY macro because the distinction between ENSURE and VERIFY is unclear. to always run code but only check for success in debug builds without raising unused-variable warnings, use ASSERT + UNUSED2.)

◆ DEBUG_DISPLAY_ERROR

#define DEBUG_DISPLAY_ERROR (   description)    DEBUG_DISPLAY_ERROR_IMPL(description, 0)

◆ DEBUG_DISPLAY_ERROR_IMPL

#define DEBUG_DISPLAY_ERROR_IMPL (   description,
  flags 
)
Value:
do\
{\
CACHE_ALIGNED(u8) context[DEBUG_CONTEXT_SIZE];\
(void)debug_CaptureContext(context);\
(void)debug_DisplayError(description, flags, context, L"debug_DisplayError", WIDEN(__FILE__), __LINE__, __func__, 0);\
}\
while(0)
#define __func__
Definition: code_annotation.h:362
#define WIDEN(x)
Definition: code_annotation.h:404
ErrorReaction debug_DisplayError(const wchar_t *description, size_t flags, void *context, const wchar_t *lastFuncToSkip, const wchar_t *file, int line, const char *func, std::atomic< bool > *suppress)
display an error dialog with a message and stack trace.
Definition: debug.cpp:410
Status debug_CaptureContext(void *context)
Definition: udbg.cpp:42
static const size_t DEBUG_CONTEXT_SIZE
Definition: debug.h:446
uint8_t u8
Definition: types.h:37

◆ DEBUG_DISPLAY_FATAL_ERROR

#define DEBUG_DISPLAY_FATAL_ERROR (   description)    DEBUG_DISPLAY_ERROR_IMPL(description, DE_NO_CONTINUE)

◆ debug_warn

#define debug_warn (   expr)    ENSURE(0 && (expr))

display the error dialog with the given text.

this is less error-prone than ENSURE(0 && "text"). note that "conditional expression is constant" warnings are disabled anyway.

if being able to suppress the warning is desirable (e.g. for self-tests), then use DEBUG_WARN_ERR instead.

◆ DEBUG_WARN_ERR

#define DEBUG_WARN_ERR (   status)
Value:
do\
{\
static std::atomic<bool> suppress__{ false };\
switch(debug_OnError(status, &suppress__, WIDEN(__FILE__), __LINE__, __func__))\
{\
case ER_CONTINUE:\
break;\
case ER_BREAK:\
default:\
debug_break();\
break;\
}\
}\
while(0)
ErrorReaction debug_OnError(Status err, std::atomic< bool > *suppress, const wchar_t *file, int line, const char *func) ANALYZER_NORETURN
called when a DEBUG_WARN_ERR indicates an error occurred; notifies the user via debug_DisplayError.
Definition: debug.cpp:512
@ ER_CONTINUE
ignore, continue as if nothing happened.
Definition: debug.h:124
@ ER_BREAK
trigger breakpoint, i.e.
Definition: debug.h:131

display the error dialog with text corresponding to the given error code.

used by WARN_RETURN_STATUS_IF_ERR et al., which wrap function calls and automatically raise warnings and return to the caller.

◆ ENSURE

#define ENSURE (   expr)
Value:
do\
{\
static std::atomic<bool> suppress__{ false };\
if(!(expr))\
{\
switch(debug_OnAssertionFailure(WIDEN(#expr), &suppress__, WIDEN(__FILE__), __LINE__, __func__))\
{\
case ER_CONTINUE:\
break;\
case ER_BREAK:\
default:\
debug_break();\
break;\
}\
}\
}\
while(0)
ErrorReaction debug_OnAssertionFailure(const wchar_t *assert_expr, std::atomic< bool > *suppress, const wchar_t *file, int line, const char *func) ANALYZER_NORETURN
called when a ENSURE/ASSERT fails; notifies the user via debug_DisplayError.
Definition: debug.cpp:527

ensure the expression <expr> evaluates to non-zero.

used to validate invariants in the program during development and thus gives a very helpful warning if something isn't going as expected. sprinkle these liberally throughout your code!

to pass more information to users at runtime, you can write ENSURE(expression && "descriptive string").

Enumeration Type Documentation

◆ DebugDisplayErrorFlags

flags to customize debug_DisplayError behavior

Enumerator
DE_NO_CONTINUE 

disallow the Continue button.

used e.g. if an exception is fatal.

DE_ALLOW_SUPPRESS 

enable the Suppress button.

set automatically by debug_DisplayError if it receives a non-NULL suppress pointer. a flag is necessary because the sys_display_error interface doesn't get that pointer. rationale for automatic setting: this may prevent someone from forgetting to specify it, and disabling Suppress despite having passed a non-NULL pointer doesn't make much sense.

DE_MANUAL_BREAK 

do not trigger a breakpoint inside debug_DisplayError; caller will take care of this if ER_BREAK is returned.

this is so that the debugger can jump directly into the offending function.

DE_NO_DEBUG_INFO 

display just the given message; do not add any information about the call stack, do not write crashlogs, etc.

◆ ErrorReaction

choices offered by the error dialog that are returned by debug_DisplayError.

Enumerator
ER_CONTINUE 

ignore, continue as if nothing happened.

note: value doesn't start at 0 because that is interpreted as a DialogBoxParam failure.

ER_BREAK 

trigger breakpoint, i.e.

enter debugger. only returned if DE_MANUAL_BREAK was passed; otherwise, debug_DisplayError will trigger a breakpoint itself.

◆ ErrorReactionInternal

all choices offered by the error dialog.

those not defined in ErrorReaction are acted upon by debug_DisplayError and never returned to callers. (this separation avoids enumerator-not-handled warnings)

Enumerator
ERI_CONTINUE 
ERI_BREAK 
ERI_SUPPRESS 

ignore and do not report again.

note: non-persistent; only applicable during this program run.

ERI_EXIT 

exit the program immediately.

ERI_NOT_IMPLEMENTED 

special return value for the display_error app hook stub to indicate that it has done nothing and that the normal sys_display_error implementation should be called instead.

Function Documentation

◆ debug_break()

void debug_break ( )

trigger a breakpoint when reached/"called".

if defined as a macro, the debugger can break directly into the target function instead of one frame below it as with a conventional call-based implementation.

◆ debug_BuildErrorMessage()

const wchar_t * debug_BuildErrorMessage ( const wchar_t *  description,
const wchar_t *  fn_only,
int  line,
const char *  func,
void *  context,
const wchar_t *  lastFuncToSkip 
)

build a string describing the given error.

this is a helper function used by debug_DumpStack and is made available so that the self-test doesn't have to display the error dialog.

Parameters
descriptiongeneral description of the problem.
fn_onlyfilename (no path) of source file that triggered the error.
line,funcexact position of the error.
context,lastFuncToSkipsee debug_DumpStack.

◆ debug_CaptureContext()

Status debug_CaptureContext ( void *  context)
Parameters
contextmust point to an instance of the platform-specific type (e.g. CONTEXT) or CACHE_ALIGNED storage of DEBUG_CONTEXT_SIZE bytes.

◆ debug_DisplayError()

ErrorReaction debug_DisplayError ( const wchar_t *  description,
size_t  flags,
void *  context,
const wchar_t *  lastFuncToSkip,
const wchar_t *  file,
int  line,
const char *  func,
std::atomic< bool > *  suppress 
)

display an error dialog with a message and stack trace.

Parameters
descriptiontext to show.
flagssee DebugDisplayErrorFlags.
context,lastFuncToSkipsee debug_DumpStack.
file,line,funclocation of the error (typically passed as WIDEN(FILE), LINE, func from a macro)
suppresspointer to a caller-allocated flag that can be used to suppress this error. if NULL, this functionality is skipped and the "Suppress" dialog button will be disabled. note: this flag is read and written exclusively here; caller only provides the storage.
Returns
ErrorReaction (user's choice: continue running or stop?)

◆ debug_DisplayMessage()

void debug_DisplayMessage ( const wchar_t *  caption,
const wchar_t *  msg 
)

translates and displays the given strings in a dialog.

this is typically only used when debug_DisplayError has failed or is unavailable because that function is much more capable. implemented via sys_display_msg; see documentation there.

◆ debug_DumpStack()

Status debug_DumpStack ( wchar_t *  buf,
size_t  maxChars,
void *  context,
const wchar_t *  lastFuncToSkip 
)

write a complete stack trace (including values of local variables) into the specified buffer.

Parameters
bufTarget buffer.
maxCharsMax chars of buffer (should be several thousand).
contextPlatform-specific representation of execution state (e.g. Win32 CONTEXT). either specify an SEH exception's context record or use debug_CaptureContext to retrieve the current state. Rationale: intermediates such as debug_DisplayError change the context, so it should be captured as soon as possible.
lastFuncToSkipIs used for omitting error-reporting functions like debug_OnAssertionFailure from the stack trace. It is either 0 (skip nothing) or a substring of a function's name (this allows platform-independent matching of stdcall-decorated names). Rationale: this is safer than specifying a fixed number of frames, which can be incorrect due to inlining.
Returns
Status; ERR::REENTERED if reentered via recursion or multithreading (not allowed since static data is used).

◆ debug_filter_add()

void debug_filter_add ( const char *  tag)

debug output is very useful, but "too much of a good thing can kill you".

we don't want to require different LOGn() macros that are enabled depending on "debug level", because changing that entails lengthy compiles and it's too coarse-grained. instead, we require all strings to start with "tag_string|" (exact case and no quotes; the alphanumeric-only <tag_string> identifies output type). they are then subject to filtering: only if the tag has been "added" via debug_filter_add is the appendant string displayed.

this approach is easiest to implement and is fine because we control all logging code. LIMODS falls from consideration since it's not portable and too complex.

notes:

  • filter changes only affect subsequent debug_*printf calls; output that didn't pass the filter is permanently discarded.
  • strings not starting with a tag are always displayed.
  • debug_filter_* can be called at any time and from the debugger, but are not reentrant.

in future, allow output with the given tag to proceed. no effect if already added.

◆ debug_filter_allows()

bool debug_filter_allows ( const char *  text)

indicate if the given text would be printed.

useful for a series of debug_printfs - avoids needing to add a tag to each of their format strings.

◆ debug_filter_clear()

void debug_filter_clear ( )

clear all filter state; equivalent to debug_filter_remove for each tag that was debug_filter_add-ed.

◆ debug_filter_remove()

void debug_filter_remove ( const char *  tag)

in future, discard output with the given tag.

no effect if not currently added.

◆ debug_GetCaller()

void * debug_GetCaller ( void *  context,
const wchar_t *  lastFuncToSkip 
)

return the caller of a certain function on the call stack.

this function is useful for recording (partial) stack traces for memory allocation tracking, etc.

Parameters
context,lastFuncToSkip- see debug_DumpStack
Returns
address of the caller

◆ debug_IsCodePointer()

bool debug_IsCodePointer ( void *  p)

does the given pointer appear to point to code?

◆ debug_IsPointerBogus()

int debug_IsPointerBogus ( const void *  p)

check if a pointer appears to be totally invalid.

this check is not authoritative (the pointer may be "valid" but incorrect) but can be used to filter out obviously wrong values in a portable manner.

Parameters
ppointer
Returns
1 if totally bogus, otherwise 0.

◆ debug_IsStackPointer()

bool debug_IsStackPointer ( void *  p)

does the given pointer appear to point to the stack?

◆ debug_OnAssertionFailure()

ErrorReaction debug_OnAssertionFailure ( const wchar_t *  assert_expr,
std::atomic< bool > *  suppress,
const wchar_t *  file,
int  line,
const char *  func 
)

called when a ENSURE/ASSERT fails; notifies the user via debug_DisplayError.

Parameters
assert_exprthe expression that failed; typically passed as #expr in the assert macro.
suppresssee debug_DisplayError.
file,linesource file name and line number of the spot that failed
funcname of the function containing it
Returns
ErrorReaction (user's choice: continue running or stop?)

◆ debug_OnError()

ErrorReaction debug_OnError ( Status  err,
std::atomic< bool > *  suppress,
const wchar_t *  file,
int  line,
const char *  func 
)

called when a DEBUG_WARN_ERR indicates an error occurred; notifies the user via debug_DisplayError.

Parameters
errStatus value indicating the error that occurred
suppresssee debug_DisplayError.
file,linesource file name and line number of the spot that failed
funcname of the function containing it
Returns
ErrorReaction (user's choice: continue running or stop?)

◆ debug_printf()

void debug_printf ( const char *  fmt,
  ... 
)

write a formatted string to the debug channel, subject to filtering (see below).

implemented via debug_puts - see performance note there.

Parameters
fmtFormat string and varargs; see printf.

◆ debug_puts()

void debug_puts ( const char *  text)

[system-dependent] write a string to the debug channel.

this can be quite slow (~1 ms)! On Windows, it uses OutputDebugString (entails context switch), otherwise stdout+fflush (waits for IO).

◆ debug_puts_filtered()

void debug_puts_filtered ( const char *  text)

call debug_puts if debug_filter_allows allows the string.

◆ debug_ResolveSymbol()

Status debug_ResolveSymbol ( void *  ptr_of_interest,
wchar_t *  sym_name,
wchar_t *  file,
int *  line 
)

read and return symbol information for the given address.

NOTE: the PDB implementation is rather slow (~500 us).

Parameters
ptr_of_interestaddress of symbol (e.g. function, variable)
sym_nameoptional out; holds at least DEBUG_SYMBOL_CHARS; receives symbol name returned via debug info.
fileoptional out; holds at least DEBUG_FILE_CHARS; receives base name only (no path; see rationale in wdbg_sym) of source file containing the symbol.
lineoptional out; receives source file line number of symbol.

note: all of the output parameters are optional; we pass back as much information as is available and desired.

Returns
Status; INFO::OK iff any information was successfully retrieved and stored.

◆ debug_SetThreadName()

void debug_SetThreadName ( const char *  name)

inform the debugger of the current thread's name.

(threads are easier to keep apart when they are identified by name rather than TID.)

◆ debug_SkipErrors()

void debug_SkipErrors ( Status  err)

suppress (prevent from showing) the error dialog from subsequent debug_OnError for the given Status.

rationale: for edge cases in some functions, warnings are raised in addition to returning an error code. self-tests deliberately trigger these cases and check for the latter but shouldn't cause the former. we therefore need to squelch them.

Parameters
errthe Status to skip.

note: only one concurrent skip request is allowed; call debug_StopSkippingErrors before the next debug_SkipErrors.

◆ debug_StopSkippingErrors()

size_t debug_StopSkippingErrors ( )
Returns
how many errors were skipped since the call to debug_SkipErrors()

◆ debug_WriteCrashlog()

Status debug_WriteCrashlog ( const char *  text)

write an error description and all logs into crashlog.txt (in unicode format).

Parameters
textdescription of the error (including stack trace); typically generated by debug_BuildErrorMessage.
Returns
Status; ERR::REENTERED if reentered via recursion or multithreading (not allowed since an infinite loop may result).

Variable Documentation

◆ DEBUG_CONTEXT_SIZE

const size_t DEBUG_CONTEXT_SIZE = 2048
static

◆ DEBUG_FILE_CHARS

const size_t DEBUG_FILE_CHARS = 100
static

◆ DEBUG_SYMBOL_CHARS

const size_t DEBUG_SYMBOL_CHARS = 1000
static

Maximum number of characters (including null terminator) written to user's buffers by debug_ResolveSymbol.