From 8eea2ce7ca918aa76b81a4545af581f9055460e8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Mar 2026 12:00:36 -1000 Subject: [PATCH] Address Copilot review: per-line stacktrace + alignment-safe read - 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 --- esphome/components/api/client.py | 15 +++++++++------ esphome/components/esp32/crash_handler.cpp | 6 +++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index a8df7f07132..0e71ad8fcbf 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -79,12 +79,15 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None: ) for parsed_msg in parse_log_message(text, timestamp): print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg) - if platform_process_stacktrace: - backtrace_state = platform_process_stacktrace(config, text, backtrace_state) - else: - backtrace_state = process_stacktrace( - config, text, backtrace_state=backtrace_state - ) + for raw_line in text.splitlines(): + if platform_process_stacktrace: + backtrace_state = platform_process_stacktrace( + config, raw_line, backtrace_state + ) + else: + backtrace_state = process_stacktrace( + config, raw_line, backtrace_state=backtrace_state + ) stop = await async_run(cli, on_log, name=name) try: diff --git a/esphome/components/esp32/crash_handler.cpp b/esphome/components/esp32/crash_handler.cpp index 94c6bc16c05..fa66209cfe1 100644 --- a/esphome/components/esp32/crash_handler.cpp +++ b/esphome/components/esp32/crash_handler.cpp @@ -4,6 +4,7 @@ #include "esphome/core/log.h" #include +#include #include #include #include @@ -34,7 +35,10 @@ static inline bool is_return_addr(uint32_t addr) { return false; // A return address on the stack points to the instruction after a call. // Check for 4-byte JAL/JALR call instruction before this address. - uint32_t inst = *(uint32_t *) (addr - 4); + // Use memcpy for alignment safety — RISC-V C extension means code addresses + // are only 2-byte aligned, so addr-4 may not be 4-byte aligned. + uint32_t inst; + memcpy(&inst, (const void *) (addr - 4), sizeof(inst)); // RISC-V instruction encoding: bits [6:0] = opcode, bits [11:7] = rd uint32_t opcode = inst & 0x7f; // Extract 7-bit opcode uint32_t rd = inst & 0xf80; // Extract rd field (bits 11:7)