Skip to content
This repository was archived by the owner on Jan 30, 2024. It is now read-only.

Commit 79460fa

Browse files
author
Jonas Schievink
authored
Merge pull request #63 from knurling-rs/unwind-dwarf
add location info to the backtrace
2 parents 7e26e33 + a457dfd commit 79460fa

File tree

3 files changed

+137
-61
lines changed

3 files changed

+137
-61
lines changed

Cargo.lock

+38-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+4-2
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,21 @@ repository = "https://github.com/knurling-rs/probe-run"
1111
version = "0.1.3"
1212

1313
[dependencies]
14+
addr2line = "0.13.0"
1415
anyhow = "1.0.32"
1516
arrayref = "0.3.6"
1617
colored = "2.0.0"
17-
signal-hook = "0.1.16"
1818
defmt-decoder = { git = "https://github.com/knurling-rs/defmt", branch = "main", optional = true }
1919
defmt-elf2table = { git = "https://github.com/knurling-rs/defmt", branch = "main", optional = true }
2020
gimli = "0.22.0"
2121
log = { version = "0.4.11", features = ["std"] }
22+
# an addr2line trait is implement for a type in this particular version
23+
object = "0.20.0"
2224
probe-rs = "0.8.0"
2325
probe-rs-rtt = "0.3.0"
2426
rustc-demangle = "0.1.16"
27+
signal-hook = "0.1.16"
2528
structopt = "0.3.15"
26-
object = "0.21.1"
2729

2830
[features]
2931
defmt = ["defmt-elf2table", "defmt-decoder"]

src/main.rs

+95-54
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@ use core::{
44
cmp,
55
convert::TryInto,
66
mem,
7-
ops::Range,
87
sync::atomic::{AtomicBool, Ordering},
98
};
109
use std::{
11-
collections::{btree_map, BTreeMap},
10+
borrow::Cow,
11+
collections::{btree_map, BTreeMap, HashSet},
1212
fs,
1313
io::{self, Write as _},
14-
path::PathBuf,
14+
path::{Path, PathBuf},
1515
process,
1616
sync::{Arc, Mutex},
1717
time::Duration,
1818
};
1919

20+
use addr2line::fallible_iterator::FallibleIterator as _;
2021
use anyhow::{anyhow, bail, Context};
2122
use arrayref::array_ref;
2223
use colored::Colorize as _;
@@ -26,7 +27,7 @@ use gimli::{
2627
};
2728
use object::{
2829
read::{File as ElfFile, Object as _, ObjectSection as _},
29-
SectionIndex,
30+
SymbolSection,
3031
};
3132
use probe_rs::config::{registry, MemoryRegion, RamRegion};
3233
use probe_rs::{
@@ -123,12 +124,16 @@ fn notmain() -> Result<i32, anyhow::Error> {
123124
);
124125
}
125126

126-
let text = elf.section_by_name(".text").ok_or_else(|| {
127-
anyhow!(
127+
// NOTE we want to raise the linking error before calling `defmt_elf2table::parse`
128+
let text = elf
129+
.section_by_name(".text")
130+
.map(|section| section.index())
131+
.ok_or_else(|| {
132+
anyhow!(
128133
"`.text` section is missing, please make sure that the linker script was passed to the \
129134
linker (check `.cargo/config.toml` and the `RUSTFLAGS` variable)"
130135
)
131-
})?;
136+
})?;
132137

133138
#[cfg(feature = "defmt")]
134139
let (table, locs) = {
@@ -228,7 +233,20 @@ fn notmain() -> Result<i32, anyhow::Error> {
228233
}
229234
}
230235

231-
let (range_names, rtt_addr, uses_heap) = range_names_from(&elf, text.index())?;
236+
let live_functions = elf
237+
.symbol_map()
238+
.symbols()
239+
.iter()
240+
.filter_map(|sym| {
241+
if sym.section() == SymbolSection::Section(text) {
242+
sym.name()
243+
} else {
244+
None
245+
}
246+
})
247+
.collect::<HashSet<_>>();
248+
249+
let (rtt_addr, uses_heap) = rtt_and_heap_info_from(&elf)?;
232250

233251
let vector_table = vector_table.ok_or_else(|| anyhow!("`.vector_table` section is missing"))?;
234252
log::debug!("vector table: {:x?}", vector_table);
@@ -338,7 +356,6 @@ fn notmain() -> Result<i32, anyhow::Error> {
338356
#[cfg(feature = "defmt")]
339357
let mut frames = vec![];
340358
let mut was_halted = false;
341-
#[cfg(feature = "defmt")]
342359
let current_dir = std::env::current_dir()?;
343360
// TODO strip prefix from crates-io paths (?)
344361
while !exit.load(Ordering::Relaxed) {
@@ -454,9 +471,11 @@ fn notmain() -> Result<i32, anyhow::Error> {
454471
&mut core,
455472
pc,
456473
debug_frame,
457-
&range_names,
474+
&elf,
458475
&vector_table,
459476
&sp_ram_region,
477+
&live_functions,
478+
&current_dir,
460479
)?;
461480

462481
core.reset_and_halt(TIMEOUT)?;
@@ -598,9 +617,11 @@ fn backtrace(
598617
core: &mut Core<'_>,
599618
mut pc: u32,
600619
debug_frame: &[u8],
601-
range_names: &RangeNames,
620+
elf: &ElfFile,
602621
vector_table: &VectorTable,
603622
sp_ram_region: &Option<RamRegion>,
623+
live_functions: &HashSet<&str>,
624+
current_dir: &Path,
604625
) -> Result<Option<TopException>, anyhow::Error> {
605626
let mut debug_frame = DebugFrame::new(debug_frame, LittleEndian);
606627
// 32-bit ARM -- this defaults to the host's address size which is likely going to be 8
@@ -613,24 +634,71 @@ fn backtrace(
613634
let bases = &BaseAddresses::default();
614635
let ctx = &mut UninitializedUnwindContext::new();
615636

637+
let addr2line = addr2line::Context::new(elf)?;
616638
let mut top_exception = None;
617-
let mut frame = 0;
639+
let mut frame_index = 0;
618640
let mut registers = Registers::new(lr, sp, core);
641+
let symtab = elf.symbol_map();
619642
println!("stack backtrace:");
620643
loop {
621-
let name = range_names
622-
.binary_search_by(|rn| {
623-
if rn.0.contains(&pc) {
624-
cmp::Ordering::Equal
625-
} else if pc < rn.0.start {
626-
cmp::Ordering::Greater
627-
} else {
628-
cmp::Ordering::Less
644+
let frames = addr2line.find_frames(pc as u64)?.collect::<Vec<_>>()?;
645+
646+
// `find_frames` returns a wrong answer, instead of an `Err`or, when the input is the PC of
647+
// a subroutine that has no debug information (e.g. external assembly). The wrong answer is
648+
// one of the subroutines GC-ed by the linker so we check that the last frame
649+
// (the non-inline one) is actually "live" (exists in the final binary). If it doesn't then
650+
// we probably asked about something with no debug info. In that scenario we fallback to
651+
// the symtab to at least provide the function name that contains the PC.
652+
let subroutine = frames
653+
.last()
654+
.expect("BUG: `addr2line::FrameIter` was empty");
655+
let has_valid_debuginfo = if let Some(function) = subroutine.function.as_ref() {
656+
live_functions.contains(&*function.raw_name()?)
657+
} else {
658+
false
659+
};
660+
661+
if has_valid_debuginfo {
662+
for frame in &frames {
663+
let name = frame
664+
.function
665+
.as_ref()
666+
.map(|function| function.demangle())
667+
.transpose()?
668+
.unwrap_or(Cow::Borrowed("???"));
669+
670+
println!("{:>4}: {}", frame_index, name);
671+
frame_index += 1;
672+
673+
if let Some((file, line)) = frame
674+
.location
675+
.as_ref()
676+
.and_then(|loc| loc.file.and_then(|file| loc.line.map(|line| (file, line))))
677+
{
678+
let file = Path::new(file);
679+
let relpath = if let Ok(relpath) = file.strip_prefix(&current_dir) {
680+
relpath
681+
} else {
682+
// not within current directory; use full path
683+
file
684+
};
685+
println!(" at {}:{}", relpath.display(), line);
629686
}
630-
})
631-
.map(|idx| &*range_names[idx].1)
632-
.unwrap_or("<unknown>");
633-
println!("{:>4}: {:#010x} - {}", frame, pc, name);
687+
}
688+
} else {
689+
// .symtab fallback
690+
// the .symtab appears to use address ranges that have their thumb bits set (e.g.
691+
// `0x101..0x200`). Passing the `pc` with the thumb bit cleared (e.g. `0x100`) to the
692+
// lookup function sometimes returns the *previous* symbol. Work around the issue by
693+
// setting `pc`'s thumb bit before looking it up
694+
let address = (pc | THUMB_BIT) as u64;
695+
let name = symtab
696+
.get(address)
697+
.and_then(|symbol| symbol.name())
698+
.unwrap_or("???");
699+
println!("{:>4}: {}", frame_index, name);
700+
frame_index += 1;
701+
}
634702

635703
// on hard fault exception entry we hit the breakpoint before the subroutine prelude (`push
636704
// lr`) is executed so special handling is required
@@ -708,8 +776,6 @@ fn backtrace(
708776
}
709777
pc = lr & !THUMB_BIT;
710778
}
711-
712-
frame += 1;
713779
}
714780

715781
Ok(top_exception)
@@ -853,15 +919,10 @@ impl Stacked {
853919
num_words as u32 * 4
854920
}
855921
}
856-
// FIXME this might already exist in the DWARF data; we should just use that
857-
/// Map from PC ranges to demangled Rust names
858-
type RangeNames = Vec<(Range<u32>, String)>;
859922

860-
fn range_names_from(
923+
fn rtt_and_heap_info_from(
861924
elf: &ElfFile,
862-
text: SectionIndex,
863-
) -> Result<(RangeNames, Option<u32>, bool /* uses heap */), anyhow::Error> {
864-
let mut range_names = vec![];
925+
) -> Result<(Option<u32>, bool /* uses heap */), anyhow::Error> {
865926
let mut rtt = None;
866927
let mut uses_heap = false;
867928

@@ -879,29 +940,9 @@ fn range_names_from(
879940
}
880941
_ => {}
881942
}
882-
883-
if symbol.section_index() == Some(text) && symbol.size() > 0 {
884-
let mut name = rustc_demangle::demangle(name).to_string();
885-
// clear the thumb bit
886-
let start = symbol.address() as u32 & !1;
887-
888-
// strip the hash (e.g. `::hd881d91ced85c2b0`)
889-
let hash_len = "::hd881d91ced85c2b0".len();
890-
if let Some(pos) = name.len().checked_sub(hash_len) {
891-
let maybe_hash = &name[pos..];
892-
if maybe_hash.starts_with("::h") {
893-
// FIXME avoid this allocation
894-
name = name[..pos].to_string();
895-
}
896-
}
897-
898-
range_names.push((start..start + symbol.size() as u32, name));
899-
}
900943
}
901944

902-
range_names.sort_unstable_by(|a, b| a.0.start.cmp(&b.0.start));
903-
904-
Ok((range_names, rtt, uses_heap))
945+
Ok((rtt, uses_heap))
905946
}
906947

907948
const LR: CoreRegisterAddress = CoreRegisterAddress(14);

0 commit comments

Comments
 (0)