Skip to content

fbuild symbols: ingest toolchain paths from build_info.json + extend schema with nm/c++filt/readelf/objdump #428

Description

@zackees

Background

fbuild symbols <elf> (PR #424) currently relies on the user passing --nm explicitly and auto-derives --cppfilt by string-replacing the toolchain prefix:

pub fn derive_cppfilt_path(nm_path: &Path) -> PathBuf {
    // strip "nm" suffix from stem, append "c++filt"
    // .../xtensa-esp32s3-elf-nm.exe -> .../xtensa-esp32s3-elf-c++filt.exe
}

This works for stock GCC cross-toolchains but is brittle:

  • Naming drift: clang ships llvm-nm + llvm-cxxfilt (no c++filt symlink on some distros).
  • macOS Homebrew installs LLVM tools as gnm / llvm-cxxfilt with different naming.
  • PIO ships some chips with riscv32-unknown-elf-nm (no c++filt; needs llvm-cxxfilt).
  • Cross-compilers under msys2 sometimes have .exe only on nm, not c++filt.

Meanwhile fbuild build emits build_info_<env>.json (see
crates/fbuild-build/src/build_info.rs) with the toolchain paths it
already knows. The natural source of truth.

What's missing today

BuildInfo has cc_path / cxx_path / ar_path / objcopy_path / size_path but no nm_path, cppfilt_path, readelf_path, or objdump_path — exactly the four tools fbuild symbols needs.

Meanwhile FastLED's existing ci/util/symbol_analysis.py reads
board_info[\"aliases\"][\"nm\"] / [\"c++filt\"] / [\"objdump\"] /
[\"readelf\"] from PIO-shape build_info.json (PlatformIO emits an
aliases block; fbuild's emitter does not). So today the Python
consumer works against PIO-built artifacts but cannot trivially work
against fbuild-built artifacts.

Proposed ideal API

1. Extend fbuild_build::build_info::BuildInfo

Add four new string fields, populated by each platform orchestrator
during link metadata collection:

```rust
pub struct BuildInfo {
// existing fields...
pub cc_path: String,
pub cxx_path: String,
pub ar_path: String,
pub objcopy_path: String,
pub size_path: String,

// NEW (this issue)
pub nm_path: String,
pub cppfilt_path: String,
pub readelf_path: String,
pub objdump_path: String,

}
```

All four follow the same "empty string when not applicable"
convention as the existing optional fields. Each platform orchestrator
already knows its toolchain prefix (it derives cc_path from it); the
derivation for the new four is mechanical.

2. Mirror in an aliases block for PIO-shape compatibility

So existing FastLED Python consumers (ci/util/symbol_analysis.py,
ci/inspect_binary.py, etc.) keep working without changes:

```json
{
"": {
"prog_path": "...",
"cc_path": "...",
"nm_path": "...",
"aliases": {
"nm": "",
"c++filt": "",
"readelf": "",
"objdump": ""
}
}
}
```

Pros: drop-in compatibility with PIO consumers. Cons: minor schema
duplication. Worth it for the migration story.

3. Teach fbuild symbols to read it

CLI signature stays back-compat. New behavior:

```
fbuild symbols
[--build-info ] # explicit override
[--nm ] # back-compat manual override (highest precedence)
[--cppfilt ]
```

Resolution order:

  1. --nm / --cppfilt flags (explicit user wins).
  2. --build-info <path> if given.
  3. Auto-discovery: walk up from the ELF's directory looking for
    build_info_<env>.json first, then build_info.json. Same lookup
    PIO uses, same lookup FastLED Python uses.
  4. PATH-based fallback (today's behavior).
  5. Hard error with a helpful message that explains the four sources.

This means the common case becomes:

```bash
fbuild symbols .fbuild/build/uno/firmware.elf

or

fbuild symbols .pio/build/esp32s3/firmware.elf
```

— no manual toolchain wiring. Today both forms require --nm because
nothing knows where to find the cross-tool.

4. Use the same aliases block from a future fbuild build --symbol-analysis

fbuild build --symbol-analysis already exists but is the old coarse
report. When the unified analyzer (PR #424 + #427) is wired in
post-merge, the orchestrator should resolve toolchain paths via the
same aliases block it just emitted, so there is exactly one source
of truth.

5. Documentation

docs/symbols.md (new): tutorial that walks through running the
analyzer against a stock bash compile esp32s3 --examples Blink
output and against a fbuild build output, showing how the same JSON
shape works for both.

Scope and ordering

This is a non-trivial schema change (touches every platform
orchestrator: AVR, ESP32, RP2040, STM32, Teensy, ESP8266, NRF52, SAM,
Renesas, Apollo3, LPC, Silabs, CH32V) plus the build_info emitter +
CLI plumbing. Not blocking merge of #424 / #427 — those land with
the auto-derive path that works for ESP-IDF GCC, the dominant case.

This issue is the cleanup follow-up that:

  • Adds the four toolchain fields to BuildInfo.
  • Adds the aliases mirror for PIO compatibility.
  • Teaches the CLI to auto-locate build_info.json.
  • Updates the orchestrators to pass the new paths.
  • Updates docs.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions