Skip to content

feat: add diag plugin for host-initiated stub logging - #67

Open
Dzarda7 wants to merge 3 commits into
masterfrom
feat/diag-plugin
Open

feat: add diag plugin for host-initiated stub logging#67
Dzarda7 wants to merge 3 commits into
masterfrom
feat/diag-plugin

Conversation

@Dzarda7

@Dzarda7 Dzarda7 commented Apr 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a diag plugin that lets the host (esptool) poll log messages from the running stub without requiring a second UART or async delivery. Log messages are buffered in a 512-byte BSS ring buffer and retrieved via the DIAG_LOG_READ command (opcode 0xDF).

How it works

  • src/stub_log.h exposes a stub_logf function pointer in the base stub's BSS and a zero-cost STUB_LOGF() macro. When no plugin is loaded the pointer is NULL and all calls are no-ops.
  • src/diag_plugin.c provides a mini-printf implementation (s_logf_impl) that writes into the ring buffer. On its first invocation it installs itself into stub_logf, wiring the base stub's call sites to the plugin.
  • src/command_handler.c adds STUB_LOGF() call sites at key points in the command dispatch loop (command received, SPI flash detect, etc.).
  • DIAG_LOG_READ (0xDF) returns however many bytes are currently in the ring buffer, to be decoded by the host.

Build system

  • tools/build_chip.sh — new script to build a single chip (mirrors build_all_chips.sh).
  • Three-pass cmake+ninja build for multi-plugin chips so all plugin ELF sizes are known before final address assignment.
  • tools/compute_plugin_addrs.py now warns (instead of hard-errors) when a plugin ELF is missing in an early pass, allowing the build to proceed and self-correct on subsequent passes.

Bug fix included

tools/elf2json.py now emits a bss_start field for every plugin, allowing the esptool loader to upload plugin BSS to its correct linked address instead of appending it to the end of the base stub data. Without this, on chips with multiple plugins (e.g. esp32s3 with nand + diag) only loading a subset caused ring buffer pointers to point at wrong memory, hanging drain.

Test plan

  • Build for all chips: tools/build_all_chips.sh
  • esptool --diag flash-id on esp32c6 prints stub log lines
  • esptool --diag flash-id on esp32s3 prints stub log lines (multi-plugin regression)
  • Without --diag, stub runs normally with no overhead

Made with Cursor

@github-actions

github-actions Bot commented Apr 19, 2026

Copy link
Copy Markdown

📊 Stub Size Report

esp32

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 7,060 7,256 +196 +2.78%
data 200 368 +168 +84.00%
Total 7,260 7,624 +364 +5.01%

esp32c2

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 6,616 6,960 +344 +5.20%
data 192 372 +180 +93.75%
diag plugin - 804 new N/A
Total 6,808 8,136 +1,328 +19.51%

esp32c3

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 6,404 6,748 +344 +5.37%
data 192 372 +180 +93.75%
diag plugin - 804 new N/A
Total 6,596 7,924 +1,328 +20.13%

esp32c5

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 6,756 7,100 +344 +5.09%
data 192 372 +180 +93.75%
diag plugin - 804 new N/A
Total 6,948 8,276 +1,328 +19.11%

esp32c6

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 6,372 6,716 +344 +5.40%
data 192 372 +180 +93.75%
diag plugin - 804 new N/A
Total 6,564 7,892 +1,328 +20.23%

esp32c61

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 6,456 6,800 +344 +5.33%
data 192 372 +180 +93.75%
diag plugin - 804 new N/A
Total 6,648 7,976 +1,328 +19.98%

esp32h2

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 6,388 6,732 +344 +5.39%
data 192 372 +180 +93.75%
diag plugin - 804 new N/A
Total 6,580 7,908 +1,328 +20.18%

esp32h4

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 6,392 6,736 +344 +5.38%
data 192 372 +180 +93.75%
diag plugin - 804 new N/A
Total 6,584 7,912 +1,328 +20.17%

esp32p4

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 7,388 7,732 +344 +4.66%
data 192 372 +180 +93.75%
diag plugin - 804 new N/A
Total 7,580 8,908 +1,328 +17.52%

esp32p4-rev1

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 7,388 7,732 +344 +4.66%
data 192 372 +180 +93.75%
diag plugin - 804 new N/A
Total 7,580 8,908 +1,328 +17.52%

esp32s2

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 7,548 7,744 +196 +2.60%
data 200 368 +168 +84.00%
diag plugin - 896 new N/A
Total 7,748 9,008 +1,260 +16.26%

esp32s3

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 8,040 8,236 +196 +2.44%
data 252 420 +168 +66.67%
diag plugin - 896 new N/A
nand plugin 4,216 4,216 0 +0.00%
Total 12,508 13,768 +1,260 +10.07%

esp32s31

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 6,536 6,880 +344 +5.26%
data 192 372 +180 +93.75%
diag plugin - 804 new N/A
Total 6,728 8,056 +1,328 +19.74%

esp8266

Segment master (bytes) PR (bytes) Diff (bytes) Diff (%)
text 12,432 12,668 +236 +1.90%
data 4,100 4,272 +172 +4.20%
Total 16,532 16,940 +408 +2.47%

Dzarda7 added 3 commits April 19, 2026 23:45
Adds a diag plugin (opcode 0xDF) that maintains a 512-byte ring buffer
in BSS. The base stub exposes a stub_logf function pointer (stub_log.h)
which the plugin installs its mini-printf implementation into on first
use. STUB_LOGF() call sites in command_handler.c are zero-cost when no
plugin is loaded.

Also adds tools/build_chip.sh for single-chip builds and temporarily
comments out SDIO transport (pending esp-stub-lib update).
compute_plugin_addrs.py no longer hard-errors when an early plugin ELF
is missing with subsequent plugins — it warns and uses size 0, allowing
the build to proceed. build_chip.sh and build_all_chips.sh now do a
third cmake+ninja pass so all plugin ELFs exist when final addresses
are computed (needed for chips like esp32s3 with both nand and diag).
When a chip has multiple plugins (e.g. esp32s3 with nand + diag) and
only a subset is loaded, appending plugin BSS to self.data placed it
at data_end rather than its actual linked address. On esp32s3 with
only --diag, the diag BSS landed at NAND_PLUGIN_BSS_ADDR instead of
DIAG_PLUGIN_BSS_ADDR, causing ring buffer pointers to read garbage
and drain_log() to loop indefinitely.

Add bss_start to the plugin JSON (elf2json.py) and update the esptool
loader to upload each plugin BSS as a separate MEM segment to its
correct linked address.
Comment thread src/diag_plugin.c
s_buf_write(&c, 1);
}

/* ---- Mini-printf --------------------------------------------------------- */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM as is but isn't there anything implemented in the ROM which could be re-used without any overhead?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, variadic function is nonsense here, just wanted to try. Either use ROM or drop the formatting.

@dobairoland

Copy link
Copy Markdown
Collaborator

I haven't read every line but some compile-time gate would be nice so STUB_LOGF would be zero-overhead when the plugin is not activated.

LGTM. Nice prof-of-concept. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants