Skip to content

fix reflection after DWARF symbol table changes in the rust compiler. - #719

Merged
lzrd merged 2 commits into
masterfrom
stoltz/fix-reflection-hubris-2615
Aug 4, 2026
Merged

fix reflection after DWARF symbol table changes in the rust compiler.#719
lzrd merged 2 commits into
masterfrom
stoltz/fix-reflection-hubris-2615

Conversation

@lzrd

@lzrd lzrd commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Decode MaybeUninit on hubris built with stable Rust.

Fixes oxidecomputer/hubris#2615.

Hubris moved to stable Rust 1.95.0, which changed how MaybeUninit is
laid out in DWARF. humility decoded the old layout only. New format DWARF
entries failed to decode, including those read by humility sensors. This
PR changes the reflection code so that it accepts both layouts.
A dump built by the new toolchain is added to the suite to catch regressions.

Two commits:

  1. the actual fix with the code to review
  2. the bulkier test data: a dump plus ~68k lines of generated expectations

The humility version is bumped to 0.13.5.

Testing

Beyond the checked-in tests, this was verified on lurch against real hardware:
reproduced on a live cosmo-a over the network, then confirmed across the
release boundary on sidecar-sp by flashing each release in turn and sweeping 21
read-only commands under both released 0.13.2 and this build. The last good
release passes under both, the first bad one fails only under 0.13.2, and no
other command changes.

Notes for review

  • readvar output is restored, not changed. 0.13.2 on new hubris leaks the
    compiler's internal types into it; this branch prints what humility printed
    before 1.95.0.
  • The tests will catch regressions in decoding existing DWARF formats but not
    future changes to formats; a dump built by a future toolchain will be
    required. The tests README now addresses that fact and asks for a dump
    whenever hubris changes rust-toolchain.toml.
  • Possible follow-up: a decode failure dumps the entire decoded value rather
    than naming its shape, which is what made this expensive to diagnose.
    Summarizing it would help whatever drifts next.
  • Larger follow-up: golden text can only tell us that output changed, not that
    it is wrong, so adding a dump from a new toolchain records whatever humility
    does with it, failures included. Machine-readable output would let the suite
    assert invariants instead of comparing snapshots, and detect the next drift
    without depending on someone reading a diff carefully.

lzrd added 2 commits August 3, 2026 21:33
Since hubris switched to stable Rust 1.95.0, every command that
reads a MaybeUninit variable out of a target fails. `humility
sensors` falling back to its readmem backend reports "not an array:
Tuple(Tuple("MaybeDangling<[f32; 136]>", ...))" and prints nothing. The
release boundary are the hubris releases tagged all-sp-v1.0.72 and
all-sp-v1.73.0. The last good releases, relative to the humility DWARF
symbol reading bug fixed here, are all-sp-v1.0.71 and all-sp-v1.71.2.

Nothing in hubris changed. MaybeUninit<T> is a union whose value member
is ManuallyDrop<T>, and in rust 1.95.0 that type gained an inner
MaybeDangling<T>, so the reflected payload sits one level deeper than it
used to. load_union now peels the repr(transparent) newtypes the
compiler wraps the payload in, then presents the result the way older
toolchains produced on their own:

    MaybeUninit<[u32; 154]> { value: [ ... ] }

Rebuilding that shape, rather than returning the bare payload, keeps the
type name we display and lets doppel's MaybeUninit keep verifying that
it was handed the type it expected. Output is unchanged on images from
either toolchain, and no longer leaks the compiler's internal type
names. Wrapper names are matched on the stem before `<`, so a hubris
type whose name merely starts with ManuallyDrop is not mistaken for the
compiler's.

Naming the struct as it is built leaves Value::set_name without callers,
so it goes: reassigning a name after the fact existed for this one case,
and it could never name the kinds that turn up here anyway, since arrays
and base types carry no name of their own.

Verified on against sidecar-sp across the release boundary and against a
live cosmo-a over the network, comparing released 0.13.2 with this build:

  - all-sp-v1.71.2 works under both, so the fix stays compatible with
    archives built by the old toolchain
  - all-sp-v1.73.0 fails under 0.13.2 and works here
  - the other twenty read-only commands in the sweep are unchanged

Fixes oxidecomputer/hubris#2615
No dump in the test corpus was built by a toolchain new enough to carry
the MaybeUninit layout that hubris has produced since it moved to
stable Rust 1.95.0.  That is why CI could not have caught the decode
break fixed in the previous commit; it took a report from the field.

The dump added here comes from a sidecar-b-lab running all-sp-v1.76.0.
The harness turns it into a case for each postmortem command, 29 in
all.  The one that matters is sensors-read: `sensors` against a dump
uses the readmem backend, which is the path that failed, so that case
fails on released humility and passes with the fix.

Three cases are hand-edited to expect failure, because they want Gimlet
state that a sidecar image does not have:

  - spd, which needs an SPD task
  - host last-panic, which needs host boot variables
  - a counters filter naming gimlet_seq

Nothing in a core file records which board it came from or which
toolchain built it, so the tests README grows a "Dump provenance"
section for this one, and asks that later additions do the same.

The README also states what this dump does not do.  A frozen dump
decodes the same way forever, so the corpus guards against humility
regressions on the toolchains it already holds, but cannot catch the
next change to how rustc represents a type; only a dump built by that
toolchain can.  Adding one when Hubris changes rust-toolchain.toml is
the way to keep that window short.
@lzrd

lzrd commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Test record: hubris#2615

Baseline throughout is released humility 0.13.2, compared against this branch.
Both were built for illumos with cargo build --locked --release and run on a
lab bench with SP hardware attached.

Release boundary

hubris release toolchain sensors -b readmem under 0.13.2
all-sp-v1.0.71 nightly-2025-07-20 works
all-sp-v1.71.2 nightly-2025-07-20 works, verified on hardware
all-sp-v1.0.72 1.95.0 breaks
all-sp-v1.73.0 1.95.0 breaks, verified on hardware
all-sp-v1.74.0 and later 1.95.0 breaks

Corroborated without hardware: MaybeDangling appears in the DWARF of every
task ELF in the v1.73.0 archive and in none of the v1.71.2 archive's.

1. Live reproduction, cosmo-a at 1.76.0, over the network

humility -a <cosmo-a archive> --ip <link-local address> sensors -b readmem

0.13.2 fails with not an array: Tuple(Tuple("MaybeDangling<[f32; 154]>", ...)), identical to the report. This branch prints the sensor table.

Plain sensors succeeds under both on this board, because it selects the hiffy
backend. The break is confined to the readmem backend, which is the fallback
on network targets without udprpc, and is why the reporter's gimlet hit it
while this cosmo does not by default.

2. Command sweep, same target, 21 read-only commands, both binaries

Only sensors -b readmem differed.

3. Release boundary on hardware, sidecar SP over SWD

Flashed all-sp-v1.71.2, swept; flashed all-sp-v1.73.0, swept. 21 commands by 2
releases by 2 binaries. Of the four combinations, exactly one fails, for one
command:

  • v1.71.2 + 0.13.2: sensors -b readmem succeeds
  • v1.71.2 + this branch: succeeds
  • v1.73.0 + 0.13.2: fails, exit 1
  • v1.73.0 + this branch: succeeds

The other 20 commands exited identically across all four runs. v1.71.2 passing
under both binaries is the backward-compatibility check.

Output was compared, not only exit status. On v1.73.0 the released binary also
leaks MaybeDangling and ManuallyDrop into readvar of the sensor arrays;
with this branch nothing in the sweep mentions either wrapper, and on v1.71.2
the released binary was already clean. The remaining error-shaped strings in
counters and ringbuf output are firmware event names (MiscReadFailed,
SegmentFailed(MuxMissing)), not humility failures.

4. Final revision, sidecar SP over SWD

The committed revision rebuilds the canonical MaybeUninit<T> { value: ... }
shape rather than exposing the bare payload, so it was re-verified rather than
inherited from run 3. sensors -b readmem prints the table, and readvar of a
sensor array prints MaybeUninit<[u32; 107]> { value: [ ... ] }, which is what
humility printed before hubris moved to 1.95.0.

5. Corpus coverage

The dump added in this PR was captured from the sidecar at 1.76.0. Released
0.13.2 fails on it; this branch passes.

No pre-existing dump in the corpus shows this failure: every recorded
sensors-read failure is for an unrelated reason, such as a missing
LAST_READING, no sensors, or a panic on boot. That is what left the corpus
blind to the break.

Unrelated observation

humility diagnose fails over a network core (can't be read via the archive or over the network) on both old and new hubris, under both binaries. It works
over SWD. Not addressed here.

Hardware state

The sidecar SP was flashed v1.71.2, then v1.73.0, then restored to the 1.76.0
image it started on, verified by caboose, with tasks running and fans sampled
steady under normal thermal control. The cosmo was read-only throughout and its
firmware was never modified.

@lzrd
lzrd requested review from evan-oxide and jamesmunns August 4, 2026 05:47

@jamesmunns jamesmunns left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change looks reasonable to me, and I appreciate the on-hardware testing.

I did open a PR, #720, which I think might be a bit nicer than the current peel_transparent approach, as it avoids creating and then peeling the wrappers by just not making them in the first place. Either approach is likely fine though.

@lzrd
lzrd merged commit e990e5d into master Aug 4, 2026
13 checks passed
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.

humility sensors fails to give me sensors

2 participants