Skip to content

Debuggability: print link command in -v output and always emit firmware.map #305

@zackees

Description

@zackees

Symptom

Diagnosing link-time issues with fbuild is harder than it needs to be because two pieces of standard build-time observability are missing:

  1. fbuild build -v does NOT print the link command line. -v exposes per-file compile progress and a few summary lines, but the actual ld/gcc -o firmware.elf … invocation is never shown. This makes it impossible to compare fbuild's link command against PlatformIO's (which pio run -v prints in full).
  2. No firmware.map is written next to firmware.elf. PlatformIO writes one by default (-Wl,-Map=firmware.map), and the first line of that map is canonical:

    Archive member included to satisfy reference by file (symbol)
    It is the primary tool for answering "why is this symbol in my ELF?" When a user passes -Wl,-Map= in build_flags, fbuild appears to honor it for that single build, but if any subsequent rebuild redirects the output path, the file gets clobbered.

Why this matters

I just spent ~45 minutes diagnosing a regression where fbuild produces a 10.1% larger AVR binary than PlatformIO (filed separately as #304). Both observability gaps slowed that down significantly:

  • Without the link command line, I couldn't see whether fbuild was passing .o files or .a files to ld (the root cause was that it passes .o files; PIO passes .a).
  • Without a map file, I had to manually reconstruct the archive-vs-object decision by diffing nm output between two ELFs.

PlatformIO's combination of -v printing the link command + an always-emitted firmware.map cuts that investigation to ~5 minutes.

Proposed fixes

A. -v should print the final link command

When -v is set, the post-link step (or whatever crate emits the link invocation) should eprintln! the full argv before running it, identically to how compile commands are printed today. Format suggestion (match PlatformIO's style for diffability):

Linking .build/<env>/release/firmware.elf
  <linker> <flag> <flag> -o firmware.elf <object> <object> <archive>

B. Always emit firmware.map

Add -Wl,-Map=firmware.map to the default link flags when building for non-WASM targets. Should land in the same output directory as firmware.elf. The cost is essentially zero (~50-500 KB of plain text), and even on resource-constrained users' machines, it pays for itself the first time the user asks "what's bloating my binary."

Acceptable knob: a --no-map-file opt-out for users who insist on minimal artifacts; default ON.

C. (Bonus) Add fbuild diag <env> or fbuild build --print-link

A standalone subcommand or flag that prints what the link command would be without actually running it. Useful for CI integration that diffs link commands across versions or against PIO.

Test plan

  • Unit / integration: capture stdout of fbuild build -v -e attiny85 and assert it contains the substring firmware.elf followed by at least one .o/.a file path on the same line.
  • Snapshot test: build a fixture project and cat firmware.map, assert it starts with Archive member included to satisfy reference by file.
  • CI: spot-check on three platforms (avr, teensy41, esp32dev) that the map file lands next to the ELF.

Related


Both fixes are small and self-contained; happy to PR them if there's a preference on which crate hosts the link-emission code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions