Remove the redundant s_socket_woke = false between the early-return
check and the while loop. If an IRQ fires in that window (recv_fn
sets s_socket_woke = true), clearing the flag would lose the wake
and sleep until the timer fires. Now the while loop sees the flag
immediately and exits. The flag is cleared after the loop instead.
Replace read_locked_() approach with a simpler wait_for_data_() called
at the top of both read() and readv(), keeping the original read/readv
structure intact.
- Extract read_locked_() to avoid holding LWIP_LOCK during socket_delay(),
which would block recv_fn() on RP2040 (needs async_context lock)
- Loop around socket_delay() for remaining time on spurious wakes from
other sockets, ensuring SO_RCVTIMEO semantics are correct
- Fix readv() to use read_locked_() directly instead of calling read(),
avoiding recursive locking and unintended socket_delay() waits
When rx_buf_ is empty during the client hello phase of the noise
handshake, rx_buf_.data() can return nullptr. Passing nullptr to
std::memcpy as the source argument is undefined behavior even when
the size is 0. Guard the memcpy with a size check.
Found by UndefinedBehaviorSanitizer in #14718.
StringRef fields decoded from protobuf point into the receive buffer
and are NOT null-terminated. DumpBuffer::append(const char*) calls
strlen() which reads past the buffer. Use the (const char*, size_t)
overload instead.
Found by AddressSanitizer in #14718.
Version the crash data by encoding the version in the magic value
(upper 16 bits = 0xDEAD sentinel, lower 16 bits = version). This
allows safely changing the scratch register layout in future firmware
without misinterpreting old crash data, and costs zero scratch
registers (we only have 8).
Add USE_RP2040_CRASH_HANDLER define and guard all call sites so
the crash handler can be conditionally compiled, matching the ESP32
crash handler pattern.
Add crash_handler_has_data() so callers can check for crash data
without triggering log output.
Log crash data when the API client subscribes to logs so dashboard
and HA users see crash reports even when connecting after boot,
matching the ESP32 crash handler behavior.
Arduino framework already wraps esp_panic_handler for its own
backtrace handler, causing a linker conflict. Only enable our
crash handler when using ESP-IDF framework by gating behind
USE_ESP32_CRASH_HANDLER define (set via cg.add_define).
Co-Authored-By: J. Nick Koston <nick@koston.org>
Store the exception cause register (exccause/mcause) and exception
type (panic_exception_t) in the .noinit struct. At log time, look up
the cause code in architecture-specific tables mirroring ESP-IDF's
internal panic_arch_fill_info() arrays.
Output now shows e.g.:
Reason: Fault - Store access fault (RISC-V)
Reason: Fault - StoreProhibited (Xtensa)
Reason: Interrupt wdt (watchdog, type-only)
Bumps CRASH_DATA_VERSION to 2. Struct gains +4 bytes (cause field)
with no padding increase (exception and pseudo_excause fit in existing
padding alongside backtrace_count and reg_frame_count). All fields
are clamped on read to prevent corrupt .noinit data from causing
out-of-bounds array access.
Co-Authored-By: J. Nick Koston <nick@koston.org>
On RISC-V, register-sourced entries (MEPC/RA) are labeled
"backtrace" while stack-scanned entries are labeled "stack scan"
to help users identify which frames are most trustworthy.
Co-Authored-By: J. Nick Koston <nick@koston.org>
- Split text into lines before calling process_stacktrace in client.py,
since process_stacktrace uses re.match and expects individual lines.
- Use memcpy instead of direct pointer cast for reading the instruction
before a return address, since RISC-V C extension means code addresses
are only 2-byte aligned and addr-4 may not be 4-byte aligned.
Co-Authored-By: J. Nick Koston <nick@koston.org>
- Add compressed c.jalr (2-byte) instruction check alongside 4-byte
JAL/JALR, since ESP32 RISC-V targets have the C extension enabled.
- Track register-sourced entries (MEPC/RA) separately from stack-scanned
ones, and skip return-address validation for register entries since
they are known-good values from the exception frame.
Co-Authored-By: J. Nick Koston <nick@koston.org>
- s_raw_crash_data and s_crash_data_valid must be mutable globals
- __real_/__wrap_ names are mandated by the --wrap linker mechanism
Co-Authored-By: J. Nick Koston <nick@koston.org>
IDF doesn't decode RISC-V backtraces on the console either — it just
dumps raw stack memory. Our crash handler provides a better trace.
Co-Authored-By: J. Nick Koston <nick@koston.org>
Stack scanning captures any value that looks like a code address, which
includes false positives. At log time (flash cache is up), validate each
address by checking if the preceding instruction is a JAL/JALR with
rd=ra. This filters spurious entries like FreeRTOS internals that happen
to be on the stack but aren't part of the actual call chain.
Co-Authored-By: J. Nick Koston <nick@koston.org>
Version first ensures future firmware can always identify the struct
layout without depending on any other field positions.
Co-Authored-By: J. Nick Koston <nick@koston.org>
Allows future firmware to detect and skip incompatible crash data
layouts. Placed alongside backtrace_count to avoid adding padding.
Co-Authored-By: J. Nick Koston <nick@koston.org>
Moving backtrace_count before the variable-length backtrace array
ensures magic, pc, and count are at fixed offsets regardless of
MAX_BACKTRACE value. This makes the .noinit data readable across
firmware versions that may change the max backtrace depth.
Co-Authored-By: J. Nick Koston <nick@koston.org>
Saves 76 bytes of RAM by removing the validated BSS copy and reading
directly from the .noinit struct after magic validation. A single bool
tracks whether valid crash data was found this boot.
Co-Authored-By: J. Nick Koston <nick@koston.org>
8 frames was cutting off useful call stack information. 16 frames
costs an additional 32 bytes of .noinit RAM and covers typical
ESPHome call chains which can be 10-12 frames deep.
The esphome logs command via API wasn't running process_stacktrace
on received log lines, so crash handler backtrace addresses were
displayed but not decoded with addr2line.
- Make s_raw_crash_data static with inline .noinit definition (no extern needed)
- Remove valid=false clearing from crash_handler_log() so both serial (boot)
and API (subscribe) paths can emit the crash data
- Clear valid flag after logging to prevent re-logging on API reconnects
- Cache esp_cpu_process_stack_pc result to avoid redundant call
- Remove unused <cstdint> include from header
When an ESP32 crashes, the backtrace is printed to UART and lost.
Users without a serial cable never see this diagnostic information.
This adds a crash handler that:
- Intercepts esp_panic_handler() via --wrap linker flag
- Captures the faulting PC and backtrace into .noinit memory
- Supports both Xtensa (ESP32/S2/S3) and RISC-V (C3/C6/H2/C2)
- Logs crash data at boot via ESP_LOGE (serial output)
- Re-logs when HA subscribes to logs (visible in HA log viewer)
- Adds CLI stacktrace decoding for the new log format