File: debug.h

package info (click to toggle)
0ad 0.0.23.1-2
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 78,292 kB
  • sloc: cpp: 245,166; ansic: 200,249; python: 13,754; sh: 6,104; perl: 4,620; makefile: 977; xml: 810; java: 533; ruby: 229; erlang: 46; pascal: 30; sql: 21; tcl: 4
file content (578 lines) | stat: -rw-r--r-- 20,260 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
/* Copyright (C) 2015 Wildfire Games.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/*
 * platform-independent debug support code.
 */

#ifndef INCLUDED_DEBUG
#define INCLUDED_DEBUG

// 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;
// - 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.

#include "lib/lib_api.h"
#include "lib/types.h"	// intptr_t
#include "lib/status.h"
#include "lib/alignment.h"
#include "lib/code_annotation.h"
#include "lib/code_generation.h"

/**
 * 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.
 **/
#if MSC_VERSION
# define debug_break __debugbreak	// intrinsic "function"
#else
extern void debug_break();
#endif


//-----------------------------------------------------------------------------
// output
//-----------------------------------------------------------------------------

/**
 * write a formatted string to the debug channel, subject to filtering
 * (see below). implemented via debug_puts - see performance note there.
 *
 * @param fmt Format string and varargs; see printf.
 **/
LIB_API void debug_printf(const char* fmt, ...) PRINTF_ARGS(1);


/**
 * 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.
 **/
LIB_API void debug_DisplayMessage(const wchar_t* caption, const wchar_t* msg);

/// flags to customize debug_DisplayError 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_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_ALLOW_SUPPRESS = 2,

	/**
	 * 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_MANUAL_BREAK = 4,

	/**
	 * display just the given message; do not add any information about the
	 * call stack, do not write crashlogs, etc.
	 */
	DE_NO_DEBUG_INFO = 8
};

/**
 * a bool that is reasonably certain to be set atomically.
 * we cannot assume support for OpenMP (requires GCC 4.2) or C++0x,
 * so we'll have to resort to intptr_t, cpu_CAS and COMPILER_FENCE.
 **/
typedef volatile intptr_t atomic_bool;

/**
 * value for suppress flag once set by debug_DisplayError.
 * rationale: this value is fairly distinctive and helps when
 * debugging the symbol engine.
 * use 0 as the initial value to avoid allocating .rdata space.
 **/
static const atomic_bool DEBUG_SUPPRESS = 0xAB;

/**
 * choices offered by the error dialog that are returned
 * by debug_DisplayError.
 **/
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_DisplayError will trigger a breakpoint itself.
	 **/
	ER_BREAK
};

/**
 * 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)
 **/
enum ErrorReactionInternal
{
	ERI_CONTINUE = ER_CONTINUE,
	ERI_BREAK = ER_BREAK,

	/**
	 * ignore and do not report again.
	 * note: non-persistent; only applicable during this program run.
	 **/
	ERI_SUPPRESS,

	/**
	 * exit the program immediately.
	 **/
	ERI_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.
	 **/
	ERI_NOT_IMPLEMENTED
};


/**
 * display an error dialog with a message and stack trace.
 *
 * @param description text to show.
 * @param flags: see DebugDisplayErrorFlags.
 * @param context, lastFuncToSkip: see debug_DumpStack.
 * @param file, line, func: location of the error (typically passed as
 * WIDEN(__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?)
 **/
LIB_API 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, atomic_bool* suppress);

// simplified version for just displaying an error message
#define DEBUG_DISPLAY_ERROR(description)\
	do\
	{\
		CACHE_ALIGNED(u8) context[DEBUG_CONTEXT_SIZE];\
		(void)debug_CaptureContext(context);\
		(void)debug_DisplayError(description, 0, context, L"debug_DisplayError", WIDEN(__FILE__), __LINE__, __func__, 0);\
	}\
	while(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 \<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.
 **/
LIB_API void debug_filter_add(const char* tag);

/**
 * in future, discard output with the given tag.
 * no effect if not currently added.
 **/
LIB_API 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.
 **/
LIB_API 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.
 **/
LIB_API bool debug_filter_allows(const char* text);

/**
 * call debug_puts if debug_filter_allows allows the string.
 **/
LIB_API void debug_puts_filtered(const char* text);

/**
 * 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_BuildErrorMessage.
 *
 * @return Status; ERR::REENTERED if reentered via recursion or
 * multithreading (not allowed since an infinite loop may result).
 **/
LIB_API Status debug_WriteCrashlog(const char* text);


//-----------------------------------------------------------------------------
// assertions
//-----------------------------------------------------------------------------

/**
 * 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").
 **/
#define ENSURE(expr)\
	do\
	{\
		static atomic_bool suppress__;\
		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)

/**
 * 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.)
 **/
#define ASSERT(expr) ENSURE(expr)
#ifdef NDEBUG
# undef ASSERT
# define ASSERT(expr)
#endif

/**
 * 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.
 **/
#define debug_warn(expr) ENSURE(0 && (expr))

/**
 * 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.
 **/
#define DEBUG_WARN_ERR(status)\
	do\
	{\
		static atomic_bool suppress__;\
		switch(debug_OnError(status, &suppress__, WIDEN(__FILE__), __LINE__, __func__))\
		{\
		case ER_CONTINUE:\
			break;\
		case ER_BREAK:\
		default:\
			debug_break();\
			break;\
		}\
	}\
	while(0)

/**
 * called when a ENSURE/ASSERT fails;
 * notifies the user via debug_DisplayError.
 *
 * @param assert_expr the expression that failed; typically passed as
 * \#expr in the assert macro.
 * @param suppress see debug_DisplayError.
 * @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?)
 **/
LIB_API ErrorReaction debug_OnAssertionFailure(const wchar_t* assert_expr, 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.
 *
 * @param err Status value indicating the error that occurred
 * @param suppress see debug_DisplayError.
 * @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?)
 **/
LIB_API ErrorReaction debug_OnError(Status err, atomic_bool* suppress, const wchar_t* file, int line, const char* func) ANALYZER_NORETURN;


/**
 * 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.
 *
 * @param err the Status to skip.
 *
 * note: only one concurrent skip request is allowed; call
 * debug_StopSkippingErrors before the next debug_SkipErrors.
 */
LIB_API void debug_SkipErrors(Status err);

/**
 * @return how many errors were skipped since the call to debug_SkipErrors()
 **/
LIB_API size_t debug_StopSkippingErrors();


//-----------------------------------------------------------------------------
// symbol access
//-----------------------------------------------------------------------------

namespace ERR
{
	const Status SYM_NO_STACK_FRAMES_FOUND = -100400;
	const Status SYM_UNRETRIEVABLE_STATIC  = -100401;
	const Status SYM_UNRETRIEVABLE         = -100402;
	const Status SYM_TYPE_INFO_UNAVAILABLE = -100403;
	const Status SYM_INTERNAL_ERROR        = -100404;
	const Status SYM_UNSUPPORTED           = -100405;
	const Status SYM_CHILD_NOT_FOUND       = -100406;
	// this limit is to prevent infinite recursion.
	const Status 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 Status 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 Status SYM_SUPPRESS_OUTPUT       = +100409;
}


/**
 * Maximum number of characters (including null terminator) written to
 * user's buffers by debug_ResolveSymbol.
 **/
static const size_t DEBUG_SYMBOL_CHARS = 1000;
static const size_t DEBUG_FILE_CHARS = 100;

/**
 * read and return symbol information for the given address.
 *
 * NOTE: the PDB implementation is rather slow (~500 us).
 *
 * @param ptr_of_interest address of symbol (e.g. function, variable)
 * @param sym_name optional out; holds at least DEBUG_SYMBOL_CHARS;
 *   receives symbol name returned via debug info.
 * @param file optional out; holds at least DEBUG_FILE_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 Status; INFO::OK iff any information was successfully
 * retrieved and stored.
 **/
LIB_API Status debug_ResolveSymbol(void* ptr_of_interest, wchar_t* sym_name, wchar_t* file, int* line);

static const size_t DEBUG_CONTEXT_SIZE = 2048;	// Win32 CONTEXT is currently 1232 bytes

/**
 * @param context must point to an instance of the platform-specific type
 *   (e.g. CONTEXT) or CACHE_ALIGNED storage of DEBUG_CONTEXT_SIZE bytes.
 **/
LIB_API Status debug_CaptureContext(void* context);


/**
 * write a complete stack trace (including values of local variables) into
 * the specified buffer.
 *
 * @param buf Target buffer.
 * @param maxChars Max chars of buffer (should be several thousand).
 * @param context Platform-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.
 * @param lastFuncToSkip Is 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.
 * @return Status; ERR::REENTERED if reentered via recursion or
 *		   multithreading (not allowed since static data is used).
 **/
LIB_API Status debug_DumpStack(wchar_t* buf, size_t maxChars, void* context, const wchar_t* lastFuncToSkip);


//-----------------------------------------------------------------------------
// 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).
 **/
LIB_API void debug_puts(const char* text);

/**
 * 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.
 *
 * @param context, lastFuncToSkip - see debug_DumpStack
 * @return address of the caller
 **/
LIB_API void* debug_GetCaller(void* context, const wchar_t* lastFuncToSkip);

/**
 * 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.
 **/
LIB_API int debug_IsPointerBogus(const void* p);

/// does the given pointer appear to point to code?
LIB_API bool debug_IsCodePointer(void* p);

/// does the given pointer appear to point to the stack?
LIB_API bool debug_IsStackPointer(void* p);


/**
 * inform the debugger of the current thread's name.
 *
 * (threads are easier to keep apart when they are identified by
 * name rather than TID.)
 **/
LIB_API void debug_SetThreadName(const char* name);


/**
 * holds memory for an error message.
 **/
struct ErrorMessageMem
{
	// rationale:
	// - error messages with stack traces require a good deal of memory
	//   (hundreds of KB). static buffers of that size are undesirable.
 	// - the heap may be corrupted, so don't use malloc.
	//   instead, "lib/sysdep/vm.h" functions 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 emm ErrorMessageMem*
 **/
LIB_API void debug_FreeErrorMessage(ErrorMessageMem* emm);

/**
 * 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.
 *
 * @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 context, lastFuncToSkip: see debug_DumpStack.
 * @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_FreeErrorMessage when no longer needed.
 **/
LIB_API 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, ErrorMessageMem* emm);

#endif	// #ifndef INCLUDED_DEBUG