Skip to content

Commit ab085c4

Browse files
authored
feat(debug): print link command on -v + always emit firmware.map (fixes #305) (#308)
Two small debuggability patches per the issue: A. Linker invocations now eprintln! the full command line when -v is passed. tracing::info! is kept for log integration but also goes to stderr so plain CLI users see it. B. Every per-platform linker now adds -Wl,-Map=firmware.map to the link command. Lands next to firmware.elf in output_dir. Cost is essentially zero (~50-500 KB plain text) and pays for itself the first time anyone asks "why is this symbol in my ELF?" (See #304 for the recent investigation that took 45 min for want of a map.) WASM and ESP32 paths intentionally skipped — different artifact shapes. Closes #305.
1 parent 57ce6af commit ab085c4

9 files changed

Lines changed: 110 additions & 10 deletions

File tree

crates/fbuild-build/src/avr/avr_linker.rs

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,19 @@ impl AvrLinker {
5454
}
5555
}
5656

57-
impl Linker for AvrLinker {
58-
fn archive(&self, objects: &[PathBuf], output: &Path) -> Result<()> {
59-
crate::linker::LinkerBase::archive(&self.ar_path, objects, output, "avr-ar")
60-
}
61-
62-
fn link(
57+
impl AvrLinker {
58+
/// Build the argv that will be passed to `avr-gcc` for the link step.
59+
///
60+
/// Factored out so it can be unit-tested without invoking the toolchain
61+
/// (see #305 — assert that `-Wl,-Map=` is present).
62+
fn build_link_args(
6363
&self,
6464
objects: &[PathBuf],
6565
archives: &[PathBuf],
6666
output_dir: &Path,
67+
elf_path: &Path,
6768
extra: &LinkExtraArgs,
68-
) -> Result<PathBuf> {
69-
std::fs::create_dir_all(output_dir)?;
70-
let elf_path = output_dir.join("firmware.elf");
71-
69+
) -> Vec<String> {
7270
let mut args: Vec<String> = vec![
7371
self.gcc_path.to_string_lossy().to_string(),
7472
format!("-mmcu={}", self.mcu),
@@ -85,6 +83,10 @@ impl Linker for AvrLinker {
8583

8684
args.extend(["-o".to_string(), elf_path.to_string_lossy().to_string()]);
8785

86+
// Always emit a linker map next to firmware.elf for debugging (#305).
87+
let map_path = output_dir.join("firmware.map");
88+
args.push(format!("-Wl,-Map={}", map_path.to_string_lossy()));
89+
8890
// Sketch objects first
8991
for obj in objects {
9092
args.push(obj.to_string_lossy().to_string());
@@ -102,7 +104,29 @@ impl Linker for AvrLinker {
102104
args.extend(extra.libs.iter().cloned());
103105
args.push("-Wl,--end-group".to_string());
104106

107+
args
108+
}
109+
}
110+
111+
impl Linker for AvrLinker {
112+
fn archive(&self, objects: &[PathBuf], output: &Path) -> Result<()> {
113+
crate::linker::LinkerBase::archive(&self.ar_path, objects, output, "avr-ar")
114+
}
115+
116+
fn link(
117+
&self,
118+
objects: &[PathBuf],
119+
archives: &[PathBuf],
120+
output_dir: &Path,
121+
extra: &LinkExtraArgs,
122+
) -> Result<PathBuf> {
123+
std::fs::create_dir_all(output_dir)?;
124+
let elf_path = output_dir.join("firmware.elf");
125+
126+
let args = self.build_link_args(objects, archives, output_dir, &elf_path, extra);
127+
105128
if self.verbose {
129+
eprintln!("link: {}", args.join(" "));
106130
tracing::info!("link: {}", args.join(" "));
107131
}
108132

@@ -168,4 +192,40 @@ mod tests {
168192
assert_eq!(linker.max_flash, Some(32256));
169193
assert_eq!(linker.max_ram, Some(2048));
170194
}
195+
196+
/// #305: every per-platform linker must emit a `firmware.map` next to
197+
/// `firmware.elf`. Assert the generated argv contains a `-Wl,-Map=` token.
198+
#[test]
199+
fn test_avr_link_args_contain_map_flag() {
200+
let linker = AvrLinker::new(
201+
PathBuf::from("/bin/avr-gcc"),
202+
PathBuf::from("/bin/avr-ar"),
203+
PathBuf::from("/bin/avr-objcopy"),
204+
PathBuf::from("/bin/avr-size"),
205+
"atmega328p",
206+
get_avr_config().unwrap(),
207+
BuildProfile::Release,
208+
Some(32256),
209+
Some(2048),
210+
false,
211+
);
212+
213+
let tmp = tempfile::TempDir::new().unwrap();
214+
let output_dir = tmp.path();
215+
let elf_path = output_dir.join("firmware.elf");
216+
let extra = LinkExtraArgs::default();
217+
let args = linker.build_link_args(&[], &[], output_dir, &elf_path, &extra);
218+
219+
let map_flag = args
220+
.iter()
221+
.find(|a| a.starts_with("-Wl,-Map="))
222+
.expect("link args must contain -Wl,-Map= for firmware.map emission");
223+
let expected_map = output_dir.join("firmware.map");
224+
assert!(
225+
map_flag.contains(&*expected_map.to_string_lossy()),
226+
"expected map flag to reference {}, got {}",
227+
expected_map.display(),
228+
map_flag
229+
);
230+
}
171231
}

crates/fbuild-build/src/ch32v/ch32v_linker.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ impl Linker for Ch32vLinker {
8686
elf_path.to_string_lossy().to_string(),
8787
]);
8888

89+
// Always emit a linker map next to firmware.elf for debugging (#305).
90+
let map_path = output_dir.join("firmware.map");
91+
args.push(format!("-Wl,-Map={}", map_path.to_string_lossy()));
92+
8993
// Sketch objects first
9094
for obj in objects {
9195
args.push(obj.to_string_lossy().to_string());
@@ -101,6 +105,7 @@ impl Linker for Ch32vLinker {
101105
args.extend(extra.libs.iter().cloned());
102106

103107
if self.verbose {
108+
eprintln!("link: {}", args.join(" "));
104109
tracing::info!("link: {}", args.join(" "));
105110
}
106111

crates/fbuild-build/src/esp8266/esp8266_linker.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ impl Linker for Esp8266Linker {
199199

200200
args.extend(["-o".to_string(), elf_path.to_string_lossy().to_string()]);
201201

202+
// Always emit a linker map next to firmware.elf for debugging (#305).
203+
let map_path = output_dir.join("firmware.map");
204+
args.push(format!("-Wl,-Map={}", map_path.to_string_lossy()));
205+
202206
// Sketch objects first
203207
for obj in objects {
204208
args.push(obj.to_string_lossy().to_string());
@@ -216,6 +220,7 @@ impl Linker for Esp8266Linker {
216220
args.push("-Wl,--end-group".to_string());
217221

218222
if self.verbose {
223+
eprintln!("link: {}", args.join(" "));
219224
tracing::info!("link: {}", args.join(" "));
220225
}
221226

crates/fbuild-build/src/generic_arm/arm_linker.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ impl Linker for ArmLinker {
8686
elf_path.to_string_lossy().to_string(),
8787
]);
8888

89+
// Always emit a linker map next to firmware.elf for debugging (#305).
90+
let map_path = output_dir.join("firmware.map");
91+
args.push(format!("-Wl,-Map={}", map_path.to_string_lossy()));
92+
8993
// Sketch objects first
9094
for obj in objects {
9195
args.push(obj.to_string_lossy().to_string());
@@ -101,6 +105,7 @@ impl Linker for ArmLinker {
101105
args.extend(extra.libs.iter().cloned());
102106

103107
if self.verbose {
108+
eprintln!("link: {}", args.join(" "));
104109
tracing::info!("link: {}", args.join(" "));
105110
}
106111

crates/fbuild-build/src/nrf52/nrf52_linker.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ impl Linker for Nrf52Linker {
9494
elf_path.to_string_lossy().to_string(),
9595
]);
9696

97+
// Always emit a linker map next to firmware.elf for debugging (#305).
98+
let map_path = output_dir.join("firmware.map");
99+
args.push(format!("-Wl,-Map={}", map_path.to_string_lossy()));
100+
97101
// Sketch objects first
98102
for obj in objects {
99103
args.push(obj.to_string_lossy().to_string());
@@ -109,6 +113,7 @@ impl Linker for Nrf52Linker {
109113
args.extend(extra.libs.iter().cloned());
110114

111115
if self.verbose {
116+
eprintln!("link: {}", args.join(" "));
112117
tracing::info!("link: {}", args.join(" "));
113118
}
114119

crates/fbuild-build/src/renesas/renesas_linker.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ impl Linker for RenesasLinker {
9999
elf_path.to_string_lossy().to_string(),
100100
]);
101101

102+
// Always emit a linker map next to firmware.elf for debugging (#305).
103+
let map_path = output_dir.join("firmware.map");
104+
args.push(format!("-Wl,-Map={}", map_path.to_string_lossy()));
105+
102106
// Sketch objects first
103107
for obj in objects {
104108
args.push(obj.to_string_lossy().to_string());
@@ -114,6 +118,7 @@ impl Linker for RenesasLinker {
114118
args.extend(extra.libs.iter().cloned());
115119

116120
if self.verbose {
121+
eprintln!("link: {}", args.join(" "));
117122
tracing::info!("link: {}", args.join(" "));
118123
}
119124

crates/fbuild-build/src/sam/sam_linker.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ impl Linker for SamLinker {
100100
elf_path.to_string_lossy().to_string(),
101101
]);
102102

103+
// Always emit a linker map next to firmware.elf for debugging (#305).
104+
let map_path = output_dir.join("firmware.map");
105+
args.push(format!("-Wl,-Map={}", map_path.to_string_lossy()));
106+
103107
// Sketch objects first
104108
for obj in objects {
105109
args.push(obj.to_string_lossy().to_string());
@@ -130,6 +134,7 @@ impl Linker for SamLinker {
130134
args.extend(extra.libs.iter().cloned());
131135

132136
if self.verbose {
137+
eprintln!("link: {}", args.join(" "));
133138
tracing::info!("link: {}", args.join(" "));
134139
}
135140

crates/fbuild-build/src/silabs/silabs_linker.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ impl Linker for SilabsLinker {
9292
elf_path.to_string_lossy().to_string(),
9393
]);
9494

95+
// Always emit a linker map next to firmware.elf for debugging (#305).
96+
let map_path = output_dir.join("firmware.map");
97+
args.push(format!("-Wl,-Map={}", map_path.to_string_lossy()));
98+
9599
// Sketch objects first
96100
for obj in objects {
97101
args.push(obj.to_string_lossy().to_string());
@@ -117,6 +121,7 @@ impl Linker for SilabsLinker {
117121
args.push("-Wl,--end-group".to_string());
118122

119123
if self.verbose {
124+
eprintln!("link: {}", args.join(" "));
120125
tracing::info!("link: {}", args.join(" "));
121126
}
122127

crates/fbuild-build/src/teensy/teensy_linker.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ impl Linker for TeensyLinker {
8383
args.extend(self.linker_scripts.to_args());
8484
args.extend(["-o".to_string(), elf_path.to_string_lossy().to_string()]);
8585

86+
// Always emit a linker map next to firmware.elf for debugging (#305).
87+
let map_path = output_dir.join("firmware.map");
88+
args.push(format!("-Wl,-Map={}", map_path.to_string_lossy()));
89+
8690
// Sketch objects first
8791
for obj in objects {
8892
args.push(obj.to_string_lossy().to_string());
@@ -98,6 +102,7 @@ impl Linker for TeensyLinker {
98102
args.extend(extra.libs.iter().cloned());
99103

100104
if self.verbose {
105+
eprintln!("link: {}", args.join(" "));
101106
tracing::info!("link: {}", args.join(" "));
102107
}
103108

0 commit comments

Comments
 (0)