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,
+        &current_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(&current_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,
         &current_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(&current_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(&current_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)