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