Pyrogenesis HEAD
Pyrogenesis, a RTS Engine
debug.h
Go to the documentation of this file.
1/* Copyright (C) 2024 Wildfire Games.
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining
4 * a copy of this software and associated documentation files (the
5 * "Software"), to deal in the Software without restriction, including
6 * without limitation the rights to use, copy, modify, merge, publish,
7 * distribute, sublicense, and/or sell copies of the Software, and to
8 * permit persons to whom the Software is furnished to do so, subject to
9 * the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included
12 * in all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 */
22
23/*
24 * platform-independent debug support code.
25 */
26
27#ifndef INCLUDED_DEBUG
28#define INCLUDED_DEBUG
29
30// this module provides platform-independent debug facilities, useful for
31// diagnosing and reporting program errors.
32// - a symbol engine provides access to compiler-generated debug information and
33// can also give a stack trace including local variables;
34// - our more powerful assert() replacement gives a stack trace so
35// that the underlying problem becomes apparent;
36// - the output routines make for platform-independent logging and
37// crashlogs with "last-known activity" reporting.
38
39#include "lib/alignment.h"
40#include "lib/code_annotation.h"
41#include "lib/code_generation.h"
42#include "lib/status.h"
43#include "lib/types.h"
44
45#include <atomic>
46
47/**
48 * trigger a breakpoint when reached/"called".
49 * if defined as a macro, the debugger can break directly into the
50 * target function instead of one frame below it as with a conventional
51 * call-based implementation.
52 **/
53#if MSC_VERSION
54# define debug_break __debugbreak // intrinsic "function"
55#else
56extern void debug_break();
57#endif
58
59
60//-----------------------------------------------------------------------------
61// output
62//-----------------------------------------------------------------------------
63
64/**
65 * write a formatted string to the debug channel, subject to filtering
66 * (see below). implemented via debug_puts - see performance note there.
67 *
68 * @param fmt Format string and varargs; see printf.
69 **/
70void debug_printf(const char* fmt, ...) PRINTF_ARGS(1);
71
72
73/**
74 * translates and displays the given strings in a dialog.
75 * this is typically only used when debug_DisplayError has failed or
76 * is unavailable because that function is much more capable.
77 * implemented via sys_display_msg; see documentation there.
78 **/
79void debug_DisplayMessage(const wchar_t* caption, const wchar_t* msg);
80
81/// flags to customize debug_DisplayError behavior
83{
84 /**
85 * disallow the Continue button. used e.g. if an exception is fatal.
86 **/
88
89 /**
90 * enable the Suppress button. set automatically by debug_DisplayError if
91 * it receives a non-NULL suppress pointer. a flag is necessary because
92 * the sys_display_error interface doesn't get that pointer.
93 * rationale for automatic setting: this may prevent someone from
94 * forgetting to specify it, and disabling Suppress despite having
95 * passed a non-NULL pointer doesn't make much sense.
96 **/
98
99 /**
100 * do not trigger a breakpoint inside debug_DisplayError; caller
101 * will take care of this if ER_BREAK is returned. this is so that the
102 * debugger can jump directly into the offending function.
103 **/
105
106 /**
107 * display just the given message; do not add any information about the
108 * call stack, do not write crashlogs, etc.
109 */
112
113/**
114 * choices offered by the error dialog that are returned
115 * by debug_DisplayError.
116 **/
118{
119 /**
120 * ignore, continue as if nothing happened.
121 * note: value doesn't start at 0 because that is interpreted as a
122 * DialogBoxParam failure.
123 **/
125
126 /**
127 * trigger breakpoint, i.e. enter debugger.
128 * only returned if DE_MANUAL_BREAK was passed; otherwise,
129 * debug_DisplayError will trigger a breakpoint itself.
130 **/
133
134/**
135 * all choices offered by the error dialog. those not defined in
136 * ErrorReaction are acted upon by debug_DisplayError and
137 * never returned to callers.
138 * (this separation avoids enumerator-not-handled warnings)
139 **/
141{
144
145 /**
146 * ignore and do not report again.
147 * note: non-persistent; only applicable during this program run.
148 **/
150
151 /**
152 * exit the program immediately.
153 **/
155
156 /**
157 * special return value for the display_error app hook stub to indicate
158 * that it has done nothing and that the normal sys_display_error
159 * implementation should be called instead.
160 **/
163
164
165/**
166 * display an error dialog with a message and stack trace.
167 *
168 * @param description text to show.
169 * @param flags: see DebugDisplayErrorFlags.
170 * @param context, lastFuncToSkip: see debug_DumpStack.
171 * @param file, line, func: location of the error (typically passed as
172 * WIDEN(__FILE__), __LINE__, __func__ from a macro)
173 * @param suppress pointer to a caller-allocated flag that can be used to
174 * suppress this error. if NULL, this functionality is skipped and the
175 * "Suppress" dialog button will be disabled.
176 * note: this flag is read and written exclusively here; caller only
177 * provides the storage.
178 * @return ErrorReaction (user's choice: continue running or stop?)
179 **/
180ErrorReaction 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);
181
182// simplified version for just displaying an error message
183#define DEBUG_DISPLAY_ERROR_IMPL(description, flags)\
184 do\
185 {\
186 CACHE_ALIGNED(u8) context[DEBUG_CONTEXT_SIZE];\
187 (void)debug_CaptureContext(context);\
188 (void)debug_DisplayError(description, flags, context, L"debug_DisplayError", WIDEN(__FILE__), __LINE__, __func__, 0);\
189 }\
190 while(0)
191
192#define DEBUG_DISPLAY_ERROR(description) DEBUG_DISPLAY_ERROR_IMPL(description, 0)
193// disallow continue for the error.
194#define DEBUG_DISPLAY_FATAL_ERROR(description) DEBUG_DISPLAY_ERROR_IMPL(description, DE_NO_CONTINUE)
195
196
197//
198// filtering
199//
200
201/**
202 * debug output is very useful, but "too much of a good thing can kill you".
203 * we don't want to require different LOGn() macros that are enabled
204 * depending on "debug level", because changing that entails lengthy
205 * compiles and it's too coarse-grained. instead, we require all
206 * strings to start with "tag_string|" (exact case and no quotes;
207 * the alphanumeric-only <tag_string> identifies output type).
208 * they are then subject to filtering: only if the tag has been
209 * "added" via debug_filter_add is the appendant string displayed.
210 *
211 * this approach is easiest to implement and is fine because we control
212 * all logging code. LIMODS falls from consideration since it's not
213 * portable and too complex.
214 *
215 * notes:
216 * - filter changes only affect subsequent debug_*printf calls;
217 * output that didn't pass the filter is permanently discarded.
218 * - strings not starting with a tag are always displayed.
219 * - debug_filter_* can be called at any time and from the debugger,
220 * but are not reentrant.
221 *
222 * in future, allow output with the given tag to proceed.
223 * no effect if already added.
224 **/
225void debug_filter_add(const char* tag);
226
227/**
228 * in future, discard output with the given tag.
229 * no effect if not currently added.
230 **/
231void debug_filter_remove(const char* tag);
232
233/**
234 * clear all filter state; equivalent to debug_filter_remove for
235 * each tag that was debug_filter_add-ed.
236 **/
237void debug_filter_clear();
238
239/**
240 * indicate if the given text would be printed.
241 * useful for a series of debug_printfs - avoids needing to add a tag to
242 * each of their format strings.
243 **/
244bool debug_filter_allows(const char* text);
245
246/**
247 * call debug_puts if debug_filter_allows allows the string.
248 **/
249void debug_puts_filtered(const char* text);
250
251/**
252 * write an error description and all logs into crashlog.txt
253 * (in unicode format).
254 *
255 * @param text description of the error (including stack trace);
256 * typically generated by debug_BuildErrorMessage.
257 *
258 * @return Status; ERR::REENTERED if reentered via recursion or
259 * multithreading (not allowed since an infinite loop may result).
260 **/
261Status debug_WriteCrashlog(const char* text);
262
263
264//-----------------------------------------------------------------------------
265// assertions
266//-----------------------------------------------------------------------------
267
268/**
269 * ensure the expression <expr> evaluates to non-zero. used to validate
270 * invariants in the program during development and thus gives a
271 * very helpful warning if something isn't going as expected.
272 * sprinkle these liberally throughout your code!
273 *
274 * to pass more information to users at runtime, you can write
275 * ENSURE(expression && "descriptive string").
276 **/
277#define ENSURE(expr)\
278 do\
279 {\
280 static std::atomic<bool> suppress__{ false };\
281 if(!(expr))\
282 {\
283 switch(debug_OnAssertionFailure(WIDEN(#expr), &suppress__, WIDEN(__FILE__), __LINE__, __func__))\
284 {\
285 case ER_CONTINUE:\
286 break;\
287 case ER_BREAK:\
288 default:\
289 debug_break();\
290 break;\
291 }\
292 }\
293 }\
294 while(0)
295
296/**
297 * same as ENSURE in debug mode, does nothing in release mode.
298 * (we don't override the `assert' macro because users may
299 * inadvertently include <assert.h> afterwards)
300 * (we do not provide an MFC-style VERIFY macro because the distinction
301 * between ENSURE and VERIFY is unclear. to always run code but only
302 * check for success in debug builds without raising unused-variable warnings,
303 * use ASSERT + UNUSED2.)
304 **/
305#define ASSERT(expr) ENSURE(expr)
306#ifdef NDEBUG
307# undef ASSERT
308# define ASSERT(expr)
309#endif
310
311/**
312 * display the error dialog with the given text. this is less error-prone than
313 * ENSURE(0 && "text"). note that "conditional expression is constant" warnings
314 * are disabled anyway.
315 *
316 * if being able to suppress the warning is desirable (e.g. for self-tests),
317 * then use DEBUG_WARN_ERR instead.
318 **/
319#define debug_warn(expr) ENSURE(0 && (expr))
320
321/**
322 * display the error dialog with text corresponding to the given error code.
323 * used by WARN_RETURN_STATUS_IF_ERR et al., which wrap function calls and automatically
324 * raise warnings and return to the caller.
325 **/
326#define DEBUG_WARN_ERR(status)\
327 do\
328 {\
329 static std::atomic<bool> suppress__{ false };\
330 switch(debug_OnError(status, &suppress__, WIDEN(__FILE__), __LINE__, __func__))\
331 {\
332 case ER_CONTINUE:\
333 break;\
334 case ER_BREAK:\
335 default:\
336 debug_break();\
337 break;\
338 }\
339 }\
340 while(0)
341
342/**
343 * called when a ENSURE/ASSERT fails;
344 * notifies the user via debug_DisplayError.
345 *
346 * @param assert_expr the expression that failed; typically passed as
347 * \#expr in the assert macro.
348 * @param suppress see debug_DisplayError.
349 * @param file, line source file name and line number of the spot that failed
350 * @param func name of the function containing it
351 * @return ErrorReaction (user's choice: continue running or stop?)
352 **/
353ErrorReaction debug_OnAssertionFailure(const wchar_t* assert_expr, std::atomic<bool>* suppress, const wchar_t* file, int line, const char* func) ANALYZER_NORETURN;
354
355/**
356 * called when a DEBUG_WARN_ERR indicates an error occurred;
357 * notifies the user via debug_DisplayError.
358 *
359 * @param err Status value indicating the error that occurred
360 * @param suppress see debug_DisplayError.
361 * @param file, line source file name and line number of the spot that failed
362 * @param func name of the function containing it
363 * @return ErrorReaction (user's choice: continue running or stop?)
364 **/
365ErrorReaction debug_OnError(Status err, std::atomic<bool>* suppress, const wchar_t* file, int line, const char* func) ANALYZER_NORETURN;
366
367
368/**
369 * suppress (prevent from showing) the error dialog from subsequent
370 * debug_OnError for the given Status.
371 *
372 * rationale: for edge cases in some functions, warnings are raised in
373 * addition to returning an error code. self-tests deliberately trigger
374 * these cases and check for the latter but shouldn't cause the former.
375 * we therefore need to squelch them.
376 *
377 * @param err the Status to skip.
378 *
379 * note: only one concurrent skip request is allowed; call
380 * debug_StopSkippingErrors before the next debug_SkipErrors.
381 */
382void debug_SkipErrors(Status err);
383
384/**
385 * @return how many errors were skipped since the call to debug_SkipErrors()
386 **/
388
389
390//-----------------------------------------------------------------------------
391// symbol access
392//-----------------------------------------------------------------------------
393
394namespace ERR
395{
398 const Status SYM_UNRETRIEVABLE = -100402;
400 const Status SYM_INTERNAL_ERROR = -100404;
401 const Status SYM_UNSUPPORTED = -100405;
403 // this limit is to prevent infinite recursion.
404 const Status SYM_NESTING_LIMIT = -100407;
405 // this limit is to prevent large symbols (e.g. arrays or linked lists)
406 // from taking up all available output space.
408}
409
410namespace INFO
411{
412 // one of the dump_sym* functions decided not to output anything at
413 // all (e.g. for member functions in UDTs - we don't want those).
414 // therefore, skip any post-symbol formatting (e.g. ) as well.
416}
417
418
419/**
420 * Maximum number of characters (including null terminator) written to
421 * user's buffers by debug_ResolveSymbol.
422 **/
423static const size_t DEBUG_SYMBOL_CHARS = 1000;
424static const size_t DEBUG_FILE_CHARS = 100;
425
426/**
427 * read and return symbol information for the given address.
428 *
429 * NOTE: the PDB implementation is rather slow (~500 us).
430 *
431 * @param ptr_of_interest address of symbol (e.g. function, variable)
432 * @param sym_name optional out; holds at least DEBUG_SYMBOL_CHARS;
433 * receives symbol name returned via debug info.
434 * @param file optional out; holds at least DEBUG_FILE_CHARS;
435 * receives base name only (no path; see rationale in wdbg_sym) of
436 * source file containing the symbol.
437 * @param line optional out; receives source file line number of symbol.
438 *
439 * note: all of the output parameters are optional; we pass back as much
440 * information as is available and desired.
441 * @return Status; INFO::OK iff any information was successfully
442 * retrieved and stored.
443 **/
444Status debug_ResolveSymbol(void* ptr_of_interest, wchar_t* sym_name, wchar_t* file, int* line);
445
446static const size_t DEBUG_CONTEXT_SIZE = 2048; // Win32 CONTEXT is currently 1232 bytes
447
448/**
449 * @param context must point to an instance of the platform-specific type
450 * (e.g. CONTEXT) or CACHE_ALIGNED storage of DEBUG_CONTEXT_SIZE bytes.
451 **/
452Status debug_CaptureContext(void* context);
453
454
455/**
456 * write a complete stack trace (including values of local variables) into
457 * the specified buffer.
458 *
459 * @param buf Target buffer.
460 * @param maxChars Max chars of buffer (should be several thousand).
461 * @param context Platform-specific representation of execution state
462 * (e.g. Win32 CONTEXT). either specify an SEH exception's
463 * context record or use debug_CaptureContext to retrieve the current state.
464 * Rationale: intermediates such as debug_DisplayError change the
465 * context, so it should be captured as soon as possible.
466 * @param lastFuncToSkip Is used for omitting error-reporting functions like
467 * debug_OnAssertionFailure from the stack trace. It is either 0 (skip nothing) or
468 * a substring of a function's name (this allows platform-independent
469 * matching of stdcall-decorated names).
470 * Rationale: this is safer than specifying a fixed number of frames,
471 * which can be incorrect due to inlining.
472 * @return Status; ERR::REENTERED if reentered via recursion or
473 * multithreading (not allowed since static data is used).
474 **/
475Status debug_DumpStack(wchar_t* buf, size_t maxChars, void* context, const wchar_t* lastFuncToSkip);
476
477
478//-----------------------------------------------------------------------------
479// helper functions (used by implementation)
480//-----------------------------------------------------------------------------
481
482/**
483 * [system-dependent] write a string to the debug channel.
484 * this can be quite slow (~1 ms)! On Windows, it uses OutputDebugString
485 * (entails context switch), otherwise stdout+fflush (waits for IO).
486 **/
487void debug_puts(const char* text);
488
489/**
490 * return the caller of a certain function on the call stack.
491 *
492 * this function is useful for recording (partial) stack traces for
493 * memory allocation tracking, etc.
494 *
495 * @param context, lastFuncToSkip - see debug_DumpStack
496 * @return address of the caller
497 **/
498void* debug_GetCaller(void* context, const wchar_t* lastFuncToSkip);
499
500/**
501 * check if a pointer appears to be totally invalid.
502 *
503 * this check is not authoritative (the pointer may be "valid" but incorrect)
504 * but can be used to filter out obviously wrong values in a portable manner.
505 *
506 * @param p pointer
507 * @return 1 if totally bogus, otherwise 0.
508 **/
509int debug_IsPointerBogus(const void* p);
510
511/// does the given pointer appear to point to code?
512bool debug_IsCodePointer(void* p);
513
514/// does the given pointer appear to point to the stack?
515bool debug_IsStackPointer(void* p);
516
517
518/**
519 * inform the debugger of the current thread's name.
520 *
521 * (threads are easier to keep apart when they are identified by
522 * name rather than TID.)
523 **/
524void debug_SetThreadName(const char* name);
525
526/**
527 * build a string describing the given error.
528 *
529 * this is a helper function used by debug_DumpStack and is made available
530 * so that the self-test doesn't have to display the error dialog.
531 *
532 * @param description: general description of the problem.
533 * @param fn_only filename (no path) of source file that triggered the error.
534 * @param line, func: exact position of the error.
535 * @param context, lastFuncToSkip: see debug_DumpStack.
536 **/
537const wchar_t* debug_BuildErrorMessage(const wchar_t* description, const wchar_t* fn_only, int line, const char* func, void* context, const wchar_t* lastFuncToSkip);
538
539#endif // #ifndef INCLUDED_DEBUG
#define PRINTF_ARGS(fmtpos)
Definition: code_annotation.h:257
#define ANALYZER_NORETURN
mark a function as noreturn for static analyzer purposes.
Definition: code_annotation.h:109
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.
Definition: bdbg.cpp:52
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.
Definition: debug.cpp:257
ErrorReactionInternal
all choices offered by the error dialog.
Definition: debug.h:141
@ ERI_NOT_IMPLEMENTED
special return value for the display_error app hook stub to indicate that it has done nothing and tha...
Definition: debug.h:161
@ ERI_CONTINUE
Definition: debug.h:142
@ ERI_EXIT
exit the program immediately.
Definition: debug.h:154
@ ERI_SUPPRESS
ignore and do not report again.
Definition: debug.h:149
@ ERI_BREAK
Definition: debug.h:143
void debug_printf(const char *fmt,...) PRINTF_ARGS(1)
write a formatted string to the debug channel, subject to filtering (see below).
Definition: debug.cpp:143
void debug_puts(const char *text)
[system-dependent] write a string to the debug channel.
Definition: udbg.cpp:104
void debug_filter_remove(const char *tag)
in future, discard output with the given tag.
Definition: debug.cpp:97
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
void debug_filter_clear()
clear all filter state; equivalent to debug_filter_remove for each tag that was debug_filter_add-ed.
Definition: debug.cpp:115
size_t debug_StopSkippingErrors()
Definition: debug.cpp:483
void debug_DisplayMessage(const wchar_t *caption, const wchar_t *msg)
translates and displays the given strings in a dialog.
Definition: debug.cpp:333
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
void debug_puts_filtered(const char *text)
call debug_puts if debug_filter_allows allows the string.
Definition: debug.cpp:157
static const size_t DEBUG_FILE_CHARS
Definition: debug.h:424
void debug_SkipErrors(Status err)
suppress (prevent from showing) the error dialog from subsequent debug_OnError for the given Status.
Definition: debug.cpp:470
Status debug_CaptureContext(void *context)
Definition: udbg.cpp:42
bool debug_IsStackPointer(void *p)
does the given pointer appear to point to the stack?
Definition: wdbg.cpp:77
void debug_break()
trigger a breakpoint when reached/"called".
Definition: udbg.cpp:48
void debug_SetThreadName(const char *name)
inform the debugger of the current thread's name.
Definition: bdbg.cpp:126
static const size_t DEBUG_SYMBOL_CHARS
Maximum number of characters (including null terminator) written to user's buffers by debug_ResolveSy...
Definition: debug.h:423
bool debug_filter_allows(const char *text)
indicate if the given text would be printed.
Definition: debug.cpp:120
ErrorReaction
choices offered by the error dialog that are returned by debug_DisplayError.
Definition: debug.h:118
@ ER_CONTINUE
ignore, continue as if nothing happened.
Definition: debug.h:124
@ ER_BREAK
trigger breakpoint, i.e.
Definition: debug.h:131
static const size_t DEBUG_CONTEXT_SIZE
Definition: debug.h:446
DebugDisplayErrorFlags
flags to customize debug_DisplayError behavior
Definition: debug.h:83
@ DE_MANUAL_BREAK
do not trigger a breakpoint inside debug_DisplayError; caller will take care of this if ER_BREAK is r...
Definition: debug.h:104
@ DE_NO_CONTINUE
disallow the Continue button.
Definition: debug.h:87
@ DE_ALLOW_SUPPRESS
enable the Suppress button.
Definition: debug.h:97
@ DE_NO_DEBUG_INFO
display just the given message; do not add any information about the call stack, do not write crashlo...
Definition: debug.h:110
Status debug_WriteCrashlog(const char *text)
write an error description and all logs into crashlog.txt (in unicode format).
bool debug_IsCodePointer(void *p)
does the given pointer appear to point to code?
Definition: wdbg.cpp:62
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
int debug_IsPointerBogus(const void *p)
check if a pointer appears to be totally invalid.
Definition: udbg.cpp:112
void * debug_GetCaller(void *context, const wchar_t *lastFuncToSkip)
return the caller of a certain function on the call stack.
Definition: bdbg.cpp:38
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.
Definition: bdbg.cpp:99
void debug_filter_add(const char *tag)
debug output is very useful, but "too much of a good thing can kill you".
Definition: debug.cpp:78
Definition: debug.h:395
const Status SYM_UNRETRIEVABLE
Definition: debug.h:398
const Status SYM_UNRETRIEVABLE_STATIC
Definition: debug.h:397
const Status SYM_CHILD_NOT_FOUND
Definition: debug.h:402
const Status SYM_TYPE_INFO_UNAVAILABLE
Definition: debug.h:399
const Status SYM_NO_STACK_FRAMES_FOUND
Definition: debug.h:396
const Status SYM_NESTING_LIMIT
Definition: debug.h:404
const Status SYM_INTERNAL_ERROR
Definition: debug.h:400
const Status SYM_UNSUPPORTED
Definition: debug.h:401
const Status SYM_SINGLE_SYMBOL_LIMIT
Definition: debug.h:407
Definition: debug.h:411
const Status SYM_SUPPRESS_OUTPUT
Definition: debug.h:415
i64 Status
Error handling system.
Definition: status.h:173