Uh oh!
There was an error while loading. Please reload this page.
feat(symbols): map-derived synthesis for anonymous rodata pools (closes #425) - #427
Conversation
#425) `fbuild symbols` previously stopped at object-file granularity for `.flash.rodata` because `nm` does not enumerate the anonymous merged string pools that `-fdata-sections` puts in `.rodata.<mangled>.str1.<N>` sub-sections. Concretely, ~377 KB of the ESP32-S3 Blink master ELF's rodata fell back to "(archive, object, .flash.rodata) -> N bytes" rather than "this function's strings -> N bytes". The linker map carries the owner inline in the input-section name. This PR adds: - `extract_owner_from_section()` in `fbuild-core::symbol_analysis` that recognises the input-section shapes that carry an owner: `.text.<owner>`, `.literal.<owner>`, `.rodata.<owner>`, `.rodata.<owner>.str1.<N>`, `.rodata.<owner>.cst<N>`, `.data.<owner>`, `.bss.<owner>`, `.data.rel.ro.<owner>`, `.gnu.linkonce.{t,r,d,b}.<owner>`. - `collect_map_derived_owners()` that walks map ranges, skips any range whose address is covered by an nm-listed symbol (so we don't double-count), and returns the owner+size for each remaining range. - `build_fine_grained_map_with_synth()` (and a thin wrapper for the existing call site) that emits synthetic `FineGrainedSymbol` rows for those owners, tagged `source: "map-derived"`. - New `source: "nm" | "map-derived"` field on `FineGrainedSymbol` with a `serde(default)` so JSON written by earlier `fbuild symbols` still deserialises. - The CLI driver (`fbuild-build::symbol_analyzer::analyze_elf`) collects synthetic mangled owners and demangles them via c++filt in the same threaded batch as the nm names. Empirically on the same ESP32-S3 Blink master ELF that motivated #425: | Metric | Before | After | |---|---:|---:| | Sized symbols emitted | 3,696 | 4,591 (+895 map-derived) | | Flash bytes named at symbol level | 316,368 | 388,380 (+72 KB) | The single largest new attribution: 58 KB of merged rodata in `main.cpp.o` now correctly named as belonging to `fl::ClocklessIdf5<3, fl::TIMING_WS2812_800KHZ, GRB, 0, false, 280>::ClocklessIdf5`, which is the FL_WARN string pool pulled in by the NEOPIXEL chipset header. Per-archive rodata for FastLED audio/channels/gfx/stl/net detector classes also now resolves to function-level owners. Tests ----- 11 new unit tests in `fbuild-core::symbol_analysis::tests` covering all input-section shapes listed above, the no-double-count invariant, range/coverage overlap logic, and the synthetic-row pipeline end to end. Plus 2 existing tests in `fbuild-build::symbol_analyzer::tests` (toolchain prefix derivation). All 24 unit tests across both crates pass. Verified end-to-end on real ESP32-S3 ELFs. Closes#425.
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…s' into feat/425-rodata-symbol-attribution
…OC gate PR #427 surfaced the workspace's "Reject .rs files over 1000 LOC" gate because the new map-derived synthesis code + its 13 unit tests pushed crates/fbuild-core/src/symbol_analysis.rs to 1076 lines (limit: 1000). Promote `symbol_analysis` from a single file to a directory module: - crates/fbuild-core/src/symbol_analysis/mod.rs (695 LOC) - crates/fbuild-core/src/symbol_analysis/tests.rs (383 LOC) `tests.rs` carries the existing `mod tests` body verbatim — no test content changed; the `use super::*;` makes private items like `nm_range_covers` and `InputSectionIndex` still visible inside the child module. 22/22 tests still pass. Pure refactor; no behavior change to the public API.
…s' into feat/425-rodata-symbol-attribution # Conflicts: # crates/fbuild-build/src/symbol_analyzer.rs
Uh oh!
There was an error while loading. Please reload this page.
…-S3 (#448) The Phase 2 + Phase 3 acceptance gate from the #434 meta. Exercises the full `fbuild build` → `fbuild bloat` flow against the latest FastLED master + ESP32-S3 toolchain to verify: - build_info.json carries the four #428 toolchain paths (nm_path, cppfilt_path, readelf_path, objdump_path) and mirrors them onto the PIO-shape `aliases` block. - `fbuild bloat .` works zero-flag and lands `report.json` + `report.md` at the documented Phase 3 path `<project>/.fbuild/build/esp32s3/bloat-report/`. - stdout prints both absolute paths on exit (Phase 3 invariant). - Symbol counts, totals, and the >100 map-derived (#427) row count are within bounds generous enough not to flake on FastLED master drift, but tight enough to catch real regressions. - Markdown output renders the top-flash table. Marked `#[ignore]` so ordinary `cargo test` runs aren't network- dependent. Wire into the nightly CI matrix via workflow_dispatch + scheduled weekly to surface FastLED master regressions here too. Closes#442. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Closes#425. Adds map-derived synthetic symbols to
fbuild symbolsso anonymous merged rodata blocks (the.rodata.<owner>.str1.<N>and.rodata.<owner>.cst<N>sub-sections that holdFL_WARN/FL_LOGand other compiler-emitted strings) are attributed to the owning function instead of stopping at the object file. Stacked on top of #424.What changed
extract_owner_from_section()strips known prefixes (.text.,.literal.,.rodata.,.data.,.bss.,.data.rel.ro.,.gnu.linkonce.{t,r,d,b}.) and known suffixes (.str1.<N>,.cst<N>) to return the mangled owner of an input section.collect_map_derived_owners()walks map ranges, drops any whose address is already covered by an nm-listed symbol, and returns the survivors with their extracted owner.build_fine_grained_map_with_synth()emits syntheticFineGrainedSymbolrows taggedsource: \"map-derived\".source: \"nm\" | \"map-derived\"field onFineGrainedSymbolwithserde(default)so JSON written by earlierfbuild symbolsstill deserialises.Empirical impact (ESP32-S3 Blink master ELF, the case that motivated #425)
Single largest new attribution: 58 KB of
main.cpp.orodata now correctly named asfl::ClocklessIdf5<3, TIMING_WS2812_800KHZ, GRB, 0, false, 280>::ClocklessIdf5— the FL_WARN string pool pulled in by the NEOPIXEL chipset header. Other meaningful new resolutions:fl::XY(unsigned char, unsigned char)(libFastLED.a/fl.gfx+.cpp.o)printBeforeSetupInfo()(libFrameworkArduino.a/chip-debug-report.cpp.o)fl::ChannelEngineRMTImpl::attemptAllocationRecovery(...)fl::ChannelEngineRMTImpl::reconfigureForNetwork()Test plan
fbuild-core::symbol_analysis::testscovering every recognised input-section shape, the no-double-count invariant when nm AND map both reference an address, range-overlap detection, and end-to-end synthesis throughbuild_fine_grained_map.fbuild-build::symbol_analyzer::testsstill pass (toolchain prefix derivation).uv run --script lintclean on touched files (rustfmt + clippy via fbuild's own gate).Out of scope
.rodatablocks,.flash_rodata_dummyalignment padding) — these stay object-bucketed by design.vtable for ...annotation lines alongside.rodata._ZTV<class>sections; we already produce the right answer because_ZTV<class>demangles cleanly, but a follow-up could short-circuit the c++filt call when the map already gives us the demangled string.🤖 Generated with Claude Code