- scan_backtrace: do bounds math in uintptr_t space and only cast to
pointer at dereference. Comparing pointers from unrelated objects
(the SP value vs the linker RAM-end symbol) is technically UB in C++
even though it works on flat-address embedded targets.
- crash_handler_log: clamp the snprintf return value before using it
as an offset. snprintf returns < 0 on error or >= size on truncation,
and feeding that straight back into pointer arithmetic is UB.
The previous gate lived only in core/defines.h, which is loaded for
static analysis / IDE support but not for actual builds. As a result
the wrap symbols (__wrap_bk_trap_udef/pabt/dabt) were preprocessed out
of crash_handler.cpp at compile time, leaving the linker without a
target for the SDK's wrapped trap calls. Set the define from
libretiny/__init__.py (where the platform-specific codegen lives —
bk72xx/__init__.py is auto-generated and not the right home).
Addresses review feedback ("unsafe assumptions about memory layout",
"its a heuristic"). The patch_bk72xx_noinit.py extra_script now also
emits PROVIDE(_esphome_{ram,flash}_{start,end}) tied directly to the
linker's MEMORY definition. crash_handler.cpp consumes those symbols
instead of hardcoding bounds, so it tracks the actual variant + board
layout (BK7231N=192KB vs others=256KB, board-specific BKOFFSET_APP /
BKRBL_SIZE_APP) without any chip-aware constants of its own.
Mirrors the linker-symbol pattern already used in
components/esp8266/crash_handler.cpp for the IROM bounds.
Mirrors the split in LibreTiny's lt_mem.c: BK7231N has 192KB RAM, every
other BK72XX variant has 256KB. The previous hardcoded 0x00440000 was
correct for everything except BK7231N, where the stack-scan upper bound
sat 64KB past the chip's physical RAM end.
Brings BK72XX to parity with esp32/esp8266/rp2040: register snapshot +
stack-scanned backtrace are captured during the SDK's exception traps
(undefined instruction / prefetch abort / data abort), persisted across
the watchdog reset in a .noinit RAM region, then logged on the next
boot via the logger and API log subscription paths.
The capture path uses linker --wrap on bk_trap_udef/pabt/dabt — the
trap symbols are defined in LibreTiny's intc.c fixup but called from
the closed-source Beken SDK across translation units, so --wrap takes
effect cleanly. The original SDK behavior (UART register dump +
bk_cpu_shutdown → watchdog reset) is preserved by tail-calling the
real handlers after capture.
A small extra_script (patch_bk72xx_noinit.py) injects a .noinit
section between .bss and _empty_ram in the generated linker script
so the crash record is not zeroed by the C runtime startup and the
heap shrinks to fit rather than overlapping it.