1mod symbol;
7
8use std::{
9 borrow::Cow,
10 collections::{BTreeMap, HashMap},
11 convert::TryInto,
12 fmt,
13 path::{Path, PathBuf},
14};
15
16use anyhow::{anyhow, bail, ensure};
17use object::{Object, ObjectSection, ObjectSymbol};
18
19use crate::{BitflagsKey, StringEntry, Table, TableEntry, Tag, DEFMT_VERSIONS};
20
21pub fn parse_impl(elf: &[u8], check_version: bool) -> Result<Option<Table>, anyhow::Error> {
22 let elf = object::File::parse(elf)?;
23 let mut version = None;
25 let mut encoding = None;
26
27 let try_get_version = |name: &str| {
30 if name.starts_with("\"_defmt_version_ = ") || name.starts_with("_defmt_version_ = ") {
31 Some(
32 name.trim_start_matches("\"_defmt_version_ = ")
33 .trim_start_matches("_defmt_version_ = ")
34 .trim_end_matches('"')
35 .to_string(),
36 )
37 } else {
38 None
39 }
40 };
41
42 let try_get_encoding = |name: &str| {
45 name.strip_prefix("_defmt_encoding_ = ")
46 .map(ToString::to_string)
47 };
48
49 for entry in elf.symbols() {
50 let name = match entry.name() {
51 Ok(name) => name,
52 Err(_) => continue,
53 };
54
55 if let Some(new_version) = try_get_version(name) {
58 if let Some(version) = version {
59 return Err(anyhow!(
60 "multiple defmt versions in use: {} and {} (only one is supported)",
61 version,
62 new_version
63 ));
64 }
65 version = Some(new_version);
66 }
67
68 if let Some(new_encoding) = try_get_encoding(name) {
69 if let Some(encoding) = encoding {
70 return Err(anyhow!(
71 "multiple defmt encodings in use: {} and {} (only one is supported)",
72 encoding,
73 new_encoding
74 ));
75 }
76 encoding = Some(new_encoding);
77 }
78 }
79
80 let defmt_section = elf.section_by_name(".defmt");
84
85 let (defmt_section, version) = match (defmt_section, version) {
86 (None, None) => return Ok(None), (Some(defmt_section), Some(version)) => (defmt_section, version),
88 (None, Some(_)) => {
89 bail!("defmt version found, but no `.defmt` section - check your linker configuration");
90 }
91 (Some(_), None) => {
92 bail!(
93 "`.defmt` section found, but no version symbol - check your linker configuration"
94 );
95 }
96 };
97
98 if check_version {
99 self::check_version(&version).map_err(anyhow::Error::msg)?;
100 }
101
102 let encoding = match encoding {
103 Some(e) => e.parse()?,
104 None => bail!("No defmt encoding specified. This is a bug."),
105 };
106
107 let mut map = BTreeMap::new();
109 let mut bitflags_map = HashMap::new();
110 let mut timestamp = None;
111 for entry in elf.symbols() {
112 let Ok(name) = entry.name() else {
113 continue;
114 };
115
116 if name.is_empty() {
117 continue;
120 }
121
122 if name == "$d" || name.starts_with("$d.") {
123 continue;
125 }
126
127 if name.starts_with("_defmt") || name.starts_with("__DEFMT_MARKER") {
128 continue;
132 }
133
134 if entry.section_index() == Some(defmt_section.index()) {
135 let sym = symbol::Symbol::demangle(name)?;
136 match sym.tag() {
137 symbol::SymbolTag::Defmt(Tag::Timestamp) => {
138 if timestamp.is_some() {
139 bail!("multiple timestamp format specifications found");
140 }
141
142 timestamp = Some(TableEntry::new(
143 StringEntry::new(Tag::Timestamp, sym.data().to_string()),
144 name.to_string(),
145 ));
146 }
147 symbol::SymbolTag::Defmt(Tag::BitflagsValue) => {
148 const BITFLAGS_VALUE_SIZE: u64 = 16;
150
151 if entry.size() != BITFLAGS_VALUE_SIZE {
152 bail!(
153 "bitflags value does not occupy 16 bytes (symbol `{}`)",
154 name
155 );
156 }
157
158 let defmt_data = defmt_section.data()?;
159 let addr = entry.address() as usize;
160 let value = match defmt_data.get(addr..addr + 16) {
161 Some(bytes) => u128::from_le_bytes(bytes.try_into().unwrap()),
162 None => bail!(
163 "bitflags value at {:#x} outside of defmt section",
164 entry.address()
165 ),
166 };
167 log::debug!("bitflags value `{}` has value {:#x}", sym.data(), value);
168
169 let segments = sym.data().split("::").collect::<Vec<_>>();
170 let (bitflags_name, value_idx, value_name) = match &*segments {
171 [bitflags_name, value_idx, value_name] => {
172 (*bitflags_name, value_idx.parse::<u128>()?, *value_name)
173 }
174 _ => bail!("malformed bitflags value string '{}'", sym.data()),
175 };
176
177 let key = BitflagsKey {
178 ident: bitflags_name.into(),
179 package: sym.package().into(),
180 disambig: sym.disambiguator().into(),
181 crate_name: sym.crate_name().map(|s| s.into()),
182 };
183
184 bitflags_map.entry(key).or_insert_with(Vec::new).push((
185 value_name.into(),
186 value_idx,
187 value,
188 ));
189 }
190 symbol::SymbolTag::Defmt(tag) => {
191 map.insert(
192 entry.address() as usize,
193 TableEntry::new(
194 StringEntry::new(tag, sym.data().to_string()),
195 name.to_string(),
196 ),
197 );
198 }
199 symbol::SymbolTag::Custom(_) => {}
200 }
201 }
202 }
203
204 let bitflags = bitflags_map
208 .into_iter()
209 .map(|(k, mut values)| {
210 values.sort_by_key(|(_, index, _)| *index);
211 let values = values
212 .into_iter()
213 .map(|(name, _index, value)| (name, value))
214 .collect();
215
216 (k, values)
217 })
218 .collect();
219
220 Ok(Some(Table {
221 entries: map,
222 timestamp,
223 bitflags,
224 encoding,
225 }))
226}
227
228fn check_version(version: &str) -> Result<(), String> {
230 if !DEFMT_VERSIONS.contains(&version) {
231 let msg = format!(
232 "defmt wire format version mismatch: firmware is using {}, `probe-run` supports {}\nsuggestion: use a newer version of `defmt` or `cargo install` a different version of `probe-run` that supports defmt {}",
233 version, DEFMT_VERSIONS.join(", "), version
234 );
235
236 return Err(msg);
237 }
238
239 Ok(())
240}
241
242#[derive(Clone)]
244pub struct Location {
245 pub file: PathBuf,
246 pub line: u64,
247 pub module: String,
248}
249
250impl fmt::Debug for Location {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 write!(f, "{}:{}", self.file.display(), self.line)
253 }
254}
255
256pub type Locations = BTreeMap<u64, Location>;
258
259pub fn get_locations(elf: &[u8], table: &Table) -> Result<Locations, anyhow::Error> {
260 let object = object::File::parse(elf)?;
261 let endian = if object.is_little_endian() {
262 gimli::RunTimeEndian::Little
263 } else {
264 gimli::RunTimeEndian::Big
265 };
266
267 let load_section = |id: gimli::SectionId| {
268 Ok(if let Some(s) = object.section_by_name(id.name()) {
269 s.uncompressed_data().unwrap_or(Cow::Borrowed(&[][..]))
270 } else {
271 Cow::Borrowed(&[][..])
272 })
273 };
274 let load_section_sup = |_| Ok(Cow::Borrowed(&[][..]));
275
276 let dwarf_sections =
277 gimli::DwarfSections::<Cow<[u8]>>::load::<_, anyhow::Error>(&load_section)?;
278 let dwarf_sup_sections = gimli::DwarfSections::load::<_, anyhow::Error>(&load_section_sup)?;
279
280 let borrow_section: &dyn for<'a> Fn(
281 &'a Cow<[u8]>,
282 ) -> gimli::EndianSlice<'a, gimli::RunTimeEndian> =
283 &|section| gimli::EndianSlice::new(section, endian);
284
285 let dwarf = dwarf_sections.borrow_with_sup(&dwarf_sup_sections, &borrow_section);
286
287 let mut units = dwarf.debug_info.units();
288
289 let mut map = BTreeMap::new();
290 while let Some(header) = units.next()? {
291 let unit = dwarf.unit(header)?;
292 let abbrev = header.abbreviations(&dwarf.debug_abbrev)?;
293
294 let mut cursor = header.entries(&abbrev);
295
296 ensure!(cursor.next_dfs()?.is_some(), "empty DWARF?");
297
298 let mut segments = vec![];
299 let mut depth = 0;
300 while let Some((delta_depth, entry)) = cursor.next_dfs()? {
301 depth += delta_depth;
302
303 if entry.tag() == gimli::constants::DW_TAG_namespace {
305 let mut attrs = entry.attrs();
306
307 while let Some(attr) = attrs.next()? {
308 if attr.name() == gimli::constants::DW_AT_name {
309 if let gimli::AttributeValue::DebugStrRef(off) = attr.value() {
310 let s = dwarf.string(off)?;
311 for _ in (depth as usize)..segments.len() + 1 {
312 segments.pop();
313 }
314 segments.push(core::str::from_utf8(&s)?.to_string());
315 }
316 }
317 }
318 } else if entry.tag() == gimli::constants::DW_TAG_variable {
319 let mut attrs = entry.attrs();
321
322 let mut decl_file = None;
324 let mut decl_line = None; let mut name = None;
326 let mut linkage_name = None;
327 let mut location = None;
328
329 while let Some(attr) = attrs.next()? {
330 match attr.name() {
331 gimli::constants::DW_AT_name => {
332 if let gimli::AttributeValue::DebugStrRef(off) = attr.value() {
333 name = Some(off);
334 }
335 }
336 gimli::constants::DW_AT_decl_file => {
337 if let gimli::AttributeValue::FileIndex(idx) = attr.value() {
338 decl_file = Some(idx);
339 }
340 }
341 gimli::constants::DW_AT_decl_line => {
342 if let gimli::AttributeValue::Udata(line) = attr.value() {
343 decl_line = Some(line);
344 }
345 }
346 gimli::constants::DW_AT_location => {
347 if let gimli::AttributeValue::Exprloc(loc) = attr.value() {
348 location = Some(loc);
349 }
350 }
351 gimli::constants::DW_AT_linkage_name => {
352 if let gimli::AttributeValue::DebugStrRef(off) = attr.value() {
353 linkage_name = Some(off);
354 }
355 }
356 _ => {}
357 }
358 }
359
360 if let (
361 Some(name_index),
362 Some(linkage_name_index),
363 Some(file_index),
364 Some(line),
365 Some(loc),
366 ) = (name, linkage_name, decl_file, decl_line, location)
367 {
368 let name_slice = dwarf.string(name_index)?;
369 let name = core::str::from_utf8(&name_slice)?;
370 let linkage_name_slice = dwarf.string(linkage_name_index)?;
371 let linkage_name = core::str::from_utf8(&linkage_name_slice)?;
372
373 if name == "DEFMT_LOG_STATEMENT" {
374 if table.raw_symbols().any(|i| i == linkage_name) {
375 let addr = exprloc2address(unit.encoding(), &loc)?;
376 let file = file_index_to_path(file_index, &unit, &dwarf)?;
377 let module = segments.join("::");
378
379 let loc = Location { file, line, module };
380
381 if let Some(old) = map.insert(addr, loc.clone()) {
382 bail!("BUG in DWARF variable filter: index collision for addr 0x{:08x} (old = {:?}, new = {:?})", addr, old, loc);
383 }
384 } else {
385 }
388 }
389 }
390 }
391 }
392 }
393
394 Ok(map)
395}
396
397fn file_index_to_path<R>(
398 index: u64,
399 unit: &gimli::Unit<R>,
400 dwarf: &gimli::Dwarf<R>,
401) -> Result<PathBuf, anyhow::Error>
402where
403 R: gimli::read::Reader,
404{
405 ensure!(index != 0, "`FileIndex` was zero");
406
407 let header = if let Some(program) = &unit.line_program {
408 program.header()
409 } else {
410 bail!("no `LineProgram`");
411 };
412
413 let file = if let Some(file) = header.file(index) {
414 file
415 } else {
416 bail!("no `FileEntry` for index {}", index)
417 };
418
419 let mut p = PathBuf::new();
420 if let Some(dir) = file.directory(header) {
421 let dir = dwarf.attr_string(unit, dir)?;
422 let dir_s = dir.to_string_lossy()?;
423 let dir = Path::new(&dir_s[..]);
424
425 if !dir.is_absolute() {
426 if let Some(ref comp_dir) = unit.comp_dir {
427 p.push(&comp_dir.to_string_lossy()?[..]);
428 }
429 }
430 p.push(dir);
431 }
432
433 p.push(
434 &dwarf
435 .attr_string(unit, file.path_name())?
436 .to_string_lossy()?[..],
437 );
438
439 Ok(p)
440}
441
442fn exprloc2address<R: gimli::read::Reader<Offset = usize>>(
443 encoding: gimli::Encoding,
444 data: &gimli::Expression<R>,
445) -> Result<u64, anyhow::Error> {
446 let mut pc = data.0.clone();
447 while pc.len() != 0 {
448 if let Ok(gimli::Operation::Address { address }) =
449 gimli::Operation::parse(&mut pc, encoding)
450 {
451 return Ok(address);
452 }
453 }
454
455 Err(anyhow!("`Operation::Address` not found"))
456}