From 48654133ecc14ec68a333fd42df1a7ebdbefceea Mon Sep 17 00:00:00 2001 From: Jorge Aparicio <jorge.aparicio@ferrous-systems.com> Date: Wed, 16 Sep 2020 14:12:44 +0200 Subject: [PATCH 1/7] start using DWARF info in the unwinder this is works towards #17 for now it extends the backtrace with inlined functions (which were not shown before) it doesn't add line info just yet though there is some, no super-precise line info available --- Cargo.lock | 16 +++ Cargo.toml | 1 + src/main.rs | 114 ++++++++--------- src/pc2frames.rs | 323 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 397 insertions(+), 57 deletions(-) create mode 100644 src/pc2frames.rs diff --git a/Cargo.lock b/Cargo.lock index 03ccdf34..7188aab6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,6 +403,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "intervaltree" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566d5aa3b5cc5c5809cc1a9c9588d917a634248bfc58f7ea14e354e71595a32c" +dependencies = [ + "smallvec", +] + [[package]] name = "jaylink" version = "0.1.5" @@ -608,6 +617,7 @@ dependencies = [ "defmt-decoder", "defmt-elf2table", "gimli 0.22.0", + "intervaltree", "log", "object 0.21.1", "probe-rs", @@ -804,6 +814,12 @@ dependencies = [ "libc", ] +[[package]] +name = "smallvec" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" + [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 1e8fe1f2..c8b60fc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ probe-rs-rtt = "0.3.0" rustc-demangle = "0.1.16" structopt = "0.3.15" object = "0.21.1" +intervaltree = "0.2.6" [features] defmt = ["defmt-elf2table", "defmt-decoder"] diff --git a/src/main.rs b/src/main.rs index a5bcaf0c..decfdd6e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,14 @@ mod logger; +mod pc2frames; use core::{ cmp, convert::TryInto, mem, - ops::Range, sync::atomic::{AtomicBool, Ordering}, }; use std::{ - collections::{btree_map, BTreeMap}, + collections::{btree_map, BTreeMap, HashSet}, fs, io::{self, Write as _}, path::PathBuf, @@ -26,7 +26,7 @@ use gimli::{ }; use object::{ read::{File as ElfFile, Object as _, ObjectSection as _}, - SectionIndex, + SymbolSection, }; use probe_rs::config::{registry, MemoryRegion, RamRegion}; use probe_rs::{ @@ -123,13 +123,6 @@ fn notmain() -> Result<i32, anyhow::Error> { ); } - let text = elf.section_by_name(".text").ok_or_else(|| { - anyhow!( - "`.text` section is missing, please make sure that the linker script was passed to the \ - linker (check `.cargo/config.toml` and the `RUSTFLAGS` variable)" - ) - })?; - #[cfg(feature = "defmt")] let (table, locs) = { let table = defmt_elf2table::parse(&bytes)?; @@ -228,7 +221,31 @@ fn notmain() -> Result<i32, anyhow::Error> { } } - let (range_names, rtt_addr, uses_heap) = range_names_from(&elf, text.index())?; + let text = elf + .section_by_name(".text") + .map(|section| section.index()) + .ok_or_else(|| { + anyhow!( + "`.text` section is missing, please make sure that the linker script was passed to the \ + linker (check `.cargo/config.toml` and the `RUSTFLAGS` variable)" + ) + })?; + + let live_functions = elf + .symbol_map() + .symbols() + .iter() + .filter_map(|sym| { + if sym.section() == SymbolSection::Section(text) { + sym.name() + } else { + None + } + }) + .collect::<HashSet<_>>(); + + let pc2frames = pc2frames::from(&elf, &live_functions)?; + let (rtt_addr, uses_heap) = rtt_and_heap_info_from(&elf)?; let vector_table = vector_table.ok_or_else(|| anyhow!("`.vector_table` section is missing"))?; log::debug!("vector table: {:x?}", vector_table); @@ -454,7 +471,8 @@ fn notmain() -> Result<i32, anyhow::Error> { &mut core, pc, debug_frame, - &range_names, + &elf, + &pc2frames, &vector_table, &sp_ram_region, )?; @@ -598,7 +616,8 @@ fn backtrace( core: &mut Core<'_>, mut pc: u32, debug_frame: &[u8], - range_names: &RangeNames, + elf: &ElfFile, + pc2frames: &pc2frames::Map, vector_table: &VectorTable, sp_ram_region: &Option<RamRegion>, ) -> Result<Option<TopException>, anyhow::Error> { @@ -614,23 +633,31 @@ fn backtrace( let ctx = &mut UninitializedUnwindContext::new(); let mut top_exception = None; - let mut frame = 0; + let mut frame_index = 0; let mut registers = Registers::new(lr, sp, core); println!("stack backtrace:"); loop { - let name = range_names - .binary_search_by(|rn| { - if rn.0.contains(&pc) { - cmp::Ordering::Equal - } else if pc < rn.0.start { - cmp::Ordering::Greater - } else { - cmp::Ordering::Less - } - }) - .map(|idx| &*range_names[idx].1) - .unwrap_or("<unknown>"); - println!("{:>4}: {:#010x} - {}", frame, pc, name); + let mut frames = pc2frames.query_point(pc as u64).collect::<Vec<_>>(); + // `IntervalTree` docs don't specify the order of the elements returned by `query_point` so + // we sort them by depth before printing + frames.sort_by_key(|frame| -frame.value.depth); + + if frames.is_empty() { + // this means there was no DWARF info associated to this PC; the PC could point into + // external assembly or external C code. Fall back to a symtab lookup + let map = elf.symbol_map(); + if let Some(name) = map.get(pc as u64).and_then(|symbol| symbol.name()) { + println!("{:>4}: {}", frame_index, name); + } else { + println!("{:>4}: <unknown> @ {:#012x}", frame_index, pc); + } + frame_index += 1; + } else { + for frame in frames { + println!("{:>4}: {}", frame_index, frame.value.name); + frame_index += 1; + } + } // on hard fault exception entry we hit the breakpoint before the subroutine prelude (`push // lr`) is executed so special handling is required @@ -708,8 +735,6 @@ fn backtrace( } pc = lr & !THUMB_BIT; } - - frame += 1; } Ok(top_exception) @@ -853,15 +878,10 @@ impl Stacked { num_words as u32 * 4 } } -// FIXME this might already exist in the DWARF data; we should just use that -/// Map from PC ranges to demangled Rust names -type RangeNames = Vec<(Range<u32>, String)>; -fn range_names_from( +fn rtt_and_heap_info_from( elf: &ElfFile, - text: SectionIndex, -) -> Result<(RangeNames, Option<u32>, bool /* uses heap */), anyhow::Error> { - let mut range_names = vec![]; +) -> Result<(Option<u32>, bool /* uses heap */), anyhow::Error> { let mut rtt = None; let mut uses_heap = false; @@ -879,29 +899,9 @@ fn range_names_from( } _ => {} } - - if symbol.section_index() == Some(text) && symbol.size() > 0 { - let mut name = rustc_demangle::demangle(name).to_string(); - // clear the thumb bit - let start = symbol.address() as u32 & !1; - - // strip the hash (e.g. `::hd881d91ced85c2b0`) - let hash_len = "::hd881d91ced85c2b0".len(); - if let Some(pos) = name.len().checked_sub(hash_len) { - let maybe_hash = &name[pos..]; - if maybe_hash.starts_with("::h") { - // FIXME avoid this allocation - name = name[..pos].to_string(); - } - } - - range_names.push((start..start + symbol.size() as u32, name)); - } } - range_names.sort_unstable_by(|a, b| a.0.start.cmp(&b.0.start)); - - Ok((range_names, rtt, uses_heap)) + Ok((rtt, uses_heap)) } const LR: CoreRegisterAddress = CoreRegisterAddress(14); diff --git a/src/pc2frames.rs b/src/pc2frames.rs new file mode 100644 index 00000000..3c36f72a --- /dev/null +++ b/src/pc2frames.rs @@ -0,0 +1,323 @@ +use core::{iter::FromIterator, ops::Range}; +use std::{borrow::Cow, collections::HashSet}; + +use anyhow::ensure; +use gimli::{read::Reader, DebuggingInformationEntry, Dwarf, Unit}; +use intervaltree::{Element, IntervalTree}; +use object::{Object as _, ObjectSection as _}; + +pub type Map = IntervalTree<u64, Frame>; + +// output - locations +// <PC range> -> [{ Option<name>, file-line }] +pub fn from(object: &object::File, live_functions: &HashSet<&str>) -> Result<Map, anyhow::Error> { + let endian = if object.is_little_endian() { + gimli::RunTimeEndian::Little + } else { + gimli::RunTimeEndian::Big + }; + + let load_section = |id: gimli::SectionId| { + Ok(if let Some(s) = object.section_by_name(id.name()) { + s.uncompressed_data().unwrap_or(Cow::Borrowed(&[][..])) + } else { + Cow::Borrowed(&[][..]) + }) + }; + let load_section_sup = |_| Ok(Cow::Borrowed(&[][..])); + + let dwarf_cow = + gimli::Dwarf::<Cow<[u8]>>::load::<_, _, anyhow::Error>(&load_section, &load_section_sup)?; + + let borrow_section: &dyn for<'a> Fn( + &'a Cow<[u8]>, + ) -> gimli::EndianSlice<'a, gimli::RunTimeEndian> = + &|section| gimli::EndianSlice::new(&*section, endian); + + let dwarf = dwarf_cow.borrow(&borrow_section); + + let mut units = dwarf.debug_info.units(); + + let mut elements = vec![]; + while let Some(header) = units.next()? { + let unit = dwarf.unit(header)?; + let abbrev = header.abbreviations(&dwarf.debug_abbrev)?; + + let mut cursor = header.entries(&abbrev); + + ensure!(cursor.next_dfs()?.is_some(), "empty DWARF?"); + + let mut depth = 0; + // None = outside a subprogram DIE + // Some(depth) = inside a subprogram DIE + let mut subprogram_depth = None; + while let Some((delta_depth, entry)) = cursor.next_dfs()? { + depth += delta_depth; + + if let Some(subprogram_depth_val) = subprogram_depth { + if depth <= subprogram_depth_val { + // leaving subprogram DIE + subprogram_depth = None; + } + } + + if entry.tag() == gimli::constants::DW_TAG_subprogram { + if let Some(sub) = Subprogram::from_die(entry, depth, &dwarf)? { + if let Span::Pc(range) = sub.span.clone() { + if live_functions.contains(&*sub.name) { + // sanity check: nested subprograms have never been observed in practice + assert!(subprogram_depth.is_none(), "BUG? nested subprogram"); + + subprogram_depth = Some(depth); + let name = demangle(&sub.name); + elements.push(Element { + range, + value: Frame { name, depth }, + }); + } else { + // we won't walk into subprograms that are were GC-ed by the linker + } + } else { + // subprograms with "inlined" span will be referred to by the 'origin' + // field of `InlinedSubroutine`s so we don't add them to the list at this + // point. Also, they don't have PC span info and won't appear as a symbol + // in the .symtab + } + } + } else if subprogram_depth.is_some() { + // within a 'live' subroutine (subroutine was not GC-ed by the linker) + if entry.tag() == gimli::constants::DW_TAG_inlined_subroutine { + let inline_sub = InlinedSubroutine::from_die(entry, depth, &dwarf, &unit)?; + elements.push(Element { + range: inline_sub.pc, + value: Frame { + name: demangle(&inline_sub.origin.name), + depth, + }, + }) + } + } + } + } + + Ok(IntervalTree::from_iter(elements)) +} + +#[derive(Debug)] +pub struct Frame { + // unmangled function name + pub name: String, + // depth in the DIE tree + pub depth: isize, + // TODO add file location +} + +#[derive(Clone, Debug, PartialEq)] +enum Span { + Pc(Range<u64>), + Inlined, +} + +#[derive(Debug)] +struct Subprogram { + // depth in the DIE tree + depth: isize, + name: String, + span: Span, + // NOTE the DIE contains `decl_file` and `decl_line` info but those points into + // the *declaration* of the function, e.g. `fn foo() {`, which is not particularly useful. + // We are more interested in the location of the statements within the function +} + +impl Subprogram { + /// returns `None` if `entry` has no "name" + fn from_die<R>( + entry: &DebuggingInformationEntry<R>, + depth: isize, + dwarf: &Dwarf<R>, + ) -> Result<Option<Self>, anyhow::Error> + where + R: Reader, + { + assert_eq!(entry.tag(), gimli::constants::DW_TAG_subprogram); + + let mut attrs = entry.attrs(); + + let mut inlined = false; + let mut linkage_name = None; + let mut low_pc = None; + let mut name = None; + let mut pc_offset = None; + while let Some(attr) = attrs.next()? { + match attr.name() { + gimli::constants::DW_AT_low_pc => { + if let gimli::AttributeValue::Addr(addr) = attr.value() { + low_pc = Some(addr); + } else { + unreachable!() + } + } + + gimli::constants::DW_AT_high_pc => { + pc_offset = Some(attr.value().udata_value().expect("unreachable")); + } + + gimli::constants::DW_AT_linkage_name => { + if let gimli::AttributeValue::DebugStrRef(off) = attr.value() { + linkage_name = Some(off); + } else { + unreachable!() + } + } + + gimli::constants::DW_AT_name => { + if let gimli::AttributeValue::DebugStrRef(off) = attr.value() { + name = Some(off); + } else { + unreachable!() + } + } + + gimli::constants::DW_AT_inline => { + if let gimli::AttributeValue::Inline(gimli::constants::DW_INL_inlined) = + attr.value() + { + inlined = true; + } + } + + _ => {} + } + } + + if let Some(off) = linkage_name.or(name) { + let name = dwarf.string(off)?.to_string()?.into_owned(); + + Ok(Some(Subprogram { + depth, + span: if inlined { + Span::Inlined + } else { + let low_pc = low_pc.expect("no `low_pc`"); + let pc_off = pc_offset.expect("no `high_pc`"); + Span::Pc(low_pc..(low_pc + pc_off)) + }, + name, + })) + } else { + // TODO what are these nameless subroutines? They seem to have "abstract origin" info + Ok(None) + } + } +} + +#[derive(Debug)] +struct InlinedSubroutine { + call_file: u64, + call_line: u64, + origin: Subprogram, + pc: Range<u64>, +} + +impl InlinedSubroutine { + fn from_die<R>( + entry: &DebuggingInformationEntry<R>, + depth: isize, + dwarf: &Dwarf<R>, + unit: &Unit<R>, + ) -> Result<Self, anyhow::Error> + where + R: Reader, + { + assert_eq!(entry.tag(), gimli::constants::DW_TAG_inlined_subroutine); + + let mut attrs = entry.attrs(); + + let mut at_range = None; + let mut call_file = None; + let mut call_line = None; + let mut low_pc = None; + let mut origin = None; + let mut pc_offset = None; + while let Some(attr) = attrs.next()? { + match attr.name() { + gimli::constants::DW_AT_abstract_origin => { + if let gimli::AttributeValue::UnitRef(off) = attr.value() { + let other_entry = unit.entry(off)?; + + let sub = Subprogram::from_die(&other_entry, depth, dwarf)?.unwrap(); + origin = Some(sub); + } else { + unreachable!() + } + } + + gimli::constants::DW_AT_ranges => { + if let gimli::AttributeValue::RangeListsRef(off) = attr.value() { + let r = dwarf + .ranges(&unit, off)? + .next()? + .expect("unexpected end of range list"); + at_range = Some(r.begin..r.end); + } + } + + gimli::constants::DW_AT_low_pc => { + if let gimli::AttributeValue::Addr(addr) = attr.value() { + low_pc = Some(addr); + } else { + unreachable!() + } + } + + gimli::constants::DW_AT_high_pc => { + pc_offset = Some(attr.value().udata_value().expect("unreachable")); + } + + gimli::constants::DW_AT_call_file => { + if let gimli::AttributeValue::FileIndex(idx) = attr.value() { + call_file = Some(idx); + } + } + + gimli::constants::DW_AT_call_line => { + if let gimli::AttributeValue::Udata(line) = attr.value() { + call_line = Some(line); + } + } + + _ => {} + } + } + + let pc = at_range.unwrap_or_else(|| { + let start = low_pc.expect("no low_pc"); + let off = pc_offset.expect("no high_pc"); + start..start + off + }); + + Ok(InlinedSubroutine { + origin: origin.expect("no abstract_origin"), + call_file: call_file.expect("no call_file"), + call_line: call_line.expect("no call_line"), + pc, + }) + } +} + +fn demangle(function: &str) -> String { + let mut demangled = rustc_demangle::demangle(function).to_string(); + // remove trailing hash (`::he40fe02240f4a81d`) + // strip the hash (e.g. `::hd881d91ced85c2b0`) + let hash_len = "::hd881d91ced85c2b0".len(); + if let Some(pos) = demangled.len().checked_sub(hash_len) { + let maybe_hash = &demangled[pos..]; + if maybe_hash.starts_with("::h") { + for _ in 0..hash_len { + demangled.pop(); + } + } + } + + demangled +} From 233ab8c064530cd85faa94ae3d4fcbe4b0da3a7e Mon Sep 17 00:00:00 2001 From: Jorge Aparicio <jorge.aparicio@ferrous-systems.com> Date: Wed, 16 Sep 2020 14:41:57 +0200 Subject: [PATCH 2/7] add TODO about extracting more location info --- src/pc2frames.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pc2frames.rs b/src/pc2frames.rs index 3c36f72a..02418dbb 100644 --- a/src/pc2frames.rs +++ b/src/pc2frames.rs @@ -8,8 +8,6 @@ use object::{Object as _, ObjectSection as _}; pub type Map = IntervalTree<u64, Frame>; -// output - locations -// <PC range> -> [{ Option<name>, file-line }] pub fn from(object: &object::File, live_functions: &HashSet<&str>) -> Result<Map, anyhow::Error> { let endian = if object.is_little_endian() { gimli::RunTimeEndian::Little @@ -95,6 +93,10 @@ pub fn from(object: &object::File, live_functions: &HashSet<&str>) -> Result<Map depth, }, }) + } else if entry.tag() == gimli::constants::DW_TAG_lexical_block + || entry.tag() == gimli::constants::DW_TAG_variable + { + // TODO extract more fine grained (statement-level) location information } } } From 636125f60316cb764cc4aa3303d4a733a4e89c5c Mon Sep 17 00:00:00 2001 From: Jorge Aparicio <jorge.aparicio@ferrous-systems.com> Date: Wed, 16 Sep 2020 14:47:29 +0200 Subject: [PATCH 3/7] set the thumb bit before the symtab lookup --- src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index decfdd6e..f16f26bb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -646,7 +646,10 @@ fn backtrace( // this means there was no DWARF info associated to this PC; the PC could point into // external assembly or external C code. Fall back to a symtab lookup let map = elf.symbol_map(); - if let Some(name) = map.get(pc as u64).and_then(|symbol| symbol.name()) { + // confusingly enough the addresses in the `.symtab` do have their thumb bit set to 1 + // so set it back before the lookup + let addr = (pc | THUMB_BIT) as u64; + if let Some(name) = map.get(addr).and_then(|symbol| symbol.name()) { println!("{:>4}: {}", frame_index, name); } else { println!("{:>4}: <unknown> @ {:#012x}", frame_index, pc); From 39c25a21aa63f789667b6cb4c521709a9ebf9043 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio <jorge.aparicio@ferrous-systems.com> Date: Wed, 16 Sep 2020 15:40:52 +0200 Subject: [PATCH 4/7] add somewhat imprecise location info --- src/main.rs | 25 ++++++++++- src/pc2frames.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 124 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index f16f26bb..a8c1a1e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use std::{ collections::{btree_map, BTreeMap, HashSet}, fs, io::{self, Write as _}, - path::PathBuf, + path::{Path, PathBuf}, process, sync::{Arc, Mutex}, time::Duration, @@ -355,7 +355,6 @@ fn notmain() -> Result<i32, anyhow::Error> { #[cfg(feature = "defmt")] let mut frames = vec![]; let mut was_halted = false; - #[cfg(feature = "defmt")] let current_dir = std::env::current_dir()?; // TODO strip prefix from crates-io paths (?) while !exit.load(Ordering::Relaxed) { @@ -475,6 +474,7 @@ fn notmain() -> Result<i32, anyhow::Error> { &pc2frames, &vector_table, &sp_ram_region, + ¤t_dir, )?; core.reset_and_halt(TIMEOUT)?; @@ -620,6 +620,7 @@ fn backtrace( pc2frames: &pc2frames::Map, vector_table: &VectorTable, sp_ram_region: &Option<RamRegion>, + current_dir: &Path, ) -> Result<Option<TopException>, anyhow::Error> { let mut debug_frame = DebugFrame::new(debug_frame, LittleEndian); // 32-bit ARM -- this defaults to the host's address size which is likely going to be 8 @@ -654,10 +655,30 @@ fn backtrace( } else { println!("{:>4}: <unknown> @ {:#012x}", frame_index, pc); } + // XXX is there no location info for external assembly? + println!(" at ???"); frame_index += 1; } else { + let mut call_loc: Option<&pc2frames::Location> = None; + // this iterates in the "callee to caller" direction for frame in frames { println!("{:>4}: {}", frame_index, frame.value.name); + + // call location is more precise; prefer that + let loc = call_loc.unwrap_or_else(|| { + &frame.value.decl_loc + }); + + let relpath = if let Ok(relpath) = loc.file.strip_prefix(¤t_dir) { + relpath + } else { + // not relative; use full path + &loc.file + }; + println!(" at {}:{}", relpath.display(), loc.line); + + // this is from where the caller (next iteration) called the callee (current iteration) + call_loc = frame.value.call_loc.as_ref(); frame_index += 1; } } diff --git a/src/pc2frames.rs b/src/pc2frames.rs index 02418dbb..00f81c78 100644 --- a/src/pc2frames.rs +++ b/src/pc2frames.rs @@ -1,7 +1,11 @@ use core::{iter::FromIterator, ops::Range}; -use std::{borrow::Cow, collections::HashSet}; +use std::{ + borrow::Cow, + collections::HashSet, + path::{Path, PathBuf}, +}; -use anyhow::ensure; +use anyhow::{bail, ensure}; use gimli::{read::Reader, DebuggingInformationEntry, Dwarf, Unit}; use intervaltree::{Element, IntervalTree}; use object::{Object as _, ObjectSection as _}; @@ -70,7 +74,15 @@ pub fn from(object: &object::File, live_functions: &HashSet<&str>) -> Result<Map let name = demangle(&sub.name); elements.push(Element { range, - value: Frame { name, depth }, + value: Frame { + name, + depth, + call_loc: None, + decl_loc: Location { + file: file_index_to_path(sub.decl_file, &unit, &dwarf)?, + line: sub.decl_line, + }, + }, }); } else { // we won't walk into subprograms that are were GC-ed by the linker @@ -91,6 +103,18 @@ pub fn from(object: &object::File, live_functions: &HashSet<&str>) -> Result<Map value: Frame { name: demangle(&inline_sub.origin.name), depth, + call_loc: Some(Location { + file: file_index_to_path(inline_sub.call_file, &unit, &dwarf)?, + line: inline_sub.call_line, + }), + decl_loc: Location { + file: file_index_to_path( + inline_sub.origin.decl_file, + &unit, + &dwarf, + )?, + line: inline_sub.origin.decl_line, + }, }, }) } else if entry.tag() == gimli::constants::DW_TAG_lexical_block @@ -111,7 +135,14 @@ pub struct Frame { pub name: String, // depth in the DIE tree pub depth: isize, - // TODO add file location + pub call_loc: Option<Location>, + pub decl_loc: Location, +} + +#[derive(Debug)] +pub struct Location { + pub file: PathBuf, + pub line: u64, } #[derive(Clone, Debug, PartialEq)] @@ -126,9 +157,8 @@ struct Subprogram { depth: isize, name: String, span: Span, - // NOTE the DIE contains `decl_file` and `decl_line` info but those points into - // the *declaration* of the function, e.g. `fn foo() {`, which is not particularly useful. - // We are more interested in the location of the statements within the function + decl_file: u64, + decl_line: u64, } impl Subprogram { @@ -150,6 +180,8 @@ impl Subprogram { let mut low_pc = None; let mut name = None; let mut pc_offset = None; + let mut decl_file = None; + let mut decl_line = None; while let Some(attr) = attrs.next()? { match attr.name() { gimli::constants::DW_AT_low_pc => { @@ -188,12 +220,26 @@ impl Subprogram { } } + gimli::constants::DW_AT_decl_file => { + if let gimli::AttributeValue::FileIndex(idx) = attr.value() { + decl_file = Some(idx); + } + } + + gimli::constants::DW_AT_decl_line => { + if let gimli::AttributeValue::Udata(line) = attr.value() { + decl_line = Some(line); + } + } + _ => {} } } if let Some(off) = linkage_name.or(name) { let name = dwarf.string(off)?.to_string()?.into_owned(); + let decl_file = decl_file.expect("no `decl_file`"); + let decl_line = decl_line.expect("no `decl_line`"); Ok(Some(Subprogram { depth, @@ -205,6 +251,8 @@ impl Subprogram { Span::Pc(low_pc..(low_pc + pc_off)) }, name, + decl_file, + decl_line, })) } else { // TODO what are these nameless subroutines? They seem to have "abstract origin" info @@ -323,3 +371,49 @@ fn demangle(function: &str) -> String { demangled } + +// XXX copy-pasted from defmt/elf2table :sadface: +fn file_index_to_path<R>( + index: u64, + unit: &gimli::Unit<R>, + dwarf: &gimli::Dwarf<R>, +) -> Result<PathBuf, anyhow::Error> +where + R: gimli::read::Reader, +{ + ensure!(index != 0, "`FileIndex` was zero"); + + let header = if let Some(program) = &unit.line_program { + program.header() + } else { + bail!("no `LineProgram`"); + }; + + let file = if let Some(file) = header.file(index) { + file + } else { + bail!("no `FileEntry` for index {}", index) + }; + + let mut p = PathBuf::new(); + if let Some(dir) = file.directory(header) { + let dir = dwarf.attr_string(unit, dir)?; + let dir_s = dir.to_string_lossy()?; + let dir = Path::new(&dir_s[..]); + + if !dir.is_absolute() { + if let Some(ref comp_dir) = unit.comp_dir { + p.push(&comp_dir.to_string_lossy()?[..]); + } + } + p.push(&dir); + } + + p.push( + &dwarf + .attr_string(unit, file.path_name())? + .to_string_lossy()?[..], + ); + + Ok(p) +} From 40aaa4194a176e0c746ec6ac99d2d96a06022751 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio <jorge.aparicio@ferrous-systems.com> Date: Thu, 17 Sep 2020 14:01:13 +0200 Subject: [PATCH 5/7] actually, let's use addr2line --- Cargo.lock | 47 ++++-- Cargo.toml | 7 +- src/main.rs | 100 ++++++----- src/pc2frames.rs | 419 ----------------------------------------------- 4 files changed, 93 insertions(+), 480 deletions(-) delete mode 100644 src/pc2frames.rs diff --git a/Cargo.lock b/Cargo.lock index 7188aab6..76846faf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,19 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" +dependencies = [ + "cpp_demangle", + "fallible-iterator", + "gimli 0.22.0", + "object 0.20.0", + "rustc-demangle", + "smallvec", +] + [[package]] name = "adler" version = "0.2.3" @@ -180,6 +194,16 @@ dependencies = [ "syn", ] +[[package]] +name = "cpp_demangle" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad49cad3673b9d586bc50cd92fdc0e9205ecd162d4206073d9774c4fe13a8fde" +dependencies = [ + "cfg-if", + "glob", +] + [[package]] name = "crc32fast" version = "1.2.0" @@ -341,6 +365,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "goblin" version = "0.2.3" @@ -403,15 +433,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "intervaltree" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566d5aa3b5cc5c5809cc1a9c9588d917a634248bfc58f7ea14e354e71595a32c" -dependencies = [ - "smallvec", -] - [[package]] name = "jaylink" version = "0.1.5" @@ -525,10 +546,6 @@ name = "object" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693" -dependencies = [ - "flate2", - "wasmparser", -] [[package]] name = "panic-probe" @@ -611,15 +628,15 @@ dependencies = [ name = "probe-run" version = "0.1.3" dependencies = [ + "addr2line", "anyhow", "arrayref", "colored", "defmt-decoder", "defmt-elf2table", "gimli 0.22.0", - "intervaltree", "log", - "object 0.21.1", + "object 0.20.0", "probe-rs", "probe-rs-rtt", "rustc-demangle", diff --git a/Cargo.toml b/Cargo.toml index c8b60fc7..1d32531f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,20 +11,21 @@ repository = "https://github.com/knurling-rs/probe-run" version = "0.1.3" [dependencies] +addr2line = "0.13.0" anyhow = "1.0.32" arrayref = "0.3.6" colored = "2.0.0" -signal-hook = "0.1.16" defmt-decoder = { git = "https://github.com/knurling-rs/defmt", branch = "main", optional = true } defmt-elf2table = { git = "https://github.com/knurling-rs/defmt", branch = "main", optional = true } gimli = "0.22.0" log = { version = "0.4.11", features = ["std"] } +# an addr2line trait is implement for a type in this particular version +object = "0.20.0" probe-rs = "0.8.0" probe-rs-rtt = "0.3.0" rustc-demangle = "0.1.16" +signal-hook = "0.1.16" structopt = "0.3.15" -object = "0.21.1" -intervaltree = "0.2.6" [features] defmt = ["defmt-elf2table", "defmt-decoder"] diff --git a/src/main.rs b/src/main.rs index a8c1a1e2..32cb3324 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ mod logger; -mod pc2frames; use core::{ cmp, @@ -8,6 +7,7 @@ use core::{ sync::atomic::{AtomicBool, Ordering}, }; use std::{ + borrow::Cow, collections::{btree_map, BTreeMap, HashSet}, fs, io::{self, Write as _}, @@ -17,6 +17,7 @@ use std::{ time::Duration, }; +use addr2line::fallible_iterator::FallibleIterator as _; use anyhow::{anyhow, bail, Context}; use arrayref::array_ref; use colored::Colorize as _; @@ -244,7 +245,6 @@ fn notmain() -> Result<i32, anyhow::Error> { }) .collect::<HashSet<_>>(); - let pc2frames = pc2frames::from(&elf, &live_functions)?; let (rtt_addr, uses_heap) = rtt_and_heap_info_from(&elf)?; let vector_table = vector_table.ok_or_else(|| anyhow!("`.vector_table` section is missing"))?; @@ -471,9 +471,9 @@ fn notmain() -> Result<i32, anyhow::Error> { pc, debug_frame, &elf, - &pc2frames, &vector_table, &sp_ram_region, + &live_functions, ¤t_dir, )?; @@ -617,9 +617,9 @@ fn backtrace( mut pc: u32, debug_frame: &[u8], elf: &ElfFile, - pc2frames: &pc2frames::Map, vector_table: &VectorTable, sp_ram_region: &Option<RamRegion>, + live_functions: &HashSet<&str>, current_dir: &Path, ) -> Result<Option<TopException>, anyhow::Error> { let mut debug_frame = DebugFrame::new(debug_frame, LittleEndian); @@ -633,54 +633,68 @@ fn backtrace( let bases = &BaseAddresses::default(); let ctx = &mut UninitializedUnwindContext::new(); + let addr2line = addr2line::Context::new(elf)?; let mut top_exception = None; let mut frame_index = 0; let mut registers = Registers::new(lr, sp, core); + let symtab = elf.symbol_map(); println!("stack backtrace:"); loop { - let mut frames = pc2frames.query_point(pc as u64).collect::<Vec<_>>(); - // `IntervalTree` docs don't specify the order of the elements returned by `query_point` so - // we sort them by depth before printing - frames.sort_by_key(|frame| -frame.value.depth); - - if frames.is_empty() { - // this means there was no DWARF info associated to this PC; the PC could point into - // external assembly or external C code. Fall back to a symtab lookup - let map = elf.symbol_map(); - // confusingly enough the addresses in the `.symtab` do have their thumb bit set to 1 - // so set it back before the lookup - let addr = (pc | THUMB_BIT) as u64; - if let Some(name) = map.get(addr).and_then(|symbol| symbol.name()) { - println!("{:>4}: {}", frame_index, name); - } else { - println!("{:>4}: <unknown> @ {:#012x}", frame_index, pc); - } - // XXX is there no location info for external assembly? - println!(" at ???"); - frame_index += 1; + let frames = addr2line.find_frames(pc as u64)?.collect::<Vec<_>>()?; + + // `find_frames` returns a wrong answer, instead of an `Err`or, when the input is the PC of + // a subroutine that has no debug information (e.g. external assembly). The wrong answer is + // one of the subroutines GC-ed by the linker so we check that the last frame + // (the non-inline one) is actually "live" (exists in the final binary). If it doesn't then + // we probably asked about something with no debug info. In that scenario we fallback to + // the symtab to at least provide the function name that contains the PC. + let subroutine = frames + .last() + .expect("BUG: `addr2line::FrameIter` was empty"); + let has_valid_debuginfo = if let Some(function) = subroutine.function.as_ref() { + live_functions.contains(&*function.raw_name()?) } else { - let mut call_loc: Option<&pc2frames::Location> = None; - // this iterates in the "callee to caller" direction - for frame in frames { - println!("{:>4}: {}", frame_index, frame.value.name); - - // call location is more precise; prefer that - let loc = call_loc.unwrap_or_else(|| { - &frame.value.decl_loc - }); - - let relpath = if let Ok(relpath) = loc.file.strip_prefix(¤t_dir) { - relpath - } else { - // not relative; use full path - &loc.file - }; - println!(" at {}:{}", relpath.display(), loc.line); + false + }; - // this is from where the caller (next iteration) called the callee (current iteration) - call_loc = frame.value.call_loc.as_ref(); + if has_valid_debuginfo { + for frame in &frames { + let name = frame + .function + .as_ref() + .map(|function| function.demangle()) + .transpose()? + .unwrap_or(Cow::Borrowed("???")); + + println!("{:>4}: {}", frame_index, name); frame_index += 1; + + if let Some((file, line)) = frame + .location + .as_ref() + .and_then(|loc| loc.file.and_then(|file| loc.line.map(|line| (file, line)))) + { + let file = Path::new(file); + let relpath = if let Ok(relpath) = file.strip_prefix(¤t_dir) { + relpath + } else { + // not within current directory; use full path + file + }; + println!(" at {}:{}", relpath.display(), line); + } } + } else { + // .symtab fallback + // confusingly enough the addresses in the `.symtab` do have their thumb bit set to 1 + // so set it back before the lookup + let address = (pc | THUMB_BIT) as u64; + let name = symtab + .get(address) + .and_then(|symbol| symbol.name()) + .unwrap_or("???"); + println!("{:>4}: {}", frame_index, name); + frame_index += 1; } // on hard fault exception entry we hit the breakpoint before the subroutine prelude (`push diff --git a/src/pc2frames.rs b/src/pc2frames.rs deleted file mode 100644 index 00f81c78..00000000 --- a/src/pc2frames.rs +++ /dev/null @@ -1,419 +0,0 @@ -use core::{iter::FromIterator, ops::Range}; -use std::{ - borrow::Cow, - collections::HashSet, - path::{Path, PathBuf}, -}; - -use anyhow::{bail, ensure}; -use gimli::{read::Reader, DebuggingInformationEntry, Dwarf, Unit}; -use intervaltree::{Element, IntervalTree}; -use object::{Object as _, ObjectSection as _}; - -pub type Map = IntervalTree<u64, Frame>; - -pub fn from(object: &object::File, live_functions: &HashSet<&str>) -> Result<Map, anyhow::Error> { - let endian = if object.is_little_endian() { - gimli::RunTimeEndian::Little - } else { - gimli::RunTimeEndian::Big - }; - - let load_section = |id: gimli::SectionId| { - Ok(if let Some(s) = object.section_by_name(id.name()) { - s.uncompressed_data().unwrap_or(Cow::Borrowed(&[][..])) - } else { - Cow::Borrowed(&[][..]) - }) - }; - let load_section_sup = |_| Ok(Cow::Borrowed(&[][..])); - - let dwarf_cow = - gimli::Dwarf::<Cow<[u8]>>::load::<_, _, anyhow::Error>(&load_section, &load_section_sup)?; - - let borrow_section: &dyn for<'a> Fn( - &'a Cow<[u8]>, - ) -> gimli::EndianSlice<'a, gimli::RunTimeEndian> = - &|section| gimli::EndianSlice::new(&*section, endian); - - let dwarf = dwarf_cow.borrow(&borrow_section); - - let mut units = dwarf.debug_info.units(); - - let mut elements = vec![]; - while let Some(header) = units.next()? { - let unit = dwarf.unit(header)?; - let abbrev = header.abbreviations(&dwarf.debug_abbrev)?; - - let mut cursor = header.entries(&abbrev); - - ensure!(cursor.next_dfs()?.is_some(), "empty DWARF?"); - - let mut depth = 0; - // None = outside a subprogram DIE - // Some(depth) = inside a subprogram DIE - let mut subprogram_depth = None; - while let Some((delta_depth, entry)) = cursor.next_dfs()? { - depth += delta_depth; - - if let Some(subprogram_depth_val) = subprogram_depth { - if depth <= subprogram_depth_val { - // leaving subprogram DIE - subprogram_depth = None; - } - } - - if entry.tag() == gimli::constants::DW_TAG_subprogram { - if let Some(sub) = Subprogram::from_die(entry, depth, &dwarf)? { - if let Span::Pc(range) = sub.span.clone() { - if live_functions.contains(&*sub.name) { - // sanity check: nested subprograms have never been observed in practice - assert!(subprogram_depth.is_none(), "BUG? nested subprogram"); - - subprogram_depth = Some(depth); - let name = demangle(&sub.name); - elements.push(Element { - range, - value: Frame { - name, - depth, - call_loc: None, - decl_loc: Location { - file: file_index_to_path(sub.decl_file, &unit, &dwarf)?, - line: sub.decl_line, - }, - }, - }); - } else { - // we won't walk into subprograms that are were GC-ed by the linker - } - } else { - // subprograms with "inlined" span will be referred to by the 'origin' - // field of `InlinedSubroutine`s so we don't add them to the list at this - // point. Also, they don't have PC span info and won't appear as a symbol - // in the .symtab - } - } - } else if subprogram_depth.is_some() { - // within a 'live' subroutine (subroutine was not GC-ed by the linker) - if entry.tag() == gimli::constants::DW_TAG_inlined_subroutine { - let inline_sub = InlinedSubroutine::from_die(entry, depth, &dwarf, &unit)?; - elements.push(Element { - range: inline_sub.pc, - value: Frame { - name: demangle(&inline_sub.origin.name), - depth, - call_loc: Some(Location { - file: file_index_to_path(inline_sub.call_file, &unit, &dwarf)?, - line: inline_sub.call_line, - }), - decl_loc: Location { - file: file_index_to_path( - inline_sub.origin.decl_file, - &unit, - &dwarf, - )?, - line: inline_sub.origin.decl_line, - }, - }, - }) - } else if entry.tag() == gimli::constants::DW_TAG_lexical_block - || entry.tag() == gimli::constants::DW_TAG_variable - { - // TODO extract more fine grained (statement-level) location information - } - } - } - } - - Ok(IntervalTree::from_iter(elements)) -} - -#[derive(Debug)] -pub struct Frame { - // unmangled function name - pub name: String, - // depth in the DIE tree - pub depth: isize, - pub call_loc: Option<Location>, - pub decl_loc: Location, -} - -#[derive(Debug)] -pub struct Location { - pub file: PathBuf, - pub line: u64, -} - -#[derive(Clone, Debug, PartialEq)] -enum Span { - Pc(Range<u64>), - Inlined, -} - -#[derive(Debug)] -struct Subprogram { - // depth in the DIE tree - depth: isize, - name: String, - span: Span, - decl_file: u64, - decl_line: u64, -} - -impl Subprogram { - /// returns `None` if `entry` has no "name" - fn from_die<R>( - entry: &DebuggingInformationEntry<R>, - depth: isize, - dwarf: &Dwarf<R>, - ) -> Result<Option<Self>, anyhow::Error> - where - R: Reader, - { - assert_eq!(entry.tag(), gimli::constants::DW_TAG_subprogram); - - let mut attrs = entry.attrs(); - - let mut inlined = false; - let mut linkage_name = None; - let mut low_pc = None; - let mut name = None; - let mut pc_offset = None; - let mut decl_file = None; - let mut decl_line = None; - while let Some(attr) = attrs.next()? { - match attr.name() { - gimli::constants::DW_AT_low_pc => { - if let gimli::AttributeValue::Addr(addr) = attr.value() { - low_pc = Some(addr); - } else { - unreachable!() - } - } - - gimli::constants::DW_AT_high_pc => { - pc_offset = Some(attr.value().udata_value().expect("unreachable")); - } - - gimli::constants::DW_AT_linkage_name => { - if let gimli::AttributeValue::DebugStrRef(off) = attr.value() { - linkage_name = Some(off); - } else { - unreachable!() - } - } - - gimli::constants::DW_AT_name => { - if let gimli::AttributeValue::DebugStrRef(off) = attr.value() { - name = Some(off); - } else { - unreachable!() - } - } - - gimli::constants::DW_AT_inline => { - if let gimli::AttributeValue::Inline(gimli::constants::DW_INL_inlined) = - attr.value() - { - inlined = true; - } - } - - gimli::constants::DW_AT_decl_file => { - if let gimli::AttributeValue::FileIndex(idx) = attr.value() { - decl_file = Some(idx); - } - } - - gimli::constants::DW_AT_decl_line => { - if let gimli::AttributeValue::Udata(line) = attr.value() { - decl_line = Some(line); - } - } - - _ => {} - } - } - - if let Some(off) = linkage_name.or(name) { - let name = dwarf.string(off)?.to_string()?.into_owned(); - let decl_file = decl_file.expect("no `decl_file`"); - let decl_line = decl_line.expect("no `decl_line`"); - - Ok(Some(Subprogram { - depth, - span: if inlined { - Span::Inlined - } else { - let low_pc = low_pc.expect("no `low_pc`"); - let pc_off = pc_offset.expect("no `high_pc`"); - Span::Pc(low_pc..(low_pc + pc_off)) - }, - name, - decl_file, - decl_line, - })) - } else { - // TODO what are these nameless subroutines? They seem to have "abstract origin" info - Ok(None) - } - } -} - -#[derive(Debug)] -struct InlinedSubroutine { - call_file: u64, - call_line: u64, - origin: Subprogram, - pc: Range<u64>, -} - -impl InlinedSubroutine { - fn from_die<R>( - entry: &DebuggingInformationEntry<R>, - depth: isize, - dwarf: &Dwarf<R>, - unit: &Unit<R>, - ) -> Result<Self, anyhow::Error> - where - R: Reader, - { - assert_eq!(entry.tag(), gimli::constants::DW_TAG_inlined_subroutine); - - let mut attrs = entry.attrs(); - - let mut at_range = None; - let mut call_file = None; - let mut call_line = None; - let mut low_pc = None; - let mut origin = None; - let mut pc_offset = None; - while let Some(attr) = attrs.next()? { - match attr.name() { - gimli::constants::DW_AT_abstract_origin => { - if let gimli::AttributeValue::UnitRef(off) = attr.value() { - let other_entry = unit.entry(off)?; - - let sub = Subprogram::from_die(&other_entry, depth, dwarf)?.unwrap(); - origin = Some(sub); - } else { - unreachable!() - } - } - - gimli::constants::DW_AT_ranges => { - if let gimli::AttributeValue::RangeListsRef(off) = attr.value() { - let r = dwarf - .ranges(&unit, off)? - .next()? - .expect("unexpected end of range list"); - at_range = Some(r.begin..r.end); - } - } - - gimli::constants::DW_AT_low_pc => { - if let gimli::AttributeValue::Addr(addr) = attr.value() { - low_pc = Some(addr); - } else { - unreachable!() - } - } - - gimli::constants::DW_AT_high_pc => { - pc_offset = Some(attr.value().udata_value().expect("unreachable")); - } - - gimli::constants::DW_AT_call_file => { - if let gimli::AttributeValue::FileIndex(idx) = attr.value() { - call_file = Some(idx); - } - } - - gimli::constants::DW_AT_call_line => { - if let gimli::AttributeValue::Udata(line) = attr.value() { - call_line = Some(line); - } - } - - _ => {} - } - } - - let pc = at_range.unwrap_or_else(|| { - let start = low_pc.expect("no low_pc"); - let off = pc_offset.expect("no high_pc"); - start..start + off - }); - - Ok(InlinedSubroutine { - origin: origin.expect("no abstract_origin"), - call_file: call_file.expect("no call_file"), - call_line: call_line.expect("no call_line"), - pc, - }) - } -} - -fn demangle(function: &str) -> String { - let mut demangled = rustc_demangle::demangle(function).to_string(); - // remove trailing hash (`::he40fe02240f4a81d`) - // strip the hash (e.g. `::hd881d91ced85c2b0`) - let hash_len = "::hd881d91ced85c2b0".len(); - if let Some(pos) = demangled.len().checked_sub(hash_len) { - let maybe_hash = &demangled[pos..]; - if maybe_hash.starts_with("::h") { - for _ in 0..hash_len { - demangled.pop(); - } - } - } - - demangled -} - -// XXX copy-pasted from defmt/elf2table :sadface: -fn file_index_to_path<R>( - index: u64, - unit: &gimli::Unit<R>, - dwarf: &gimli::Dwarf<R>, -) -> Result<PathBuf, anyhow::Error> -where - R: gimli::read::Reader, -{ - ensure!(index != 0, "`FileIndex` was zero"); - - let header = if let Some(program) = &unit.line_program { - program.header() - } else { - bail!("no `LineProgram`"); - }; - - let file = if let Some(file) = header.file(index) { - file - } else { - bail!("no `FileEntry` for index {}", index) - }; - - let mut p = PathBuf::new(); - if let Some(dir) = file.directory(header) { - let dir = dwarf.attr_string(unit, dir)?; - let dir_s = dir.to_string_lossy()?; - let dir = Path::new(&dir_s[..]); - - if !dir.is_absolute() { - if let Some(ref comp_dir) = unit.comp_dir { - p.push(&comp_dir.to_string_lossy()?[..]); - } - } - p.push(&dir); - } - - p.push( - &dwarf - .attr_string(unit, file.path_name())? - .to_string_lossy()?[..], - ); - - Ok(p) -} From 12c188bd557917192b371426b08947f999b54ee2 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio <jorge.aparicio@ferrous-systems.com> Date: Thu, 17 Sep 2020 15:08:37 +0200 Subject: [PATCH 6/7] restore `let text` to its original position --- src/main.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index 32cb3324..ec0b4dc9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -124,6 +124,17 @@ fn notmain() -> Result<i32, anyhow::Error> { ); } + // NOTE we want to raise the linking error before calling `defmt_elf2table::parse` + let text = elf + .section_by_name(".text") + .map(|section| section.index()) + .ok_or_else(|| { + anyhow!( + "`.text` section is missing, please make sure that the linker script was passed to the \ + linker (check `.cargo/config.toml` and the `RUSTFLAGS` variable)" + ) + })?; + #[cfg(feature = "defmt")] let (table, locs) = { let table = defmt_elf2table::parse(&bytes)?; @@ -222,16 +233,6 @@ fn notmain() -> Result<i32, anyhow::Error> { } } - let text = elf - .section_by_name(".text") - .map(|section| section.index()) - .ok_or_else(|| { - anyhow!( - "`.text` section is missing, please make sure that the linker script was passed to the \ - linker (check `.cargo/config.toml` and the `RUSTFLAGS` variable)" - ) - })?; - let live_functions = elf .symbol_map() .symbols() From a457dfd5cfa300344905dab2c7658c1e5eb11319 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio <jorge.aparicio@ferrous-systems.com> Date: Thu, 17 Sep 2020 15:11:30 +0200 Subject: [PATCH 7/7] elaborate on why we set the thumb bit before the symtab lookup --- src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index ec0b4dc9..68ae63cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -687,8 +687,10 @@ fn backtrace( } } else { // .symtab fallback - // confusingly enough the addresses in the `.symtab` do have their thumb bit set to 1 - // so set it back before the lookup + // the .symtab appears to use address ranges that have their thumb bits set (e.g. + // `0x101..0x200`). Passing the `pc` with the thumb bit cleared (e.g. `0x100`) to the + // lookup function sometimes returns the *previous* symbol. Work around the issue by + // setting `pc`'s thumb bit before looking it up let address = (pc | THUMB_BIT) as u64; let name = symtab .get(address)