1#![cfg(feature = "unstable")]
7#![cfg_attr(docsrs, feature(doc_cfg))]
8#![cfg_attr(docsrs, doc(cfg(unstable)))]
9
10mod symbol;
11
12use std::{
13 borrow::Cow,
14 collections::BTreeMap,
15 fmt,
16 path::{Path, PathBuf},
17};
18
19use anyhow::{anyhow, bail, ensure};
20pub use defmt_decoder::Table;
21use defmt_decoder::{StringEntry, TableEntry};
22use object::{Object, ObjectSection};
23
24pub fn parse(elf: &[u8]) -> Result<Option<Table>, anyhow::Error> {
28 let elf = object::File::parse(elf)?;
29 let mut version = None;
31 let is_defmt_version = |name: &str| {
32 name.starts_with("\"_defmt_version_ = ") || name.starts_with("_defmt_version_ = ")
33 };
34 for (_, entry) in elf.symbols() {
35 let name = match entry.name() {
36 Some(name) => name,
37 None => continue,
38 };
39
40 if is_defmt_version(name) {
45 let new_version = name
46 .trim_start_matches("\"_defmt_version_ = ")
47 .trim_start_matches("_defmt_version_ = ")
48 .trim_end_matches('"');
49 if let Some(version) = version {
50 return Err(anyhow!(
51 "multiple defmt versions in use: {} and {} (only one is supported)",
52 version,
53 new_version
54 ));
55 }
56 version = Some(new_version);
57 }
58 }
59
60 let defmt_shndx = elf.section_by_name(".defmt").map(|s| s.index());
64
65 let (defmt_shndx, version) = match (defmt_shndx, version) {
66 (None, None) => return Ok(None), (Some(defmt_shndx), Some(version)) => (defmt_shndx, version),
68 (None, Some(_)) => {
69 bail!("defmt version found, but no `.defmt` section - check your linker configuration");
70 }
71 (Some(_), None) => {
72 bail!(
73 "`.defmt` section found, but no version symbol - check your linker configuration"
74 );
75 }
76 };
77
78 defmt_decoder::check_version(version).map_err(anyhow::Error::msg)?;
79
80 let mut map = BTreeMap::new();
82 for (_, entry) in elf.symbols() {
83 let name = match entry.name() {
86 Some(name) if !name.is_empty() => name,
87 _ => continue,
88 };
89
90 if is_defmt_version(name) {
91 continue;
95 }
96
97 if entry.section_index() == Some(defmt_shndx) {
98 let sym = symbol::Symbol::demangle(name)?;
99 if let symbol::SymbolTag::Defmt(tag) = sym.tag() {
100 map.insert(
101 entry.address() as usize,
102 TableEntry::new(
103 StringEntry::new(tag, sym.data().to_string()),
104 name.to_string(),
105 ),
106 );
107 }
108 }
109 }
110
111 Ok(Some(Table::new(map)))
112}
113
114#[derive(Clone)]
115pub struct Location {
116 pub file: PathBuf,
117 pub line: u64,
118 pub module: String,
119}
120
121impl fmt::Debug for Location {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(f, "{}:{}", self.file.display(), self.line)
124 }
125}
126
127pub type Locations = BTreeMap<u64, Location>;
128
129pub fn get_locations(elf: &[u8], table: &Table) -> Result<Locations, anyhow::Error> {
130 let live_syms = table.raw_symbols().collect::<Vec<_>>();
131 let object = object::File::parse(elf)?;
132 let endian = if object.is_little_endian() {
133 gimli::RunTimeEndian::Little
134 } else {
135 gimli::RunTimeEndian::Big
136 };
137
138 let load_section = |id: gimli::SectionId| {
139 Ok(if let Some(s) = object.section_by_name(id.name()) {
140 s.uncompressed_data().unwrap_or(Cow::Borrowed(&[][..]))
141 } else {
142 Cow::Borrowed(&[][..])
143 })
144 };
145 let load_section_sup = |_| Ok(Cow::Borrowed(&[][..]));
146
147 let dwarf_cow =
148 gimli::Dwarf::<Cow<[u8]>>::load::<_, _, anyhow::Error>(&load_section, &load_section_sup)?;
149
150 let borrow_section: &dyn for<'a> Fn(
151 &'a Cow<[u8]>,
152 ) -> gimli::EndianSlice<'a, gimli::RunTimeEndian> =
153 &|section| gimli::EndianSlice::new(&*section, endian);
154
155 let dwarf = dwarf_cow.borrow(&borrow_section);
156
157 let mut units = dwarf.debug_info.units();
158
159 let mut map = BTreeMap::new();
160 while let Some(header) = units.next()? {
161 let unit = dwarf.unit(header)?;
162 let abbrev = header.abbreviations(&dwarf.debug_abbrev)?;
163
164 let mut cursor = header.entries(&abbrev);
165
166 ensure!(cursor.next_dfs()?.is_some(), "empty DWARF?");
167
168 let mut segments = vec![];
169 let mut depth = 0;
170 while let Some((delta_depth, entry)) = cursor.next_dfs()? {
171 depth += delta_depth;
172
173 if entry.tag() == gimli::constants::DW_TAG_namespace {
175 let mut attrs = entry.attrs();
176
177 while let Some(attr) = attrs.next()? {
178 match attr.name() {
179 gimli::constants::DW_AT_name => {
180 if let gimli::AttributeValue::DebugStrRef(off) = attr.value() {
181 let s = dwarf.string(off)?;
182 for _ in (depth as usize)..segments.len() + 1 {
183 segments.pop();
184 }
185 segments.push(core::str::from_utf8(&s)?.to_string());
186 }
187 }
188 _ => {}
189 }
190 }
191 } else if entry.tag() == gimli::constants::DW_TAG_variable {
192 let mut attrs = entry.attrs();
194
195 let mut decl_file = None;
197 let mut decl_line = None; let mut name = None;
199 let mut linkage_name = None;
200 let mut location = None;
201
202 while let Some(attr) = attrs.next()? {
203 match attr.name() {
204 gimli::constants::DW_AT_name => {
205 if let gimli::AttributeValue::DebugStrRef(off) = attr.value() {
206 name = Some(off);
207 }
208 }
209
210 gimli::constants::DW_AT_decl_file => {
211 if let gimli::AttributeValue::FileIndex(idx) = attr.value() {
212 decl_file = Some(idx);
213 }
214 }
215
216 gimli::constants::DW_AT_decl_line => {
217 if let gimli::AttributeValue::Udata(line) = attr.value() {
218 decl_line = Some(line);
219 }
220 }
221
222 gimli::constants::DW_AT_location => {
223 if let gimli::AttributeValue::Exprloc(loc) = attr.value() {
224 location = Some(loc);
225 }
226 }
227
228 gimli::constants::DW_AT_linkage_name => {
229 if let gimli::AttributeValue::DebugStrRef(off) = attr.value() {
230 linkage_name = Some(off);
231 }
232 }
233
234 _ => {}
235 }
236 }
237
238 if let (
239 Some(name_index),
240 Some(linkage_name_index),
241 Some(file_index),
242 Some(line),
243 Some(loc),
244 ) = (name, linkage_name, decl_file, decl_line, location)
245 {
246 let name_slice = dwarf.string(name_index)?;
247 let name = core::str::from_utf8(&name_slice)?;
248 let linkage_name_slice = dwarf.string(linkage_name_index)?;
249 let linkage_name = core::str::from_utf8(&linkage_name_slice)?;
250
251 if name == "DEFMT_LOG_STATEMENT" {
252 if live_syms.contains(&linkage_name) {
253 let addr = exprloc2address(unit.encoding(), &loc)?;
254 let file = file_index_to_path(file_index, &unit, &dwarf)?;
255 let module = segments.join("::");
256
257 let loc = Location { file, line, module };
258
259 if let Some(old) = map.insert(addr, loc.clone()) {
260 bail!("BUG in DWARF variable filter: index collision for addr 0x{:08x} (old = {:?}, new = {:?})", addr, old, loc);
261 }
262 } else {
263 }
266 }
267 }
268 }
269 }
270 }
271
272 Ok(map)
273}
274
275fn file_index_to_path<R>(
276 index: u64,
277 unit: &gimli::Unit<R>,
278 dwarf: &gimli::Dwarf<R>,
279) -> Result<PathBuf, anyhow::Error>
280where
281 R: gimli::read::Reader,
282{
283 ensure!(index != 0, "`FileIndex` was zero");
284
285 let header = if let Some(program) = &unit.line_program {
286 program.header()
287 } else {
288 bail!("no `LineProgram`");
289 };
290
291 let file = if let Some(file) = header.file(index) {
292 file
293 } else {
294 bail!("no `FileEntry` for index {}", index)
295 };
296
297 let mut p = PathBuf::new();
298 if let Some(dir) = file.directory(header) {
299 let dir = dwarf.attr_string(unit, dir)?;
300 let dir_s = dir.to_string_lossy()?;
301 let dir = Path::new(&dir_s[..]);
302
303 if !dir.is_absolute() {
304 if let Some(ref comp_dir) = unit.comp_dir {
305 p.push(&comp_dir.to_string_lossy()?[..]);
306 }
307 }
308 p.push(&dir);
309 }
310
311 p.push(
312 &dwarf
313 .attr_string(unit, file.path_name())?
314 .to_string_lossy()?[..],
315 );
316
317 Ok(p)
318}
319
320fn exprloc2address<R: gimli::read::Reader<Offset = usize>>(
321 encoding: gimli::Encoding,
322 data: &gimli::Expression<R>,
323) -> Result<u64, anyhow::Error> {
324 let mut pc = data.0.clone();
325 while pc.len() != 0 {
326 if let Ok(gimli::Operation::Address { address }) =
327 gimli::Operation::parse(&mut pc, encoding)
328 {
329 return Ok(address);
330 }
331 }
332
333 Err(anyhow!("`Operation::Address` not found"))
334}