Core Dump (Crash Reporting)

The CoreDump class wraps ESP-IDF’s flash core dump (espcoredump with CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y and a coredump data partition) in an idiomatic espp API: has_core_dump(), summary() (the raw esp_core_dump_summary_t), and format_report() — a ready-to-print text report with the reset reason, panic reason, crashed task + PC, the raw backtrace addresses (Xtensa, with a (corrupted) marker when the on-device unwind failed) or captured stack dump (RISC-V), and the exact addr2line command line using the right toolchain prefix for the build target. Abnormal resets that write no core dump (brownout, interrupt / task watchdog) are still reported with a short hint. Raw image access (image_size(), read_image(), erase()) supports downloading the complete ELF core dump over any transport; all failures are reported via std::error_code.

The CoreDumpService class serves that information over any byte stream: it reuses the espp ota component’s CRC-32-verified stream framing (detail/ota_stream_protocol.hpp) with message types in a dedicated range (GET_SUMMARY / GET_SIZE / READ / ERASE requests, SUMMARY / SIZE / DATA / OK / ERROR replies). Construct it with a send function and feed it received bytes — mounting it on a USB vendor (WebUSB) callback, a CDC (Web Serial) callback, or a socket takes a few lines. Unknown frame types are ignored, so the service coexists with other framed protocols — and, because the parser resynchronizes on the frame magic, with free-form console text — on the same stream.

The hosted espp Core Dump Console web app speaks the protocol over WebUSB (vendor interface) or Web Serial (CDC, where it doubles as a serial monitor): crash summary, chunked core.elf download, client-side nearest-symbol backtrace resolution against your local app ELF, and erase.

API Reference

Header File

Macros

ESPP_COREDUMP_HAS_ELF_SUMMARY
ESP_IDF_VERSION_VAL(major, minor, patch)
ESP_IDF_VERSION

Classes

class CoreDump : public espp::BaseComponent

Idiomatic espp access to the ESP-IDF flash core dump.

Wraps the `espcoredump` component’s flash APIs (`CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH` + a `coredump` data partition) in a no-exceptions, `std::error_code`-reporting espp API:

  • **Crash detection & summary**: `has_core_dump()`, `summary()` (the raw `esp_core_dump_summary_t`) and `format_report()` — a ready-to-print / ready-to-transmit text report with the reset reason, crashed task, PC, raw backtrace addresses (Xtensa) or a captured stack dump (RISC-V), and the exact `addr2line` command line (with the right toolchain prefix for the build target) to decode them. Abnormal resets that do NOT produce a core dump (brownout, interrupt / task watchdog) are still reported with a short hint (e.g. brownout → check power).

  • **Raw image access**: `image_size()`, `read_image(offset, out, ec)` and `erase(ec)` for downloading the complete core-dump image (the flash header + ELF core file + checksum) over any transport, so host tools (`espcoredump.py`, gdb, or the espp core-dump web console) can do the full offline analysis.

The class performs no allocation beyond the returned strings and holds no state other than a small cache of the validated image location (the full-image checksum scan runs once, not once per read_image() chunk, so chunked downloads stay linear; erase() invalidates the cache); all methods are safe to call whether or not a core dump is present. All flash-touching methods (`has_core_dump()`, `summary()`, `format_report()`, `image_size()`, `read_image()`, `erase()`) are serialized by an internal mutex, so one CoreDump instance can be shared by several threads / transports (e.g. multiple espp::CoreDumpService instances) without a READ on one racing an ERASE on another. When `CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH` is disabled, the dump-related methods degrade gracefully (no dump present) and `format_report()` still reports every abnormal reset reason (panic, brownout, watchdogs, …) — it returns an empty string only for clean reset reasons (power-on, software reset, deep-sleep wake, …).

For serving this information over a byte-stream transport (USB vendor / WebUSB, CDC / Web Serial, sockets) see espp::CoreDumpService (`coredump_service.hpp`) and the browser web app `web/coredump_console.html`.

CoreDump Example

  // --------------------------------------------------------------------------
  // Core dump accessor: crash report + raw image access for the service below.
  // --------------------------------------------------------------------------
  espp::CoreDump core_dump({.log_level = espp::Logger::Verbosity::INFO});
  const std::string report = core_dump.format_report();
  if (report.empty()) {
    logger.info("Clean boot history (reset reason: {})",
                espp::CoreDump::reset_reason_name(espp::CoreDump::reset_reason()));
  } else {
    logger.error("Previous abnormal reset:\n{}", report);
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH // avoid a constant-condition warning when disabled
    if (core_dump.has_core_dump())
      logger.info("Core dump image in flash: {} bytes (download / erase it with the web console)",
                  core_dump.image_size());
#endif
  }

  // --------------------------------------------------------------------------
  // USB composite device: a vendor/WebUSB function (framed protocol only) and
  // a CDC function carrying the system console PLUS the same framed protocol
  // (the web console's Web Serial transport separates text from frames).
  // --------------------------------------------------------------------------
  espp::UsbDevice::Config usb_cfg;
  usb_cfg.pid = 0x0d36; // distinct from the espp default so the webapp filter is specific
  usb_cfg.manufacturer = "espp";
  usb_cfg.product = "espp CoreDump";
  usb_cfg.log_level = espp::Logger::Verbosity::INFO;
  espp::UsbDevice::CdcFunction cdc;
  cdc.interface_name = "espp CoreDump (console)";
  usb_cfg.cdc = cdc;
  espp::UsbDevice::VendorFunction vendor;
  vendor.interface_name = "espp CoreDump (WebUSB)";
  vendor.webusb = true; // advertise BOS / WebUSB / MS OS 2.0 descriptors
  vendor.landing_page_url = "esp-cpp.github.io/espp/apps/coredump_console.html";
  usb_cfg.vendor = vendor;
  espp::UsbDevice usb(usb_cfg);

  // One CoreDumpService per byte stream (each owns its own frame parser),
  // both sharing the same espp::CoreDump. Replies go back on the stream the
  // request came in on.
  espp::CoreDumpService vendor_service(
      core_dump, {.send = [&](std::span<const uint8_t> frame) { usb.write_vendor(frame); },
                  .log_level = espp::Logger::Verbosity::INFO});
  espp::CoreDumpService cdc_service(
      core_dump, {.send = [&](std::span<const uint8_t> frame) { usb.write_cdc(frame); },
                  .log_level = espp::Logger::Verbosity::INFO});

Public Functions

inline CoreDump()

Construct the CoreDump accessor with the default configuration.

inline explicit CoreDump(const Config &config)

Construct the CoreDump accessor. Does not touch the flash.

Parameters:

config – Configuration parameters.

inline bool has_core_dump() const

Whether a valid core dump image is present in the coredump flash partition (`esp_core_dump_image_check()`).

inline std::string format_report() const

Format a human-readable report of the last reset and any stored core dump.

The report describes two related but SEPARATE events: the reset reason of THIS boot, and the stored core dump image — which is not necessarily from the latest reset, because IDF keeps the image until it is explicitly erased (a later brownout / software / watchdog reset leaves an older panic image in place, and no-overwrite mode can even preserve one across another panic).

  • With a core dump present: the reset reason, panic reason (if recorded), crashed task + PC, the raw backtrace addresses (with a “(corrupted)” marker when the on-device unwind failed) on Xtensa targets or the size of the captured stack dump on RISC-V, and the exact `addr2line` command line (correct toolchain prefix for CONFIG_IDF_TARGET) to decode the addresses against the app ELF. When the current reset reason is NOT PANIC, the dump is explicitly labeled as coming from an earlier crash rather than from this reset.

  • Abnormal reset without a core dump: the reset reason itself is reported with a short hint. Brownout and watchdog resets never write a dump (brownout → check the power supply; watchdog → a task or ISR hogged the CPU); any other abnormal reason (PANIC when the dump is missing — core dump to flash disabled, no `coredump` partition, or the dump failed / was already erased — plus UNKNOWN, power-glitch, CPU-lockup, efuse-error resets) is reported as “no core dump image

    available”.

  • Only genuinely clean reset reasons return an empty string, meaning “nothing abnormal to report”: power-on, external-pin reset, software reset, deep-sleep wake, SDIO, and USB / JTAG resets.

Returns:

The report text (”” = clean boot history).

inline size_t image_size() const

Total size in bytes of the stored core dump image (flash header + ELF data + checksum), or 0 if no valid core dump is present.

inline bool read_image(size_t offset, std::span<uint8_t> out, std::error_code &ec) const

Read a chunk of the raw core dump image from flash.

Parameters:
  • offset – Byte offset into the image (0 .. image_size()-1).

  • out – Destination span; up to `out.size()` bytes are read (the span is NOT resized — reads past the end of the image fail).

  • ec[out] Set on failure: no core dump present (no_such_device), the requested range exceeds the image or the reported image lies outside the coredump partition (result_out_of_range), or the flash read failed (io_error).

Returns:

true if `out.size()` bytes were read into out, false otherwise.

inline bool erase(std::error_code &ec)

Erase the stored core dump (`esp_core_dump_image_erase()`). Note: this only removes the stored dump. The reset reason of the current boot is unaffected — after a panic reset, format_report() still reports the abnormal reset reason (with no dump attached) until a clean reset follows. Idempotent: succeeds as a no-op when no dump is present.

Parameters:

ec[out] Set on failure (io_error).

Returns:

true on success (or no-op), false otherwise (ec is set).

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns:

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters:

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters:

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters:

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns:

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters:

rate_limit – The rate limit to use for the logger

Public Static Functions

static inline esp_reset_reason_t reset_reason()

The reset reason of THIS boot (`esp_reset_reason()`).

static inline const char *reset_reason_name(esp_reset_reason_t reason)

Human-readable name for a reset reason (e.g. “PANIC”, “BROWNOUT”).

static inline bool is_clean_reset_reason(esp_reset_reason_t reason)

Whether a reset reason indicates a normal, deliberate reset (a clean boot history: power-on, external pin, software reset, deep-sleep wake, SDIO, USB / JTAG). Everything else (panic, watchdogs, brownout, power glitch, CPU lockup, unknown, …) is abnormal and worth reporting even without a core dump.

static inline constexpr const char *toolchain_prefix()

The GNU toolchain binary prefix for the build target (e.g. “xtensa-esp32s3-elf-” or “riscv32-esp-elf-“), for composing addr2line / gdb command lines in reports and host tools.

struct Config

Configuration for the CoreDump component.

Public Members

espp::Logger::Verbosity log_level = {espp::Logger::Verbosity::WARN}

Logger verbosity.

Header File

Classes

class CoreDumpService : public espp::BaseComponent

Transport-agnostic service exposing the flash core dump (see espp::CoreDump) over any framed byte stream.

The service answers the core-dump protocol requests (GET_SUMMARY / GET_SIZE / READ / ERASE — see `coredump_service.hpp`’s header comment for the wire spec) with replies encoded by the espp `stream_frame` codec. It is constructed with a `send` function that transmits an encoded reply frame, so mounting it on a transport takes a few lines:

  • **USB vendor / WebUSB**: `send` = `usb.write_vendor(frame)`, and call `feed(data)` from the vendor receive callback.

  • **USB CDC / Web Serial**: `send` = `usb.write_cdc(frame)`, and call `feed(data)` from the CDC receive callback. The framing parser resynchronizes on the frame magic, so the SAME stream can also carry console text (the espp core-dump web console renders the text and the frames side by side).

  • **Sockets / UART / …**: same pattern with the transport’s send / receive.

`feed()` runs an internal incremental frame parser and dispatches every complete, CRC-verified frame to `handle_frame()`; a transport that already parses frames itself (e.g. one shared parser for several protocols) can call `handle_frame(type, payload)` directly (after routing by module). feed() ignores frames for other modules, and handle_frame() returns false for types outside the core-dump protocol, so the service coexists with other protocols on one stream.

Each instance owns one parser, so create one instance per byte stream (they can all share the same espp::CoreDump: it serializes its flash-touching methods with its own internal mutex, so e.g. a READ arriving on one transport cannot interleave with an ERASE arriving on another).

**Threading & the `send` callback contract**: `feed()` / `handle_frame()` / `reset_parser()` are serialized against each other by an internal mutex, which covers the parser state, the flash access, and building the reply frame — but the `send` callback is always invoked AFTER that mutex is released. `send` may therefore freely call back into the service (e.g. a loopback transport, or an error path that calls `reset_parser()`) without deadlocking. The flip side: the service does NOT serialize `send` itself — with the recommended one-instance-per-stream design each instance’s `send` is only ever called from that stream’s single receive context, but if you do feed one instance from multiple tasks, `send` can be invoked concurrently and must be thread-safe.

CoreDumpService Example

  // --------------------------------------------------------------------------
  // Core dump accessor: crash report + raw image access for the service below.
  // --------------------------------------------------------------------------
  espp::CoreDump core_dump({.log_level = espp::Logger::Verbosity::INFO});
  const std::string report = core_dump.format_report();
  if (report.empty()) {
    logger.info("Clean boot history (reset reason: {})",
                espp::CoreDump::reset_reason_name(espp::CoreDump::reset_reason()));
  } else {
    logger.error("Previous abnormal reset:\n{}", report);
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH // avoid a constant-condition warning when disabled
    if (core_dump.has_core_dump())
      logger.info("Core dump image in flash: {} bytes (download / erase it with the web console)",
                  core_dump.image_size());
#endif
  }

  // --------------------------------------------------------------------------
  // USB composite device: a vendor/WebUSB function (framed protocol only) and
  // a CDC function carrying the system console PLUS the same framed protocol
  // (the web console's Web Serial transport separates text from frames).
  // --------------------------------------------------------------------------
  espp::UsbDevice::Config usb_cfg;
  usb_cfg.pid = 0x0d36; // distinct from the espp default so the webapp filter is specific
  usb_cfg.manufacturer = "espp";
  usb_cfg.product = "espp CoreDump";
  usb_cfg.log_level = espp::Logger::Verbosity::INFO;
  espp::UsbDevice::CdcFunction cdc;
  cdc.interface_name = "espp CoreDump (console)";
  usb_cfg.cdc = cdc;
  espp::UsbDevice::VendorFunction vendor;
  vendor.interface_name = "espp CoreDump (WebUSB)";
  vendor.webusb = true; // advertise BOS / WebUSB / MS OS 2.0 descriptors
  vendor.landing_page_url = "esp-cpp.github.io/espp/apps/coredump_console.html";
  usb_cfg.vendor = vendor;
  espp::UsbDevice usb(usb_cfg);

  // One CoreDumpService per byte stream (each owns its own frame parser),
  // both sharing the same espp::CoreDump. Replies go back on the stream the
  // request came in on.
  espp::CoreDumpService vendor_service(
      core_dump, {.send = [&](std::span<const uint8_t> frame) { usb.write_vendor(frame); },
                  .log_level = espp::Logger::Verbosity::INFO});
  espp::CoreDumpService cdc_service(
      core_dump, {.send = [&](std::span<const uint8_t> frame) { usb.write_cdc(frame); },
                  .log_level = espp::Logger::Verbosity::INFO});

Note

`handle_frame()` runs the flash access (and the reply `send`) in the caller’s context. Reads are fast, but ERASE can take tens of milliseconds — when feeding from a latency-sensitive context (e.g. the TinyUSB task), queue the received bytes and `feed()` from a worker task (see the example).

Public Types

enum class Msg : uint8_t

Core-dump protocol message types (the stream_frame `type` byte within module 4; see the header comment for the payload spec).

Values:

enumerator GetSummary

request the crash report text

enumerator GetSize

request the core-dump image size

enumerator Read

read image bytes (u32 offset + u16 length)

enumerator Erase

erase the stored core dump

enumerator Summary

UTF-8 crash report (empty = clean boot history)

enumerator Size

u32 image size (0 = no core dump)

enumerator Data

u32 offset + image bytes

enumerator Ok

u32 context-dependent success value

enumerator Error

u32 informational code + authoritative UTF-8 message

using Stream = espp::stream_frame::StreamParser

Frame-stream parser type (from the shared stream_frame codec).

using send_fn = std::function<void(std::span<const uint8_t> frame)>

Function used to transmit one encoded reply frame to the host.

Param frame:

The complete encoded frame bytes (header + payload + CRC).

Public Functions

inline explicit CoreDumpService(CoreDump &core_dump, const Config &config)

Construct the service.

Parameters:
  • core_dump – The core-dump accessor to serve (may be shared between several service instances / transports; must outlive the service).

  • config – Configuration parameters (the reply `send` function).

inline void feed(std::span<const uint8_t> data)

Feed received transport bytes to the service.

Runs the internal incremental frame parser (arbitrary chunking, CRC verification, resynchronization past non-frame bytes such as console text) and processes every complete frame (see handle_frame()).

Note

The reply `send` callback is invoked after the internal mutex has been released (see the class-level threading notes). Frames are processed and their replies sent ONE AT A TIME, so reply memory is bounded at a single frame regardless of how many requests one input chunk carries (a max-length READ request is only 17 bytes on the wire while its DATA reply can be ~4 KiB, so accumulating all the replies first would let a single 4 KiB receive chunk materialize close to 1 MiB).

Parameters:

data – Any number of received bytes.

inline bool handle_frame(uint8_t type, std::span<const uint8_t> payload)

Handle one already-parsed frame.

Note

The reply `send` callback is invoked after the internal mutex has been released (see the class-level threading notes).

Parameters:
  • type – The frame type byte.

  • payload – The frame payload bytes.

Returns:

true if the frame type belongs to the core-dump protocol and the frame was processed (a reply frame is produced; it is delivered only when a `send` callback is configured, and dropped with a warning otherwise), false if it was ignored (another protocol’s frame — nothing is produced, so multiple services can share one stream).

inline void reset_parser()

Discard any partially-buffered frame bytes (e.g. on transport reconnect or after an RX overflow).

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns:

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters:

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters:

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters:

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns:

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters:

rate_limit – The rate limit to use for the logger

Public Static Attributes

static constexpr uint8_t kModule = 4

Dispatcher module id owned by the core-dump protocol (the frame `module` byte). Reply Msg values keep the high bit set, which build() maps to the frame reply flag.

static constexpr size_t kMaxReadLength = espp::stream_frame::kMaxPayloadSize - 4

Maximum image bytes per READ request / DATA reply (the DATA payload is a 4-byte offset plus the data, capped by the framing’s payload limit).

struct Config

Configuration for the CoreDumpService.

Public Members

send_fn send = {nullptr}

Transmits an encoded reply frame (required).

espp::Logger::Verbosity log_level = {espp::Logger::Verbosity::WARN}

Logger verbosity.