Debug levels
Additive. Everything above your level compiles out entirely — no runtime check, no dead branch, no string in the binary.
make flash BOARD=board-esp32-devkitc DEBUG=debug-level-2Default is debug-level-1.
| Level | Turns on | Use it for |
|---|---|---|
debug-level-0 |
nothing | Shipping |
debug-level-1 |
flint-log, flint-metrics, flint-panic |
Development. The default. |
debug-level-2 |
+ flint-trace |
Following scheduler decisions |
debug-level-3 |
+ flint-timing, flint-cpu-util |
Where the time goes |
What each feature does
Section titled “What each feature does”| Feature | Effect |
|---|---|
flint-log |
log_info! / log_warn! / log_error! reach the console, plus a ring buffer |
flint-metrics |
Counter and Gauge record instead of discarding |
flint-panic |
Panic writes a snapshot to SRAM that survives soft reset |
flint-trace |
log_trace! and kernel event tracing |
flint-timing |
Per-task timing |
flint-cpu-util |
CPU utilisation accounting |
debug-level-0 looks like a dead board
Section titled “debug-level-0 looks like a dead board”Tasks run. They print nothing. There is no logging code to print with.
That is indistinguishable from a hang unless you know, so the kernel says so once over raw UART:
[FLINT] boot complete. Logging is COMPILED OUT (debug-level-0) -- tasks will run but print nothing.[FLINT] Rebuild with DEBUG=debug-level-1 to see task output.Leave it alone until you’re shipping.
Two switches that aren’t levels
Section titled “Two switches that aren’t levels”Both are const bool in source, not features. Flip and rebuild.
BOOT_DIAGNOSTICS in kernel/src/boot.rs — the [FLINT] ... boot banner.
On by default. It’s how you bisect a failing boot; see
Troubleshooting.
TRAP_DIAGNOSTICS in kernel/src/switch.rs — per-tick heartbeat from
inside the trap handler. Off by default, because on a working kernel it’s
pure noise. Turn it on when the console goes quiet.
On-target self-tests
Section titled “On-target self-tests”Separate from the levels, and the only tests that exercise the machine rather than the kernel’s logic:
make test-target # auto-detect the portmake test-target BOARD=board-m5-atom-matrix PORT=COM5 # ESP32-PICO on Windowsmake test-target BOARD=board-m5-atom-matrix PORT=/dev/ttyUSB0Flashes the board, captures the serial output, and exits non-zero if anything failed. Five checks run after interrupts are unmasked, each chosen because a host test cannot fail it:
| Test | What only silicon can show |
|---|---|
timer_preserves_windowed_context |
a trap corrupting the interrupted task’s register windows |
deep_window_recursion_returns_intact |
a window spilled past the physical register file and misrestored |
tick_advances |
the timer interrupt actually fires |
tick_never_goes_backwards |
tick()’s wrapping CCOUNT re-base slipping |
critical_section_masks_the_tick |
that a critical section really masks — on a host cs_with masks nothing, so this is unfalsifiable there |
Set PORT whenever more than one serial device is attached. Otherwise
espflash prompts for a choice, the harness has no terminal to answer with, and
you get a timeout that reports “the board never reached the self-test” — a
confusing way to say “pick a port”.
The harness is strict on purpose: it counts the PASS/FAIL lines that
actually arrived and rejects the run if they disagree with the board’s own
summary. A dropped serial line reads as a void run, not a pass. If that
triggers, lower MONITOR_BAUD.
To flash the self-test image without the harness judging it — useful when you want to watch the console yourself:
make flash EXTRA_FEATURES=self-testVerifying the watchdogs
Section titled “Verifying the watchdogs”kernel::watchdog::arm() is opt-in and apps/examples/demo calls it — but a board that
runs normally only proves the watchdogs are being fed. Proving one fires
means breaking the board on purpose:
make test-watchdog WDT=kernel # masks interrupts and hangs -> RTC WDT, ~5 smake test-watchdog WDT=idle # spins without yielding -> idle WDT, ~10 sPASS is the board rebooting a few seconds after the [wdt-test] line — the
ROM banner and FlintMain reached appear a second time.
FAIL is the console going quiet and staying quiet. That means armed but not firing, which is worse than no watchdog: it reports protection nobody has.
The two modes are not interchangeable. Masking interrupts stops the tick, so only the RTC watchdog can notice. Spinning without masking keeps the tick running and keeps the RTC watchdog fed — the system looks perfectly healthy to it — and only the idle-fed one sees that idle stopped running.
Measure it, don’t guess:
make build DEBUG=debug-level-0 && make build DEBUG=debug-level-3make build prints the size table each time.