/** * ========================================================================= * File : debug.h * Project : 0 A.D. * Description : platform-independent debug support code. * * @author Jan.Wassenberg@stud.uni-karlsruhe.de * ========================================================================= */ /* * Copyright (c) 2005 Jan Wassenberg * * Redistribution and/or modification are also permitted under the * terms of the GNU General Public License as published by the * Free Software Foundation (version 2 or later, at your option). * * 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. */ #ifndef DEBUG_H_INCLUDED #define DEBUG_H_INCLUDED #include "lib.h" // STMT #if OS_WIN # include "lib/sysdep/win/wdbg.h" #else # include "lib/sysdep/unix/udbg.h" #endif /** overview -------- this module provides platform-independent debug facilities, useful for diagnosing and reporting program errors. - a symbol engine provides access to compiler-generated debug information and can also give a stack trace including local variables; - the breakpoint API enables stopping when a given address is executed, read or written to (as specified); - a hook into the system's memory allocator can optionally check for and report heap corruption; - our more powerful assert() replacement gives a stack trace so that the underlying problem becomes apparent; - the output routines make for platform-independent logging and crashlogs with "last-known activity" reporting. usage ----- please see the detailed comments below on how to use the individual features. much of this is only helpful if you explicity ask for it! rationale --------- much of this functionality already exists within the VC7 IDE/debugger. motivation for this code is as follows: - we want a consistent interface for all platforms; - limitations(*) in the VC variants should be fixed; - make debugging as easy as possible. * mostly pertaining to Release mode - e.g. symbols cannot be resolved even if debug information is present and assert dialogs are useless. **/ //----------------------------------------------------------------------------- // debug memory allocator //----------------------------------------------------------------------------- /** * check heap integrity (independently of mmgr). * errors are reported by the CRT or via debug_display_error. **/ extern void debug_heap_check(void); enum DebugHeapChecks { /** * no automatic checks. (default) **/ DEBUG_HEAP_NONE = 0, /** * basic automatic checks when deallocating. **/ DEBUG_HEAP_NORMAL = 1, /** * all automatic checks on every memory API call. this is really * slow (x100), but reports errors closer to where they occurred. **/ DEBUG_HEAP_ALL = 2 }; /** * call at any time; from then on, the specified checks will be performed. * if not called, the default is DEBUG_HEAP_NONE, i.e. do nothing. **/ extern void debug_heap_enable(DebugHeapChecks what); //----------------------------------------------------------------------------- // output //----------------------------------------------------------------------------- enum DebugLevel { DEBUG_LEVEL_NONE = 0, DEBUG_LEVEL_BRIEF = 2, DEBUG_LEVEL_DETAILED = 5, DEBUG_LEVEL_FULL = 9, }; #define DEBUG_PRINTF_BRIEF if(debug_level >= DEBUG_LEVEL_BRIEF) debug_printf #define DEBUG_PRINTF_DETAILED if(debug_level >= DEBUG_LEVEL_DETAILED) debug_printf #define DEBUG_PRINTF_FULL if(debug_level >= DEBUG_LEVEL_FULL ) debug_printf /** * write a formatted string to the debug channel, subject to filtering * (see below). implemented via debug_puts - see performance note there. * * @param format string and varargs; see printf. **/ extern void debug_printf(const char* fmt, ...); /// note: this merely converts to a MBS and calls debug_printf. extern void debug_wprintf(const wchar_t* fmt, ...); /** * translates and displays the given strings in a dialog. * this is typically only used when debug_display_error has failed or * is unavailable because that function is much more capable. * implemented via sys_display_msgw; see documentation there. **/ extern void debug_display_msgw(const wchar_t* caption, const wchar_t* msg); /// flags to customize debug_display_error behavior enum DebugDisplayErrorFlags { /** * disallow the Continue button. used e.g. if an exception is fatal. **/ DE_NO_CONTINUE = 1, /** * enable the Suppress button. set automatically by debug_display_error 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_ALLOW_SUPPRESS = 2, /** * do not trigger a breakpoint inside debug_display_error; 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_MANUAL_BREAK = 4 }; /** * value for suppress flag once set by debug_display_error. * rationale: this value is fairly distinctive and helps when * debugging the symbol engine. * initial value is 0 rather that another constant; this avoids * allocating .rdata space. **/ const u8 DEBUG_SUPPRESS = 0xAB; /** * choices offered by the shared error dialog **/ enum ErrorReaction { /** * ignore, continue as if nothing happened. * note: value doesn't start at 0 because that is interpreted as a * DialogBoxParam failure. **/ ER_CONTINUE = 1, /** * trigger breakpoint, i.e. enter debugger. * only returned if DE_MANUAL_BREAK was passed; otherwise, * debug_display_error will trigger a breakpoint itself. **/ ER_BREAK, /** * ignore and do not report again. * note: non-persistent; only applicable during this program run. * acted on by debug_display_error; never returned to caller. **/ ER_SUPPRESS, /** * exit the program immediately. * acted on by debug_display_error; never returned to caller. **/ ER_EXIT, /** * 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. * acted on by debug_display_error; never returned to caller. **/ ER_NOT_IMPLEMENTED }; /** * display an error dialog with a message and stack trace. * * @param description text to show. * @param flags: see DebugDisplayErrorFlags. * @param context, skip: see debug_dump_stack. * @param file, line, func: location of the error (typically passed as * __FILE__, __LINE__, __func__ from a macro) * @param suppress pointer 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. values: see DEBUG_SUPPRESS above. * @return ErrorReaction (user's choice: continue running or stop?) **/ extern ErrorReaction debug_display_error(const wchar_t* description, uint flags, uint skip, void* context, const char* file, int line, const char* func, u8* suppress); /** * convenience version, in case the advanced parameters aren't needed. * macro instead of providing overload/default values for C compatibility. **/ #define DISPLAY_ERROR(text) debug_display_error(text, 0, 0,0, __FILE__,__LINE__,__func__, 0) // // filtering // /** * 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 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. * in future, allow output with the given tag to proceed. * no effect if already added. **/ extern void debug_filter_add(const char* tag); /** * in future, discard output with the given tag. * no effect if not currently added. **/ extern void debug_filter_remove(const char* tag); /** * clear all filter state; equivalent to debug_filter_remove for * each tag that was debug_filter_add-ed. **/ extern void debug_filter_clear(); /** * 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. **/ extern bool debug_filter_allows(const char* text); /** * write to memory buffer (fast) * used for "last activity" reporting in the crashlog. * * @param format string and varags; see printf. **/ extern void debug_wprintf_mem(const wchar_t* fmt, ...); /** * write an error description and all logs into crashlog.txt * (in unicode format). * * @param text description of the error (including stack trace); * typically generated by debug_error_message_build. * * @return LibError; ERR::REENTERED if reentered via recursion or * multithreading (not allowed since an infinite loop may result). **/ extern LibError debug_write_crashlog(const wchar_t* text); //----------------------------------------------------------------------------- // debug_assert //----------------------------------------------------------------------------- /** * make sure the expression 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! * * recommended use is debug_assert(expression && "descriptive string") - * the string can pass more information about the problem on to whomever * is seeing the error. * * rationale: we call this "debug_assert" instead of "assert" for the * following reasons: * - consistency (everything here is prefixed with debug_) and * - to avoid inadvertent use of the much less helpful built-in CRT assert. * if we were to override assert, it would be difficult to tell whether * user source has included (possibly indirectly via other * headers) and thereby stomped on our definition. **/ #define debug_assert(expr) \ STMT(\ static u8 suppress__;\ if(!(expr))\ {\ switch(debug_assert_failed(#expr, &suppress__, __FILE__, __LINE__, __func__))\ {\ case ER_BREAK:\ debug_break();\ break;\ default:\ break;\ }\ }\ ) /** * show a dialog to make sure unexpected states in the program are noticed. * this is less error-prone than "debug_assert(0 && "text");" and avoids * "conditional expression is constant" warnings. we'd really like to * completely eliminate the problem; replacing 0 literals with extern * volatile variables fools VC7 but isn't guaranteed to be free of overhead. * we therefore just squelch the warning (unfortunately non-portable). * this duplicates the code from debug_assert to avoid compiler warnings about * constant conditions. **/ #define debug_warn(expr) \ STMT(\ static u8 suppress__;\ switch(debug_assert_failed(expr, &suppress__, __FILE__, __LINE__, __func__))\ {\ case ER_BREAK:\ debug_break();\ break;\ default:\ break;\ }\ ) /** * if (LibError)err indicates an function failed, display the error dialog. * used by CHECK_ERR et al., which wrap function calls and automatically * warn user and return to caller. **/ #define DEBUG_WARN_ERR(err)\ STMT(\ static u8 suppress__;\ switch(debug_warn_err(err, &suppress__, __FILE__, __LINE__, __func__))\ {\ case ER_BREAK:\ debug_break();\ break;\ default:\ break;\ }\ ) /** * called when a debug_assert fails; * notifies the user via debug_display_error. * * @param assert_expr the expression that failed; typically passed as * #expr in the assert macro. * @param suppress see debug_display_error. * @param file, line source file name and line number of the spot that failed * @param func name of the function containing it * @return ErrorReaction (user's choice: continue running or stop?) **/ extern ErrorReaction debug_assert_failed(const char* assert_expr, u8* suppress, const char* file, int line, const char* func); /** * called when a DEBUG_WARN_ERR indicates an error occurred; * notifies the user via debug_display_error. * * @param err LibError value indicating the error that occurred * @param suppress see debug_display_error. * @param file, line source file name and line number of the spot that failed * @param func name of the function containing it * @return ErrorReaction (user's choice: continue running or stop?) **/ extern ErrorReaction debug_warn_err(LibError err, u8* suppress, const char* file, int line, const char* func); /** * suppress (prevent from showing) the next error dialog for a * specific LibError. * * 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. * * @param err the LibError to skip. if the next error to be raised matches * this, it is skipped. otherwise, we raise a warning to help catch * erroneous usage. either way, the skip request is reset afterwards. * * note: this is thread-safe, but to prevent confusion, only one * concurrent skip request is allowed. */ extern void debug_skip_next_err(LibError err); /** * same as debug_skip_next_err, but for asserts. * note that this is implemented in terms of it, so only one assert or * error skip request may be active at a time. */ extern void debug_skip_assert(); //----------------------------------------------------------------------------- // breakpoints //----------------------------------------------------------------------------- /** * trigger a breakpoint when reached/"called". * defined as a macro by the platform-specific header above; this allows * breaking directly into the target function, instead of one frame * below it as with a conventional call-based implementation. **/ //#define debug_break() // not defined here; see above /** * sometimes mmgr's 'fences' (making sure padding before and after the * allocation remains intact) aren't enough to catch hard-to-find * memory corruption bugs. another tool is to trigger a debug exception * when the later to be corrupted variable is accessed; the problem should * then become apparent. * the VC++ IDE provides such 'breakpoints', but can only detect write access. * additionally, it can't resolve symbols in Release mode (where this would * be most useful), so we provide a breakpoint API. * (values chosen to match IA-32 bit defs, so compiler can optimize. * this isn't required; it'll work regardless.) **/ enum DbgBreakType { DBG_BREAK_CODE = 0, /// execute DBG_BREAK_DATA_WRITE = 1, /// write DBG_BREAK_DATA = 3 /// read or write }; /** * arrange for a debug exception to be raised when the * indicated memory is accessed. * * @param addr memory address * for simplicity, the length (range of bytes to be checked) is derived * from addr's alignment, and is typically 1 machine word. * @param type the type of access to watch for (see DbgBreakType) * @return LibError; ERR::LIMIT if no more breakpoints are available * (they are a limited resource - only 4 on IA-32). **/ extern LibError debug_set_break(void* addr, DbgBreakType type); /** * remove all breakpoints that were set by debug_set_break. * important, since these are a limited resource. **/ extern LibError debug_remove_all_breaks(); //----------------------------------------------------------------------------- // symbol access //----------------------------------------------------------------------------- namespace ERR { const LibError SYM_NO_STACK_FRAMES_FOUND = -100400; const LibError SYM_UNRETRIEVABLE_STATIC = -100401; const LibError SYM_UNRETRIEVABLE_REG = -100402; const LibError SYM_TYPE_INFO_UNAVAILABLE = -100403; const LibError SYM_INTERNAL_ERROR = -100404; const LibError SYM_UNSUPPORTED = -100405; const LibError SYM_CHILD_NOT_FOUND = -100406; // this limit is to prevent infinite recursion. const LibError SYM_NESTING_LIMIT = -100407; // this limit is to prevent large symbols (e.g. arrays or linked lists) // from taking up all available output space. const LibError SYM_SINGLE_SYMBOL_LIMIT = -100408; } namespace INFO { // one of the dump_sym* functions decided not to output anything at // all (e.g. for member functions in UDTs - we don't want those). // therefore, skip any post-symbol formatting (e.g. ) as well. const LibError SYM_SUPPRESS_OUTPUT = +100809; } /** * maximum number of characters (including trailing \0) written to * user's buffers by debug_resolve_symbol. **/ const size_t DBG_SYMBOL_LEN = 1000; const size_t DBG_FILE_LEN = 100; /** * read and return symbol information for the given address. * * NOTE: the PDB implementation is rather slow (~500us). * * @param ptr_of_interest address of symbol (e.g. function, variable) * @param sym_name optional out; size >= DBG_SYMBOL_LEN chars; * receives symbol name returned via debug info. * @param file optional out; size >= DBG_FILE_LEN chars; receives * base name only (no path; see rationale in wdbg_sym) of * source file containing the symbol. * @param line optional 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. * @return LibError; INFO::OK iff any information was successfully * retrieved and stored. **/ extern LibError debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, int* line); /** * write a complete stack trace (including values of local variables) into * the specified buffer. * * @param buf target buffer * @param max_chars of buffer (should be several thousand) * @param skip number of stack frames (i.e. functions on call stack) to skip. * this prevents error-reporting functions like debug_assert_failed from * cluttering up the trace. * @param context platform-specific representation of execution state * (e.g. Win32 CONTEXT). if not NULL, tracing starts there; this is useful * for exceptions. otherwise, tracing starts from the current call stack. * @return LibError; ERR::REENTERED if reentered via recursion or * multithreading (not allowed since static data is used). **/ extern LibError debug_dump_stack(wchar_t* buf, size_t max_chars, uint skip, void* context); extern const char* debug_get_symbol_string(void* symbol, const char* name, const char* file, int line); //----------------------------------------------------------------------------- // helper functions (used by implementation) //----------------------------------------------------------------------------- /** * [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). **/ extern void debug_puts(const char* text); /** * return address of the Nth function on the call stack. * * used by mmgr to determine what function requested each allocation; * this is fast enough to allow that. * * @param skip number of stack frames (i.e. functions on call stack) to skip. * @param context platform-specific representation of execution state * (e.g. Win32 CONTEXT). if not NULL, tracing starts there; this is useful * for exceptions. otherwise, tracing starts from the current call stack. * @return address of Nth function **/ extern void* debug_get_nth_caller(uint skip, void* context); /** * 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. * * @param p pointer * @return 1 if totally bogus, otherwise 0. **/ extern int debug_is_pointer_bogus(const void* p); /// does the given pointer appear to point to code? extern bool debug_is_code_ptr(void* p); /// does the given pointer appear to point to the stack? extern bool debug_is_stack_ptr(void* p); /** * set the current thread's name; it will be returned by subsequent calls to * debug_get_thread_name. * * if supported on this platform, the debugger is notified of the new name; * it will be displayed there instead of just the handle. * * @param name identifier string for thread. MUST remain valid throughout * the entire program; best to pass a string literal. allocating a copy * would be quite a bit more work due to cleanup issues. **/ extern void debug_set_thread_name(const char* name); /** * return current thread's name. * * @return thread name, or NULL if one hasn't been assigned yet * via debug_set_thread_name. **/ extern const char* debug_get_thread_name(); /** * holds memory for an error message. **/ struct ErrorMessageMem { // rationale: // - error messages with stack traces require a good deal of memory // (dozens of KB). static buffers of that size are undesirable. // - the heap may be corrupted, so don't use malloc. allocator.h's // page_aligned_malloc (implemented via mmap) should be safe. // - alloca is a bit iffy (the stack may be maxed out), non-portable and // complicates the code because it can't be allocated by a subroutine. // - this method is probably slow, but error messages aren't built often. // if necessary, first try malloc and use mmap if that fails. void* pa_mem; }; /** * free memory from the error message. * * @param ErrorMessageMem* **/ extern void debug_error_message_free(ErrorMessageMem* emm); /** * build a string describing the given error. * * this is a helper function used by debug_dump_stack and is made available * so that the self-test doesn't have to display the error dialog. * * @param description: general description of the problem. * @param fn_only filename (no path) of source file that triggered the error. * @param line, func: exact position of the error. * @param skip, context: see debug_dump_stack. * @param emm memory for the error message. caller should allocate * stack memory and set alloc_buf*; if not, there will be no * fallback in case heap alloc fails. should be freed via * debug_error_message_free when no longer needed. **/ extern const wchar_t* debug_error_message_build( const wchar_t* description, const char* fn_only, int line, const char* func, uint skip, void* context, ErrorMessageMem* emm); /** * call at exit to avoid some leaks. * not strictly necessary. **/ extern void debug_shutdown(); #endif // #ifndef DEBUG_H_INCLUDED