@@ -4,19 +4,20 @@ use core::{
4
4
cmp,
5
5
convert:: TryInto ,
6
6
mem,
7
- ops:: Range ,
8
7
sync:: atomic:: { AtomicBool , Ordering } ,
9
8
} ;
10
9
use std:: {
11
- collections:: { btree_map, BTreeMap } ,
10
+ borrow:: Cow ,
11
+ collections:: { btree_map, BTreeMap , HashSet } ,
12
12
fs,
13
13
io:: { self , Write as _} ,
14
- path:: PathBuf ,
14
+ path:: { Path , PathBuf } ,
15
15
process,
16
16
sync:: { Arc , Mutex } ,
17
17
time:: Duration ,
18
18
} ;
19
19
20
+ use addr2line:: fallible_iterator:: FallibleIterator as _;
20
21
use anyhow:: { anyhow, bail, Context } ;
21
22
use arrayref:: array_ref;
22
23
use colored:: Colorize as _;
@@ -26,7 +27,7 @@ use gimli::{
26
27
} ;
27
28
use object:: {
28
29
read:: { File as ElfFile , Object as _, ObjectSection as _} ,
29
- SectionIndex ,
30
+ SymbolSection ,
30
31
} ;
31
32
use probe_rs:: config:: { registry, MemoryRegion , RamRegion } ;
32
33
use probe_rs:: {
@@ -123,12 +124,16 @@ fn notmain() -> Result<i32, anyhow::Error> {
123
124
) ;
124
125
}
125
126
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 ! (
128
133
"`.text` section is missing, please make sure that the linker script was passed to the \
129
134
linker (check `.cargo/config.toml` and the `RUSTFLAGS` variable)"
130
135
)
131
- } ) ?;
136
+ } ) ?;
132
137
133
138
#[ cfg( feature = "defmt" ) ]
134
139
let ( table, locs) = {
@@ -228,7 +233,20 @@ fn notmain() -> Result<i32, anyhow::Error> {
228
233
}
229
234
}
230
235
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) ?;
232
250
233
251
let vector_table = vector_table. ok_or_else ( || anyhow ! ( "`.vector_table` section is missing" ) ) ?;
234
252
log:: debug!( "vector table: {:x?}" , vector_table) ;
@@ -338,7 +356,6 @@ fn notmain() -> Result<i32, anyhow::Error> {
338
356
#[ cfg( feature = "defmt" ) ]
339
357
let mut frames = vec ! [ ] ;
340
358
let mut was_halted = false ;
341
- #[ cfg( feature = "defmt" ) ]
342
359
let current_dir = std:: env:: current_dir ( ) ?;
343
360
// TODO strip prefix from crates-io paths (?)
344
361
while !exit. load ( Ordering :: Relaxed ) {
@@ -454,9 +471,11 @@ fn notmain() -> Result<i32, anyhow::Error> {
454
471
& mut core,
455
472
pc,
456
473
debug_frame,
457
- & range_names ,
474
+ & elf ,
458
475
& vector_table,
459
476
& sp_ram_region,
477
+ & live_functions,
478
+ & current_dir,
460
479
) ?;
461
480
462
481
core. reset_and_halt ( TIMEOUT ) ?;
@@ -598,9 +617,11 @@ fn backtrace(
598
617
core : & mut Core < ' _ > ,
599
618
mut pc : u32 ,
600
619
debug_frame : & [ u8 ] ,
601
- range_names : & RangeNames ,
620
+ elf : & ElfFile ,
602
621
vector_table : & VectorTable ,
603
622
sp_ram_region : & Option < RamRegion > ,
623
+ live_functions : & HashSet < & str > ,
624
+ current_dir : & Path ,
604
625
) -> Result < Option < TopException > , anyhow:: Error > {
605
626
let mut debug_frame = DebugFrame :: new ( debug_frame, LittleEndian ) ;
606
627
// 32-bit ARM -- this defaults to the host's address size which is likely going to be 8
@@ -613,24 +634,71 @@ fn backtrace(
613
634
let bases = & BaseAddresses :: default ( ) ;
614
635
let ctx = & mut UninitializedUnwindContext :: new ( ) ;
615
636
637
+ let addr2line = addr2line:: Context :: new ( elf) ?;
616
638
let mut top_exception = None ;
617
- let mut frame = 0 ;
639
+ let mut frame_index = 0 ;
618
640
let mut registers = Registers :: new ( lr, sp, core) ;
641
+ let symtab = elf. symbol_map ( ) ;
619
642
println ! ( "stack backtrace:" ) ;
620
643
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) ;
629
686
}
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
+ }
634
702
635
703
// on hard fault exception entry we hit the breakpoint before the subroutine prelude (`push
636
704
// lr`) is executed so special handling is required
@@ -708,8 +776,6 @@ fn backtrace(
708
776
}
709
777
pc = lr & !THUMB_BIT ;
710
778
}
711
-
712
- frame += 1 ;
713
779
}
714
780
715
781
Ok ( top_exception)
@@ -853,15 +919,10 @@ impl Stacked {
853
919
num_words as u32 * 4
854
920
}
855
921
}
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 ) > ;
859
922
860
- fn range_names_from (
923
+ fn rtt_and_heap_info_from (
861
924
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 > {
865
926
let mut rtt = None ;
866
927
let mut uses_heap = false ;
867
928
@@ -879,29 +940,9 @@ fn range_names_from(
879
940
}
880
941
_ => { }
881
942
}
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
- }
900
943
}
901
944
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) )
905
946
}
906
947
907
948
const LR : CoreRegisterAddress = CoreRegisterAddress ( 14 ) ;
0 commit comments