11//! Unix (Linux / macOS / *BSD) symbolicator using `addr2line` and
22//! `object`.
33//!
4- //! `Context` borrows from the parsed `object::File`, which borrows
5- //! from the raw binary bytes. We resolve the lifetime puzzle by
6- //! reading the binary once and leaking the bytes via `Box::leak`,
7- //! upgrading them to `'static`. The leaked allocation lives for
8- //! the rest of the process; for a profiler this is fine, since the
9- //! same process that opened the binary uses it for the duration.
4+ //! `addr2line::Context<EndianRcSlice<_>>` (the type returned by
5+ //! the default `Context::new` constructor) contains `Rc<[u8]>` in
6+ //! its readers plus internal `UnsafeCell`-backed memoisation, so
7+ //! it is fundamentally `!Send + !Sync`. We sidestep the cross-
8+ //! thread sharing requirement by holding the symbolicator in a
9+ //! `thread_local!` cell. Each thread that calls
10+ //! `symbolicated_report()` builds its own context on first use;
11+ //! the per-address cache in `super::report` then deduplicates the
12+ //! per-address resolution work across threads.
13+ //!
14+ //! Memory cost: roughly the binary's on-disk size per thread that
15+ //! ever symbolicated. Bytes are leaked once per thread via
16+ //! `Box::leak` so the context's `'static` references remain valid
17+ //! for the rest of the process.
1018
11- use std:: sync :: OnceLock ;
19+ use std:: cell :: RefCell ;
1220
1321use addr2line:: Context ;
1422use object:: read:: File as ObjectFile ;
@@ -21,14 +29,22 @@ struct UnixSymbolicator {
2129 ctx : Ctx ,
2230}
2331
24- static SYMBOLICATOR : OnceLock < Option < UnixSymbolicator > > = OnceLock :: new ( ) ;
32+ thread_local ! {
33+ // Outer `Option` distinguishes "not yet tried" (None) from
34+ // "tried and failed" (Some(None)). After the first call, the
35+ // outer is always Some, so we never retry the failed-build
36+ // path on subsequent calls.
37+ static SYMBOLICATOR : RefCell <Option <Option <UnixSymbolicator >>> =
38+ const { RefCell :: new( None ) } ;
39+ }
2540
2641fn build ( ) -> Option < UnixSymbolicator > {
2742 let path = super :: self_binary:: self_exe ( ) ?;
2843 let bytes = std:: fs:: read ( path) . ok ( ) ?;
2944 // Leak the bytes so the parsed `File` and the `Context` can
3045 // safely hold references for the rest of the process. Memory
31- // cost: roughly the binary's on-disk size, one-time.
46+ // cost: roughly the binary's on-disk size, one-time per
47+ // thread that ever symbolicates.
3248 let leaked: & ' static [ u8 ] = Box :: leak ( bytes. into_boxed_slice ( ) ) ;
3349 let obj = ObjectFile :: parse ( leaked) . ok ( ) ?;
3450 let ctx = Context :: new ( & obj) . ok ( ) ?;
@@ -38,60 +54,63 @@ fn build() -> Option<UnixSymbolicator> {
3854/// Resolve one address. Returns one or more frames; multiple
3955/// frames mean inlined call-site expansion at this address.
4056pub ( crate ) fn resolve ( address : u64 ) -> Vec < SymbolicatedFrame > {
41- let sym = SYMBOLICATOR . get_or_init ( build) ;
42- let Some ( sym) = sym. as_ref ( ) else {
43- return vec ! [ SymbolicatedFrame {
44- address,
45- function: None ,
46- file: None ,
47- line: None ,
48- inlined: false ,
49- } ] ;
50- } ;
51-
52- let mut out = Vec :: new ( ) ;
53- let Ok ( mut frames) = sym. ctx . find_frames ( address) . skip_all_loads ( ) else {
54- return vec ! [ SymbolicatedFrame {
57+ let unresolved = || {
58+ vec ! [ SymbolicatedFrame {
5559 address,
5660 function: None ,
5761 file: None ,
5862 line: None ,
5963 inlined: false ,
60- } ] ;
64+ } ]
6165 } ;
6266
63- let mut idx = 0usize ;
64- while let Ok ( Some ( frame) ) = frames. next ( ) {
65- let function = frame
66- . function
67- . as_ref ( )
68- . and_then ( |f| f. raw_name ( ) . ok ( ) )
69- . map ( |name| rustc_demangle:: demangle ( & name) . to_string ( ) ) ;
67+ SYMBOLICATOR . with ( |cell| {
68+ let mut guard = match cell. try_borrow_mut ( ) {
69+ Ok ( g) => g,
70+ Err ( _) => return unresolved ( ) ,
71+ } ;
72+ let sym = guard. get_or_insert_with ( build) ;
73+ let Some ( sym) = sym. as_ref ( ) else {
74+ return unresolved ( ) ;
75+ } ;
7076
71- let ( file , line ) = match frame . location {
72- Some ( loc ) => ( loc . file . map ( std :: path :: PathBuf :: from ) , loc . line ) ,
73- None => ( None , None ) ,
77+ let mut out = Vec :: new ( ) ;
78+ let Ok ( mut frames ) = sym . ctx . find_frames ( address ) . skip_all_loads ( ) else {
79+ return unresolved ( ) ;
7480 } ;
7581
76- out. push ( SymbolicatedFrame {
77- address,
78- function,
79- file,
80- line,
81- inlined : idx > 0 ,
82- } ) ;
83- idx += 1 ;
84- }
82+ let mut idx = 0usize ;
83+ while let Ok ( Some ( frame) ) = frames. next ( ) {
84+ let function = frame
85+ . function
86+ . as_ref ( )
87+ . and_then ( |f| f. raw_name ( ) . ok ( ) )
88+ . map ( |name| rustc_demangle:: demangle ( & name) . to_string ( ) ) ;
8589
86- if out. is_empty ( ) {
87- out. push ( SymbolicatedFrame {
88- address,
89- function : None ,
90- file : None ,
91- line : None ,
92- inlined : false ,
93- } ) ;
94- }
90+ let ( file, line) = match frame. location {
91+ Some ( loc) => ( loc. file . map ( std:: path:: PathBuf :: from) , loc. line ) ,
92+ None => ( None , None ) ,
93+ } ;
94+
95+ out. push ( SymbolicatedFrame {
96+ address,
97+ function,
98+ file,
99+ line,
100+ inlined : idx > 0 ,
101+ } ) ;
102+ idx += 1 ;
103+ }
95104
96- out
105+ if out. is_empty ( ) {
106+ out. push ( SymbolicatedFrame {
107+ address,
108+ function : None ,
109+ file : None ,
110+ line : None ,
111+ inlined : false ,
112+ } ) ;
113+ }
114+ out
115+ } )
97116}
0 commit comments