Paper Mario DX
Paper Mario (N64) modding
 
Loading...
Searching...
No Matches
backtrace.c
Go to the documentation of this file.
1// Backtrace (call stack) support.
2// Heavily based on libdragon: https://github.com/DragonMinded/libdragon/blob/trunk/src/backtrace.c
3
4#include "common.h"
5#include "gcc/string.h"
6#include "nu/nusys.h"
7#include "backtrace.h"
8#include "PR/osint.h"
9
11#define BACKTRACE_DEBUG 0
12
14#define FUNCTION_ALIGNMENT 32
15
16typedef s64 int64_t;
17typedef s32 int32_t;
18typedef s16 int16_t;
19typedef s8 int8_t;
20typedef u64 uint64_t;
21typedef u32 uint32_t;
22typedef u16 uint16_t;
23typedef u8 uint8_t;
24
25typedef s32 bool;
26#define true 1
27#define false 0
28
36
44
45#define MIPS_OP_ADDIU_SP(op) (((op) & 0xFFFF0000) == 0x27BD0000)
46#define MIPS_OP_DADDIU_SP(op) (((op) & 0xFFFF0000) == 0x67BD0000)
47#define MIPS_OP_JR_RA(op) (((op) & 0xFFFFFFFF) == 0x03E00008)
48#define MIPS_OP_SD_RA_SP(op) (((op) & 0xFFFF0000) == 0xFFBF0000)
49#define MIPS_OP_SW_RA_SP(op) (((op) & 0xFFFF0000) == 0xAFBF0000)
50#define MIPS_OP_SD_FP_SP(op) (((op) & 0xFFFF0000) == 0xFFBE0000)
51#define MIPS_OP_SW_FP_SP(op) (((op) & 0xFFFF0000) == 0xAFBE0000)
52#define MIPS_OP_LUI_GP(op) (((op) & 0xFFFF0000) == 0x3C1C0000)
53#define MIPS_OP_NOP(op) ((op) == 0x00000000)
54#define MIPS_OP_MOVE_FP_SP(op) ((op) == 0x03A0F025)
55
56#define debugf osSyncPrintf
57
58bool __bt_analyze_func(bt_func_t *func, uint32_t *ptr, uint32_t func_start, bool from_exception);
59
65static u32 get_physical_address(u32 addr) {
66 return 0x80000000 | osVirtualToPhysical((void*)addr);
67}
68
70static bool is_valid_address(uint32_t addr) {
71 addr = get_physical_address(addr);
72 return addr >= 0x80000400 && addr < 0x80800000 && (addr & 3) == 0;
73}
74
75static void backtrace_foreach(void (*cb)(void *arg, void *ptr), void *arg) {
76 /*
77 * This function is called in very risky contexts, for instance as part of an exception
78 * handler or during an assertion. We try to always provide as much information as
79 * possible in these cases, with graceful degradation if something more elaborate cannot
80 * be extracted. Thus, this function:
81 *
82 * * Must not use malloc(). The heap might be corrupted or empty.
83 * * Must not use assert(), because that might trigger recursive assertions.
84 * * Must avoid raising exceptions. Specifically, it must avoid risky memory accesses
85 * to wrong addresses.
86 */
87
88 uint32_t* exception_ra;
89 uint32_t func_start;
90
91 // Current value of SP/RA/FP registers.
92 uint32_t *sp, *ra, *fp;
93 asm volatile (
94 "move %0, $ra\n"
95 "move %1, $sp\n"
96 "move %2, $fp\n"
97 : "=r"(ra), "=r"(sp), "=r"(fp)
98 );
99
100 #if BACKTRACE_DEBUG
101 debugf("backtrace: start\n");
102 #endif
103
104 exception_ra = NULL; // If != NULL,
105 func_start = 0; // Start of the current function (when known)
106
107 // Start from the backtrace function itself. Put the start pointer somewhere after the initial
108 // prolog (eg: 64 instructions after start), so that we parse the prolog itself to find sp/fp/ra offsets.
109 ra = (uint32_t*)backtrace_foreach + 64;
110
111 while (1) {
112 // Analyze the function pointed by ra, passing information about the previous exception frame if any.
113 // If the analysis fail (for invalid memory accesses), stop right away.
114 bt_func_t func;
115 if (!__bt_analyze_func(&func, ra, func_start, (bool)exception_ra))
116 return;
117
118 #if BACKTRACE_DEBUG
119 debugf("backtrace: %s, ra=%p, sp=%p, fp=%p ra_offset=%d, fp_offset=%d, stack_size=%d\n",
120 func.type == BT_FUNCTION ? "BT_FUNCTION" : (func.type == BT_EXCEPTION ? "BT_EXCEPTION" : (func.type == BT_FUNCTION_FRAMEPOINTER ? "BT_FRAMEPOINTER" : "BT_LEAF")),
121 ra, sp, fp, func.ra_offset, func.fp_offset, func.stack_size);
122 #endif
123
124 switch (func.type) {
126 if (!func.fp_offset) {
127 debugf("backtrace: framepointer used but not saved onto stack at %p\n", ra);
128 } else {
129 // Use the frame pointer to refer to the current frame.
130 sp = fp;
131 if (!is_valid_address((uint32_t)sp)) {
132 debugf("backtrace: interrupted because of invalid frame pointer 0x%08lx\n", (uint32_t)sp);
133 return;
134 }
135 }
136 // FALLTHROUGH!
137 case BT_FUNCTION:
138 if (func.fp_offset)
139 fp = *(uint32_t**)((uint32_t)sp + func.fp_offset);
140 ra = *(uint32_t**)((uint32_t)sp + func.ra_offset) - 2;
141 sp = (uint32_t*)((uint32_t)sp + func.stack_size);
142 exception_ra = NULL;
143 func_start = 0;
144 break;
145 /*
146 case BT_EXCEPTION: {
147 // Exception frame. We must return back to EPC, but let's keep the
148 // RA value. If the interrupted function is a leaf function, we
149 // will need it to further walk back.
150 // Notice that FP is a callee-saved register so we don't need to
151 // recover it from the exception frame (also, it isn't saved there
152 // during interrupts).
153 exception_ra = *(uint32_t**)((uint32_t)sp + func.ra_offset);
154
155 // reg_block_t = __OSThreadContext ?
156
157 // Read EPC from exception frame and adjust it with CAUSE BD bit
158 ra = *(uint32_t**)((uint32_t)sp + offsetof(reg_block_t, epc) + 32);
159 uint32_t cause = *(uint32_t*)((uint32_t)sp + offsetof(reg_block_t, cr) + 32);
160 if (cause & C0_CAUSE_BD) ra++;
161
162 sp = (uint32_t*)((uint32_t)sp + func.stack_size);
163
164 // Special case: if the exception is due to an invalid EPC
165 // (eg: a null function pointer call), we can rely on RA to get
166 // back to the caller. This assumes that we got there via a function call
167 // rather than a raw jump, but that's a reasonable assumption. It's anyway
168 // the best we can do.
169 if ((C0_GET_CAUSE_EXC_CODE(cause) == EXCEPTION_CODE_TLB_LOAD_I_MISS ||
170 C0_GET_CAUSE_EXC_CODE(cause) == EXCEPTION_CODE_LOAD_I_ADDRESS_ERROR) &&
171 !is_valid_address((uint32_t)ra)) {
172
173 // Store the invalid address in the backtrace, so that it will appear in dumps.
174 // This makes it easier for the user to understand the reason for the exception.
175 cb(arg, ra);
176 #if BACKTRACE_DEBUG
177 debugf("backtrace: %s, ra=%p, sp=%p, fp=%p ra_offset=%d, fp_offset=%d, stack_size=%d\n",
178 "BT_INVALID", ra, sp, fp, func.ra_offset, func.fp_offset, func.stack_size);
179 #endif
180
181 ra = exception_ra - 2;
182
183 // The function that jumped into an invalid PC was not interrupted by the exception: it
184 // is a regular function
185 // call now.
186 exception_ra = NULL;
187 break;
188 }
189
190 // The next frame might be a leaf function, for which we will not be able
191 // to find a stack frame. It is useful to try finding the function start.
192 // Try to open the symbol table: if we find it, we can search for the start
193 // address of the function.
194 symtable_header_t symt = symt_open();
195 if (symt.head[0]) {
196 int idx;
197 addrtable_entry_t entry = symt_addrtab_search(&symt, (uint32_t)ra, &idx);
198 while (!ADDRENTRY_IS_FUNC(entry))
199 entry = symt_addrtab_entry(&symt, --idx);
200 func_start = ADDRENTRY_ADDR(entry);
201 #if BACKTRACE_DEBUG
202 debugf("Found interrupted function start address: %08lx\n", func_start);
203 #endif
204 }
205 } break;
206 */
207 case BT_LEAF:
208 ra = exception_ra - 2;
209 // A leaf function has no stack. On the other hand, an exception happening at the
210 // beginning of a standard function (before RA is saved), does have a stack but
211 // will be marked as a leaf function. In this case, we mus update the stack pointer.
212 sp = (uint32_t*)((uint32_t)sp + func.stack_size);
213 exception_ra = NULL;
214 func_start = 0;
215 break;
216 }
217
218 if (is_valid_address((uint32_t)ra)) {
219 // Call the callback with this stack frame
220 cb(arg, ra);
221 }
222 }
223}
224
225static void backtrace_foreach_foreign(void (*cb)(void *arg, void *ptr), void *arg, uint32_t *sp, uint32_t *ra, uint32_t *fp) {
226 uint32_t* exception_ra;
227 uint32_t func_start;
228
229 exception_ra = NULL; // If != NULL,
230 func_start = 0; // Start of the current function (when known)
231
232 while (1) {
233 // Analyze the function pointed by ra, passing information about the previous exception frame if any.
234 // If the analysis fail (for invalid memory accesses), stop right away.
235 bt_func_t func;
236 if (!__bt_analyze_func(&func, ra, func_start, (bool)exception_ra))
237 return;
238
239 #if BACKTRACE_DEBUG
240 debugf("backtrace: %s, ra=%p, sp=%p, fp=%p ra_offset=%d, fp_offset=%d, stack_size=%d\n",
241 func.type == BT_FUNCTION ? "BT_FUNCTION" : (func.type == BT_EXCEPTION ? "BT_EXCEPTION" : (func.type == BT_FUNCTION_FRAMEPOINTER ? "BT_FRAMEPOINTER" : "BT_LEAF")),
242 ra, sp, fp, func.ra_offset, func.fp_offset, func.stack_size);
243 #endif
244
245 switch (func.type) {
247 if (!func.fp_offset) {
248 debugf("backtrace: framepointer used but not saved onto stack at %p\n", ra);
249 } else {
250 // Use the frame pointer to refer to the current frame.
251 sp = fp;
252 if (!is_valid_address((uint32_t)sp)) {
253 debugf("backtrace: interrupted because of invalid frame pointer 0x%08lx\n", (uint32_t)sp);
254 return;
255 }
256 }
257 // FALLTHROUGH!
258 case BT_FUNCTION:
259 if (func.fp_offset)
260 fp = *(uint32_t**)((uint32_t)sp + func.fp_offset);
261 ra = *(uint32_t**)((uint32_t)sp + func.ra_offset) - 2;
262 sp = (uint32_t*)((uint32_t)sp + func.stack_size);
263 exception_ra = NULL;
264 func_start = 0;
265 break;
266 case BT_LEAF:
267 ra = exception_ra - 2;
268 // A leaf function has no stack. On the other hand, an exception happening at the
269 // beginning of a standard function (before RA is saved), does have a stack but
270 // will be marked as a leaf function. In this case, we mus update the stack pointer.
271 sp = (uint32_t*)((uint32_t)sp + func.stack_size);
272 exception_ra = NULL;
273 func_start = 0;
274 break;
275 }
276
277 if (is_valid_address((uint32_t)ra)) {
278 // Call the callback with this stack frame
279 cb(arg, ra);
280 }
281 }
282}
283
285 void **buffer;
286 int size;
287 int i;
288};
289
290static void backtrace_cb(void *arg, void *ptr) {
291 struct backtrace_cb_ctx *ctx = arg;
292 if (ctx->i >= 0 && ctx->i < ctx->size)
293 ctx->buffer[ctx->i] = ptr;
294 if (ctx->i < ctx->size)
295 ctx->i++;
296}
297
298int backtrace(void **buffer, int size) {
299 struct backtrace_cb_ctx ctx = {
300 buffer,
301 size,
302 -1, // skip backtrace itself
303 };
304 backtrace_foreach(backtrace_cb, &ctx);
305 return ctx.i;
306}
307
308int backtrace_thread(void **buffer, int size, OSThread *thread) {
309 struct backtrace_cb_ctx ctx = {
310 buffer,
311 size,
312 0,
313 };
314 u32 sp = (u32)thread->context.sp;
315 u32 pc = (u32)thread->context.pc;
316 u32 fp = (u32)thread->context.s8;
317 backtrace_cb(&ctx, (void*)pc);
318 backtrace_foreach_foreign(backtrace_cb, &ctx, (uint32_t*)sp, (uint32_t*)pc, (uint32_t*)fp);
319 return ctx.i;
320}
321
331s32 address2symbol(u32 address, Symbol* out) {
332 #define symbolsPerChunk 0x1000
333 #define chunkSize ((sizeof(Symbol) * symbolsPerChunk))
334
335 static u32 romHeader[0x10];
336 nuPiReadRom(0, &romHeader, sizeof(romHeader));
337
338 u32 symbolTableRomAddr = romHeader[SYMBOL_TABLE_PTR_ROM_ADDR / sizeof(*romHeader)];
339 if (symbolTableRomAddr == NULL) {
340 osSyncPrintf("address2symbol: no symbols available (SYMBOL_TABLE_PTR is NULL)\n");
341 return -1;
342 }
343
344 // Read the header
345 SymbolTable symt;
346 nuPiReadRom(symbolTableRomAddr, &symt, sizeof(SymbolTable));
347 if (symt.magic[0] != 'S' || symt.magic[1] != 'Y' || symt.magic[2] != 'M' || symt.magic[3] != 'S') {
348 osSyncPrintf("address2symbol: no symbols available (invalid magic '%c%c%c%c')\n", symt.magic[0], symt.magic[1], symt.magic[2], symt.magic[3]);
349 return -1;
350 }
351 if (symt.symbolCount <= 0) {
352 osSyncPrintf("address2symbol: no symbols available (symbolCount=%d)\n", symt.symbolCount);
353 return -1;
354 }
355
356 // Read symbols in chunks
357 static Symbol chunk[chunkSize];
358 s32 i;
359 for (i = 0; i < symt.symbolCount; i++) {
360 // Do we need to load the next chunk?
361 if (i % symbolsPerChunk == 0) {
362 u32 chunkAddr = symbolTableRomAddr + sizeof(SymbolTable) + (i / symbolsPerChunk) * chunkSize;
363 nuPiReadRom(chunkAddr, chunk, chunkSize);
364 }
365
366 Symbol sym = chunk[i % symbolsPerChunk];
367
368 if (sym.address == address) {
369 *out = sym;
370 return 0;
371 } else if (address < sym.address) {
372 // Symbols are sorted by address, so if we passed the address, we can stop
373 break;
374 } else {
375 // Keep searching, but remember this as the last symbol
376 // incase we don't find an exact match
377 *out = sym;
378 }
379 }
380 return address - out->address;
381}
382
383char* load_symbol_string(char* dest, u32 addr, int n) {
384 if (addr == NULL) {
385 return NULL;
386 }
387
388 u32 aligned = addr & ~3;
389 nuPiReadRom(aligned, dest, n);
390 dest[n-1] = '\0'; // Ensure null-termination
391
392 // Shift to start of string
393 return (char*)((u32)dest + (addr & 3));
394}
395
396void backtrace_address_to_string(u32 address, char* dest) {
397 Symbol sym;
398 s32 offset = address2symbol(address, &sym);
399
400 if (offset >= 0 && offset < 0x1000) { // 0x1000 = arbitrary func size limit
401 char name[0x40];
402 char file[0x40];
403 char* namep = load_symbol_string(name, sym.nameOffset, ARRAY_COUNT(name));
404 char* filep = load_symbol_string(file, sym.fileOffset, ARRAY_COUNT(file));
405
406 offset = 0; // Don't show offsets
407
408 if (filep == NULL)
409 if (offset == 0)
410 sprintf(dest, "%s", namep);
411 else
412 sprintf(dest, "%s+0x%X", namep, offset);
413 else
414 if (offset == 0)
415 sprintf(dest, "%s (%s)", namep, filep);
416 else
417 sprintf(dest, "%s (%s+0x%X)", namep, filep, offset);
418 } else {
419 sprintf(dest, "%p", address);
420 }
421}
422
423void debug_backtrace(void) {
424 s32 bt[32];
425 s32 max = backtrace((void**)bt, ARRAY_COUNT(bt));
426 s32 i;
427 char buf[128];
428
429 osSyncPrintf("Backtrace:\n");
430 for (i = 0; i < max; i++) {
432 osSyncPrintf(" %s\n", buf);
433 }
434}
435
490bool __bt_analyze_func(bt_func_t *func, uint32_t *ptr, uint32_t func_start, bool from_exception) {
491 // exceptasm.s
492 #define inthandler ((uint32_t*)0x8006A9F0)
493 #define inthandler_end ((uint32_t*)0x8006B35C)
494
495 uint32_t addr;
496
497 *func = (bt_func_t){
498 .type = (ptr >= inthandler && ptr < inthandler_end) ? BT_EXCEPTION : BT_FUNCTION,
499 .stack_size = 0, .ra_offset = 0, .fp_offset = 0
500 };
501
502 addr = (uint32_t)ptr;
503 while (1) {
504 uint32_t op;
505
506 // Validate that we can dereference the virtual address without raising an exception
507 if (!is_valid_address(addr)) {
508 // This address is invalid, probably something is corrupted. Avoid looking further.
509 osSyncPrintf("backtrace: interrupted because of invalid return address 0x%08x\n", addr);
510 return false;
511 }
512 op = *(uint32_t*)get_physical_address(addr);
513 if (MIPS_OP_ADDIU_SP(op) || MIPS_OP_DADDIU_SP(op)) {
514 // Extract the stack size only from the start of the function, where the
515 // stack is allocated (negative value). This is important because the RA
516 // could point to a leaf basis block at the end of the function (like in the
517 // assert case), and if we picked the positive ADDIU SP at the end of the
518 // proper function body, we might miss a fp_offset.
519 if (op & 0x8000)
520 func->stack_size = -(int16_t)(op & 0xFFFF);
521 } else if (MIPS_OP_SD_RA_SP(op)) {
522 func->ra_offset = (int16_t)(op & 0xFFFF) + 4; // +4 = load low 32 bit of RA
523 // If we found a stack size, it might be a red herring (an alloca); we need one
524 // happening "just before" sd ra,xx(sp)
525 func->stack_size = 0;
526 } else if (MIPS_OP_SW_RA_SP(op)) {
527 // 32-bit version of above
528 func->ra_offset = (int16_t)(op & 0xFFFF);
529 func->stack_size = 0;
530 } else if (MIPS_OP_SD_FP_SP(op)) {
531 func->fp_offset = (int16_t)(op & 0xFFFF) + 4; // +4 = load low 32 bit of FP
532 } else if (MIPS_OP_SW_FP_SP(op)) {
533 // 32-bit version of above
534 func->fp_offset = (int16_t)(op & 0xFFFF);
535 } else if (MIPS_OP_LUI_GP(op)) {
536 // Loading gp is commonly done in _start, so it's useless to go back more
537 return false;
538 } else if (MIPS_OP_MOVE_FP_SP(op)) {
539 // This function uses the frame pointer. Uses that as base of the stack.
540 // Even with -fomit-frame-pointer (default on our toolchain), the compiler
541 // still emits a framepointer for functions using a variable stack size
542 // (eg: using alloca() or VLAs).
544 }
545 // We found the stack frame size and the offset of the return address in the stack frame
546 // We can stop looking and process the frame
547 if (func->stack_size != 0 && func->ra_offset != 0)
548 break;
549 if (from_exception) {
550 // The function we are analyzing was interrupted by an exception, so it might
551 // potentially be a leaf function (no stack frame). We need to make sure to stop
552 // at the beginning of the function and mark it as leaf function. Use
553 // func_start if specified, or try to guess using the nops used to align the function
554 // (crossing fingers that they're there).
555 if (addr == func_start) {
556 // The frame that was interrupted by an interrupt handler is a special case: the
557 // function could be a leaf function with no stack. If we were able to identify
558 // the function start (via the symbol table) and we reach it, it means that
559 // we are in a real leaf function.
560 func->type = BT_LEAF;
561 break;
562 } else if (!func_start && MIPS_OP_NOP(op) && (addr + 4) % FUNCTION_ALIGNMENT == 0) {
563 // If we are in the frame interrupted by an interrupt handler, and we does not know
564 // the start of the function (eg: no symbol table), then try to stop by looking for
565 // a NOP that pads between functions. Obviously the NOP we find can be either a false
566 // positive or a false negative, but we can't do any better without symbols.
567 func->type = BT_LEAF;
568 break;
569 }
570 }
571 addr -= 4;
572 }
573 return true;
574}
#define MIPS_OP_LUI_GP(op)
Matches: lui $gp, imm.
Definition backtrace.c:52
s32 int32_t
Definition backtrace.c:17
int backtrace(void **buffer, int size)
Walk the stack and return the current call stack.
Definition backtrace.c:298
#define MIPS_OP_DADDIU_SP(op)
Matches: daddiu $sp, $sp, imm.
Definition backtrace.c:46
u32 uint32_t
Definition backtrace.c:21
#define MIPS_OP_SD_FP_SP(op)
Matches: sd $fp, imm($sp)
Definition backtrace.c:50
char * load_symbol_string(char *dest, u32 addr, int n)
Definition backtrace.c:383
#define debugf
Definition backtrace.c:56
#define inthandler_end
s8 int8_t
Definition backtrace.c:19
#define symbolsPerChunk
void debug_backtrace(void)
Print a backtrace.
Definition backtrace.c:423
bool __bt_analyze_func(bt_func_t *func, uint32_t *ptr, uint32_t func_start, bool from_exception)
Analyze a function to find out its stack frame layout and properties (useful for backtracing).
Definition backtrace.c:490
s16 int16_t
Definition backtrace.c:18
int stack_size
Size of the stack frame.
Definition backtrace.c:40
int backtrace_thread(void **buffer, int size, OSThread *thread)
Definition backtrace.c:308
void backtrace_address_to_string(u32 address, char *dest)
Converts a function address to a string representation using its name, offset, and file.
Definition backtrace.c:396
u16 uint16_t
Definition backtrace.c:22
#define chunkSize
int ra_offset
Offset of the return address from the top of the stack frame.
Definition backtrace.c:41
u64 uint64_t
Definition backtrace.c:20
#define MIPS_OP_SD_RA_SP(op)
Matches: sd $ra, imm($sp)
Definition backtrace.c:48
s32 address2symbol(u32 address, Symbol *out)
Uses the symbol table to look up the symbol corresponding to the given address.
Definition backtrace.c:331
s32 bool
Definition backtrace.c:25
bt_func_type
The "type" of funciton as categorized by the backtrace heuristic (__bt_analyze_func)
Definition backtrace.c:30
@ BT_FUNCTION_FRAMEPOINTER
The function uses the register fp as frame pointer (normally, this happens only when the function use...
Definition backtrace.c:32
@ BT_LEAF
Leaf function (no calls), no stack frame allocated, sp/ra not modified.
Definition backtrace.c:34
@ BT_EXCEPTION
This is an exception handler (inthandler.S)
Definition backtrace.c:33
@ BT_FUNCTION
Regular function with a stack frame.
Definition backtrace.c:31
#define MIPS_OP_SW_RA_SP(op)
Matches: sw $ra, imm($sp)
Definition backtrace.c:49
#define MIPS_OP_NOP(op)
Matches: nop.
Definition backtrace.c:53
int fp_offset
Offset of the saved fp from the top of the stack frame; this is != 0 only if the function modifies fp...
Definition backtrace.c:42
u8 uint8_t
Definition backtrace.c:23
#define MIPS_OP_ADDIU_SP(op)
Matches: addiu $sp, $sp, imm.
Definition backtrace.c:45
bt_func_type type
Type of the function.
Definition backtrace.c:39
s64 int64_t
Definition backtrace.c:16
#define MIPS_OP_SW_FP_SP(op)
Matches: sw $fp, imm($sp)
Definition backtrace.c:51
#define inthandler
#define MIPS_OP_MOVE_FP_SP(op)
Matches: move $fp, $sp.
Definition backtrace.c:54
#define FUNCTION_ALIGNMENT
Function alignment enfored by the compiler (-falign-functions).
Definition backtrace.c:14
Description of a function for the purpose of backtracing (filled by __bt_analyze_func)
Definition backtrace.c:38
u32 fileOffset
Offset of the file name and line string.
Definition backtrace.h:17
#define SYMBOL_TABLE_PTR_ROM_ADDR
ROM address of the pointer to the symbol table.
Definition backtrace.h:12
u32 address
RAM address.
Definition backtrace.h:15
u32 nameOffset
Offset of the symbol name string.
Definition backtrace.h:16
char magic[4]
Definition backtrace.h:21
u32 symbolCount
Definition backtrace.h:22
void osSyncPrintf(const char *fmt,...)
Definition is_debug.c:32
#define ARRAY_COUNT(arr)
Definition macros.h:40