Reduce preferences normal region from 96 to 78 words (words 32-109)
to formally reserve words 110-127 for the crash handler backtrace.
Preferences that don't fit in RTC fall back to flash storage, so
this has no functional impact for typical configurations.
- Add IRAM_ATTR to is_code_addr/recover_code_addr as safety net
in case the compiler doesn't inline them
- Restore EXCVADDR logging for exception resets (faulting address
is the key diagnostic for LoadProhibit/StoreProhibit)
- Add comment noting Xtensa stack pointer alignment assumption
- Match Arduino core's declaration of _irom0_text_start/_end as
void functions (mmu_iram.h declares them this way)
- Use C-style casts with NOLINT for int-to-ptr conversions in
custom_crash_callback (clang-tidy performance-no-int-to-ptr)
The ESPHome CLI already decodes addresses inline (shown as
WARNING Decoded lines). The hint was redundant and cost ~100
bytes of flash plus 220 bytes of stack for the format buffer.
The LX106 (ESP8266) has no MMU, TLB, PIF, or privilege levels, so
exception causes 1, 5, 7, 8, 12-18, and 24-26 can never occur.
Remove them and shorten remaining cause names. Also drop Syscall,
Alloca, PCValue, Privileged which are unreachable on this core.
GCC's ROM divide routine triggers IllegalInstruction (exccause=0) at
ROM addresses 0x4000dce5/0x4000dd3d instead of IntegerDivideByZero
(exccause=6). Patch exccause to match the Arduino core's postmortem
handler behavior.
WDT resets also populate exccause in rst_info (e.g. Level1Interrupt
for stack overflow soft WDT). Show the cause string for all crash
reasons, not just REASON_EXCEPTION_RST. Also simplify PC logging
to a single line for all crash types.
resetInfo is a global that persists until next reset — no need to
cache the crash validity in a separate bool. Check resetInfo.reason
directly in crash_handler_has_data() and crash_handler_log().
This eliminates the only RAM byte from the crash handler and removes
the now-empty crash_handler_read_and_clear() function and its call
from arch_init().
Remove EPC2/EPC3 (always 0 on LX106, no level 2/3 interrupts) and
DEPC (extremely rare double exception). Combine PC and EXCVADDR into
one log line for exception resets. Shrink hint buffer 256->220.
Saves ~36 bytes flash in crash_handler_log.
Switch statements generate CSWTCH jump tables in RAM on ESP8266.
PROGMEM_STRING_TABLE causes LoadStoreError from flash cache conflicts
in API subscribe paths. If-else with LOG_STR avoids both: strings
stay in flash via PSTR, and comparison branches don't need a data
table. Zero RAM overhead confirmed via nm.
Reading IROM addresses from IROM code causes LoadStoreError on ESP8266
due to the direct-mapped flash cache — the reading code and target
address can share a cache line, evicting the function mid-execution.
Remove is_return_addr() and rely on linker-symbol IROM bounds
(_irom0_text_start/_end) to eliminate false positives instead. This
is less precise but crash-safe.
Two issues:
1. safe_read_code_byte used volatile cast which prevented the compiler
from using flash-safe l32i instructions, causing LoadStoreError on
IROM addresses. Replace with progmem_read_byte.
2. IROM upper bound (0x40400000 = 2MB) was too generous for devices
with smaller flash (e.g. 1MB), causing both false positives in the
backtrace and faults when is_return_addr tried to verify CALL
instructions at unmapped addresses. Use _irom0_text_start/end
linker symbols for precise firmware code bounds.
GCC generates CSWTCH jump tables in RAM rodata on ESP8266 for switch
statements. Convert exception cause and reset reason lookups to
PROGMEM_STRING_TABLE which keeps data in flash. Exception causes are
split into two tables (0-9 and 12-29) to stay under the 255-byte
blob limit. Added static_asserts on reset reason enum values.
- Verify each candidate is a real return address by checking that the
instruction at addr-3 is a CALL/CALLX (Xtensa 3-byte call opcodes).
This filtering happens at log time when flash is readable, similar
to the ESP32 RISC-V handler's is_return_addr() approach.
- Scan the entire stack instead of just 64 words — with filtering,
false positives from stale stack data are eliminated.
- Skip epc1 during scanning (already reported as fault PC).
- Increase to 16 RTC slots (72 bytes) for broader capture before
filtering. Bump hint buffer to 256 for more addresses.
A null EXCVADDR (0x00000000) is the key diagnostic for null pointer
crashes (LoadProhibited/StoreProhibited). Suppressing it when zero
hides the most useful information.
Add a crash handler for ESP8266 that captures and logs crash data
from previous boots, matching the existing ESP32 and RP2040
implementations. Uses the SDK's rst_info (always available after
reboot) for basic crash info and Arduino's custom_crash_callback
to scan the stack for return addresses stored in RTC user memory.
On RP2040, the core0 stack lives in a 4KB SCRATCH_Y RAM bank. The Noise
protocol handshake calls into curve25519 scalar multiplication which
needs ~2KB+ of stack. When all handshake state branches lived in a
single function, the compiler allocated stack space for all branches
simultaneously (SERVER_HELLO msg[46]+mac[13], HANDSHAKE buffer[65],
NoiseBuffer, etc.), leaving insufficient headroom for the crypto path.
Split state_action_() into per-state methods so each state's locals
occupy separate stack frames. The crypto path (WRITE_MESSAGE) now only
carries its own 85 bytes of locals instead of ~200+ bytes from all
branches combined.
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Switch to blocking mode before writing the feature ACK byte in the
no-password path. lwIP may not transmit data written in non-blocking
mode when the socket immediately switches to blocking and blocks in
recv(). Writing the feature ACK in blocking mode ensures it is flushed
to the wire before entering the blocking data transfer phase.
Add delay(0) after switching to blocking mode in handle_data_() to
flush any pending non-blocking writes (the feature ACK byte) before
entering the blocking read loop. lwIP may not transmit data written
in non-blocking mode until the task yields.