backtrace.c (1971B)
1 /* 2 * __kit_backtrace -- freestanding call-stack capture via the frame-pointer 3 * chain. See rt/include/kit/backtrace.h for the contract and doc/plan/ 4 * BACKTRACE.md for the design. 5 * 6 * kit keeps a frame pointer on every backend and never omits it, so each 7 * prologue stores {saved_fp, saved_ra} and anchors the frame pointer at the 8 * record. The layout is uniform across all kit targets: 9 * 10 * fp[0] = caller's frame pointer (saved fp) 11 * fp[1] = return address (saved ra) 12 * 13 * Indexing a void** scales by the target pointer width, so the same two 14 * indices are correct on 32- and 64-bit targets alike — no per-arch offset 15 * table, no #ifdef cascade (per the RUNTIME.md "no target-dispatch ifdef" 16 * rule). __builtin_frame_address(0) marks __kit_backtrace as reading its own 17 * frame, which forces it to keep a valid record (it never degrades to a 18 * frameless leaf), so the seed frame is always walkable. 19 */ 20 #include <kit/backtrace.h> 21 22 int __kit_backtrace(void** buf, int max, int skip) { 23 void** fp; 24 int n = 0; 25 if (!buf || max <= 0) return 0; 26 if (skip < 0) skip = 0; 27 28 fp = (void**)__builtin_frame_address(0); 29 while (fp) { 30 void** next = (void**)fp[0]; /* saved caller frame pointer */ 31 void* ra = fp[1]; /* saved return address */ 32 33 /* A real frame always has a non-null return address; a null one is the 34 * synthetic stack origin (_start / runtime entry zeroes the record), so 35 * stop without recording it. */ 36 if (!ra) break; 37 38 if (skip > 0) { 39 --skip; 40 } else { 41 if (n >= max) break; 42 buf[n++] = ra; 43 } 44 45 /* The stack grows down, so a valid caller frame sits strictly above this 46 * one. A null, non-increasing, or misaligned link is the chain terminator 47 * (or garbage) — stop, which also bounds a runaway walk. */ 48 if (next <= fp) break; 49 if (((unsigned long)(void*)next & (sizeof(void*) - 1u)) != 0u) break; 50 fp = next; 51 } 52 return n; 53 }