Skip to main content

Module boot_report

Module boot_report 

Source
Expand description

phase-412 – the boot self-report, for boards with no reachable log sink. A fixed RAM record the image writes about itself, for targets where no log sink reaches a human.

§Why this exists

phase-412 derived six pool counts and the executor arena for the mr-canhubk344 island, and then could not tell whether the derived image was correct. The only available signal was the ROS graph’s node count, and one unchanged configuration produced 4, 0, 0, 4, 4 across five runs. Every other channel was already disqualified for that board:

  • The console is on lpuart0, which is not wired on the MR-CANHUBK344. lpuart2 is the zenoh serial transport and cannot carry a second protocol.
  • nros_log therefore reaches nothing. Both arena diagnostics (issue 0900) go through it, so the two messages written specifically to explain an arena failure are invisible on the one board that needed them.
  • SEGGER RTT was tried and could not discriminate: a working image and a derived image both emitted only the Zephyr banner, and a deliberate positive control produced nothing at all.
  • Semihosting halts the core until a probe answers and FAULTS with no probe attached, so an image carrying it cannot run standalone.

What all of those share is that they are STREAMS: they need the board to still be running, and they need somebody attached at the moment the interesting thing happens. The failure this campaign is trying to see is the opposite shape. An under-sized arena halts DURING entity creation, before the first spin, so issue 0900’s advisory never prints – the failure cannot report itself through any stream.

So this is not a stream. It is a fixed-size record in RAM that the image keeps up to date as it boots, read out AFTERWARDS by halting the core and dumping memory. It survives the halt because it does not depend on anything still running, and a PARTIAL record is the useful case rather than a lost one: the last stage reached and the allocation that did not fit are exactly what names the knob to change.

§Reading it

The record is a #[no_mangle] static, so it has a symbol in the ELF and a debugger can find it without the address being wired in anywhere:

pyocd commander -t s32k344 -c "halt" -c "savemem <addr> <len> report.bin"
python3 scripts/read-boot-report.py <elf> report.bin

MAGIC distinguishes a written record from uninitialised RAM, and BootReport::struct_size lets a reader refuse a layout it does not know rather than decode it wrongly.

§Cost, and why it is opt-in

Enabled by setting NROS_BOOT_REPORT=1 at build time (Zephyr: CONFIG_NROS_BOOT_REPORT=y), which makes nros-node’s build script emit cfg(nros_boot_report). With the cfg absent every function here is an empty #[inline(always)] body and the static does not exist, so an image that does not opt in is byte-identical to one built before this module – the same rule issue 0900’s arena knob and phase-403’s rx_buffer_from_type() both keep.

Enabled, it costs BootReport::struct_size bytes of .bss – 132, the same on every target because every field is a u32 – and a handful of relaxed atomic stores on paths that run once per entity at registration.

The 132 is not a detail: it is the LENGTH an operator types into savemem, and this sentence said 60 for as long as the record had fifteen fields. A short dump decodes – read-boot-report.py needs 33 * 4 bytes and a 100-byte one is refused, but a reader who trusts the prose over the tool spends the refusal looking at the wrong thing. Ask the tool instead: read-boot-report.py --addr-only <elf> prints the address AND the length, from the ELF’s own symbol size.

§Adding a field (phase-460 W5, for W7 and after)

The record is meant to grow, and the order it grows in is the whole contract: read-boot-report.py decodes POSITIONALLY. So a new field is APPENDED – after the last field, never inserted between two – and the four edits are one commit:

  1. the field on BootReport, its zero in BootReport::new, the same name in the same position on Snapshot, and its load in snapshot;
  2. VERSION bumped, so an older decoder REFUSES the record instead of reading the new word as one it knows;
  3. FIELDS in scripts/read-boot-report.py, same name, same position, and KNOWN_VERSION to match;
  4. the field count in the_record_is_thirty_three_packed_u32s below.

check-boot-report-layout fails on 1 without 3, and the Rust test fails if the compiler laid the record out with padding. Appending is what keeps a dump taken from an older image decodable by eye: every word before the new one is still where it was, and the version says which words exist.

Enums§

AllocArena
Which allocator refused the allocation BootReport::failed_alloc_size names.
KnobSource
Which rung of the knob ladder decided a knob the record carries.
Stage
How far boot got. Monotonic, and the single most useful field: an arena failure halts during entity creation, so the stage that was NOT reached names the phase to look at.

Constants§

MAGIC
"NRSR" – nano-ros self report. Written LAST, so a reader that finds it knows every field before it is already valid.
VERSION
Layout version. Bump on any field change; a reader refuses what it does not know rather than decoding a record it would misread.

Functions§

checkpoint
init
note_alloc
note_alloc_failed
note_arena_capacity
note_cpp_init_ret
note_domain
note_error
note_heap
note_heap_alloc_failed
note_sample_dropped_too_small
Always 0 without the record, and the CALLER must not care.
nros_boot_report_note_heap
The C entry point exists in BOTH builds, unlike every other stub here.
nros_boot_report_note_heap_alloc_failed
The C entry point exists in BOTH builds, for the reason below.
nros_boot_report_note_sample_dropped_too_small
The C entry point exists in BOTH builds, for the reason above.