1 //! Support for symbolication using the `gimli` crate on crates.io
3 //! This is the default symbolication implementation for Rust.
5 use self::gimli
::read
::EndianSlice
;
6 use self::gimli
::NativeEndian
as Endian
;
8 use self::stash
::Stash
;
9 use super::BytesOrWideString
;
10 use super::ResolveWhat
;
11 use super::SymbolName
;
13 use core
::convert
::TryInto
;
17 use mystd
::ffi
::OsString
;
19 use mystd
::path
::Path
;
20 use mystd
::prelude
::v1
::*;
22 #[cfg(backtrace_in_libstd)]
26 #[cfg(not(backtrace_in_libstd))]
27 extern crate std
as mystd
;
31 #[path = "gimli/mmap_windows.rs"]
34 target_os
= "android",
35 target_os
= "freebsd",
36 target_os
= "fuchsia",
41 target_os
= "openbsd",
42 target_os
= "solaris",
43 target_os
= "illumos",
45 #[path = "gimli/mmap_unix.rs"]
48 #[path = "gimli/mmap_fake.rs"]
55 const MAPPINGS_CACHE_SIZE
: usize = 4;
58 // 'static lifetime is a lie to hack around lack of support for self-referential structs.
71 /// Creates a `Mapping` by ensuring that the `data` specified is used to
72 /// create a `Context` and it can only borrow from that or the `Stash` of
73 /// decompressed sections or auxiliary data.
74 fn mk
<F
>(data
: Mmap
, mk
: F
) -> Option
<Mapping
>
76 F
: for<'a
> FnOnce(&'a
[u8], &'a Stash
) -> Option
<Context
<'a
>>,
78 Mapping
::mk_or_other(data
, move |data
, stash
| {
79 let cx
= mk(data
, stash
)?
;
84 /// Creates a `Mapping` from `data`, or if the closure decides to, returns a
85 /// different mapping.
86 fn mk_or_other
<F
>(data
: Mmap
, mk
: F
) -> Option
<Mapping
>
88 F
: for<'a
> FnOnce(&'a
[u8], &'a Stash
) -> Option
<Either
<Mapping
, Context
<'a
>>>,
90 let stash
= Stash
::new();
91 let cx
= match mk(&data
, &stash
)?
{
92 Either
::A(mapping
) => return Some(mapping
),
96 // Convert to 'static lifetimes since the symbols should
97 // only borrow `map` and `stash` and we're preserving them below.
98 cx
: unsafe { core::mem::transmute::<Context<'_>, Context<'static>>(cx) }
,
106 dwarf
: addr2line
::Context
<EndianSlice
<'a
, Endian
>>,
110 impl<'data
> Context
<'data
> {
113 object
: Object
<'data
>,
114 sup
: Option
<Object
<'data
>>,
115 ) -> Option
<Context
<'data
>> {
116 let mut sections
= gimli
::Dwarf
::load(|id
| -> Result
<_
, ()> {
117 let data
= object
.section(stash
, id
.name()).unwrap_or(&[]);
118 Ok(EndianSlice
::new(data
, Endian
))
122 if let Some(sup
) = sup
{
124 .load_sup(|id
| -> Result
<_
, ()> {
125 let data
= sup
.section(stash
, id
.name()).unwrap_or(&[]);
126 Ok(EndianSlice
::new(data
, Endian
))
130 let dwarf
= addr2line
::Context
::from_dwarf(sections
).ok()?
;
132 Some(Context { dwarf, object }
)
136 fn mmap(path
: &Path
) -> Option
<Mmap
> {
137 let file
= File
::open(path
).ok()?
;
138 let len
= file
.metadata().ok()?
.len().try_into().ok()?
;
139 unsafe { Mmap::map(&file, len) }
145 use self::coff
::Object
;
150 target_os
= "watchos",
153 use self::macho
::Object
;
156 use self::elf
::Object
;
163 use libs_windows
::native_libraries
;
168 target_os
= "watchos",
171 use libs_macos
::native_libraries
;
172 } else if #[cfg(target_os = "illumos")] {
174 use libs_illumos
::native_libraries
;
178 target_os
= "fuchsia",
179 target_os
= "freebsd",
180 target_os
= "openbsd",
181 all(target_os
= "android", feature
= "dl_iterate_phdr"),
183 not(target_env
= "uclibc"),
185 mod libs_dl_iterate_phdr
;
186 use libs_dl_iterate_phdr
::native_libraries
;
187 } else if #[cfg(target_env = "libnx")] {
189 use libs_libnx
::native_libraries
;
190 } else if #[cfg(target_os = "haiku")] {
192 use libs_haiku
::native_libraries
;
194 // Everything else should doesn't know how to load native libraries.
195 fn native_libraries() -> Vec
<Library
> {
203 /// All known shared libraries that have been loaded.
204 libraries
: Vec
<Library
>,
206 /// Mappings cache where we retain parsed dwarf information.
208 /// This list has a fixed capacity for its entire lifetime which never
209 /// increases. The `usize` element of each pair is an index into `libraries`
210 /// above where `usize::max_value()` represents the current executable. The
211 /// `Mapping` is corresponding parsed dwarf information.
213 /// Note that this is basically an LRU cache and we'll be shifting things
214 /// around in here as we symbolize addresses.
215 mappings
: Vec
<(usize, Mapping
)>,
220 /// Segments of this library loaded into memory, and where they're loaded.
221 segments
: Vec
<LibrarySegment
>,
222 /// The "bias" of this library, typically where it's loaded into memory.
223 /// This value is added to each segment's stated address to get the actual
224 /// virtual memory address that the segment is loaded into. Additionally
225 /// this bias is subtracted from real virtual memory addresses to index into
226 /// debuginfo and the symbol table.
230 struct LibrarySegment
{
231 /// The stated address of this segment in the object file. This is not
232 /// actually where the segment is loaded, but rather this address plus the
233 /// containing library's `bias` is where to find it.
234 stated_virtual_memory_address
: usize,
235 /// The size of this segment in memory.
239 // unsafe because this is required to be externally synchronized
240 pub unsafe fn clear_symbol_cache() {
241 Cache
::with_global(|cache
| cache
.mappings
.clear());
247 mappings
: Vec
::with_capacity(MAPPINGS_CACHE_SIZE
),
248 libraries
: native_libraries(),
252 // unsafe because this is required to be externally synchronized
253 unsafe fn with_global(f
: impl FnOnce(&mut Self)) {
254 // A very small, very simple LRU cache for debug info mappings.
256 // The hit rate should be very high, since the typical stack doesn't cross
257 // between many shared libraries.
259 // The `addr2line::Context` structures are pretty expensive to create. Its
260 // cost is expected to be amortized by subsequent `locate` queries, which
261 // leverage the structures built when constructing `addr2line::Context`s to
262 // get nice speedups. If we didn't have this cache, that amortization would
263 // never happen, and symbolicating backtraces would be ssssllllooooowwww.
264 static mut MAPPINGS_CACHE
: Option
<Cache
> = None
;
266 f(MAPPINGS_CACHE
.get_or_insert_with(|| Cache
::new()))
269 fn avma_to_svma(&self, addr
: *const u8) -> Option
<(usize, *const u8)> {
273 .filter_map(|(i
, lib
)| {
274 // First up, test if this `lib` has any segment containing the
275 // `addr` (handling relocation). If this check passes then we
276 // can continue below and actually translate the address.
278 // Note that we're using `wrapping_add` here to avoid overflow
279 // checks. It's been seen in the wild that the SVMA + bias
280 // computation overflows. It seems a bit odd that would happen
281 // but there's not a huge amount we can do about it other than
282 // probably just ignore those segments since they're likely
283 // pointing off into space. This originally came up in
284 // rust-lang/backtrace-rs#329.
285 if !lib
.segments
.iter().any(|s
| {
286 let svma
= s
.stated_virtual_memory_address
;
287 let start
= svma
.wrapping_add(lib
.bias
);
288 let end
= start
.wrapping_add(s
.len
);
289 let address
= addr
as usize;
290 start
<= address
&& address
< end
295 // Now that we know `lib` contains `addr`, we can offset with
296 // the bias to find the stated virtual memory address.
297 let svma
= (addr
as usize).wrapping_sub(lib
.bias
);
298 Some((i
, svma
as *const u8))
303 fn mapping_for_lib
<'a
>(&'a
mut self, lib
: usize) -> Option
<&'a
mut Context
<'a
>> {
304 let idx
= self.mappings
.iter().position(|(idx
, _
)| *idx
== lib
);
306 // Invariant: after this conditional completes without early returning
307 // from an error, the cache entry for this path is at index 0.
309 if let Some(idx
) = idx
{
310 // When the mapping is already in the cache, move it to the front.
312 let entry
= self.mappings
.remove(idx
);
313 self.mappings
.insert(0, entry
);
316 // When the mapping is not in the cache, create a new mapping,
317 // insert it into the front of the cache, and evict the oldest cache
318 // entry if necessary.
319 let name
= &self.libraries
[lib
].name
;
320 let mapping
= Mapping
::new(name
.as_ref())?
;
322 if self.mappings
.len() == MAPPINGS_CACHE_SIZE
{
326 self.mappings
.insert(0, (lib
, mapping
));
329 let cx
: &'a
mut Context
<'
static> = &mut self.mappings
[0].1.cx
;
330 // don't leak the `'static` lifetime, make sure it's scoped to just
332 Some(unsafe { mem::transmute::<&'a mut Context<'static>, &'a mut Context<'a>>(cx) }
)
336 pub unsafe fn resolve(what
: ResolveWhat
<'_
>, cb
: &mut dyn FnMut(&super::Symbol
)) {
337 let addr
= what
.address_or_ip();
338 let mut call
= |sym
: Symbol
<'_
>| {
339 // Extend the lifetime of `sym` to `'static` since we are unfortunately
340 // required to here, but it's only ever going out as a reference so no
341 // reference to it should be persisted beyond this frame anyway.
342 let sym
= mem
::transmute
::<Symbol
<'_
>, Symbol
<'
static>>(sym
);
343 (cb
)(&super::Symbol { inner: sym }
);
346 Cache
::with_global(|cache
| {
347 let (lib
, addr
) = match cache
.avma_to_svma(addr
as *const u8) {
352 // Finally, get a cached mapping or create a new mapping for this file, and
353 // evaluate the DWARF info to find the file/line/name for this address.
354 let cx
= match cache
.mapping_for_lib(lib
) {
358 let mut any_frames
= false;
359 if let Ok(mut frames
) = cx
.dwarf
.find_frames(addr
as u64) {
360 while let Ok(Some(frame
)) = frames
.next() {
362 let name
= match frame
.function
{
363 Some(f
) => Some(f
.name
.slice()),
364 None
=> cx
.object
.search_symtab(addr
as u64),
367 addr
: addr
as *mut c_void
,
368 location
: frame
.location
,
374 if let Some((object_cx
, object_addr
)) = cx
.object
.search_object_map(addr
as u64) {
375 if let Ok(mut frames
) = object_cx
.dwarf
.find_frames(object_addr
) {
376 while let Ok(Some(frame
)) = frames
.next() {
379 addr
: addr
as *mut c_void
,
380 location
: frame
.location
,
381 name
: frame
.function
.map(|f
| f
.name
.slice()),
388 if let Some(name
) = cx
.object
.search_symtab(addr
as u64) {
389 call(Symbol
::Symtab
{
390 addr
: addr
as *mut c_void
,
398 pub enum Symbol
<'a
> {
399 /// We were able to locate frame information for this symbol, and
400 /// `addr2line`'s frame internally has all the nitty gritty details.
403 location
: Option
<addr2line
::Location
<'a
>>,
404 name
: Option
<&'a
[u8]>,
406 /// Couldn't find debug information, but we found it in the symbol table of
407 /// the elf executable.
408 Symtab { addr: *mut c_void, name: &'a [u8] }
,
412 pub fn name(&self) -> Option
<SymbolName
<'_
>> {
414 Symbol
::Frame { name, .. }
=> {
415 let name
= name
.as_ref()?
;
416 Some(SymbolName
::new(name
))
418 Symbol
::Symtab { name, .. }
=> Some(SymbolName
::new(name
)),
422 pub fn addr(&self) -> Option
<*mut c_void
> {
424 Symbol
::Frame { addr, .. }
=> Some(*addr
),
425 Symbol
::Symtab { .. }
=> None
,
429 pub fn filename_raw(&self) -> Option
<BytesOrWideString
<'_
>> {
431 Symbol
::Frame { location, .. }
=> {
432 let file
= location
.as_ref()?
.file?
;
433 Some(BytesOrWideString
::Bytes(file
.as_bytes()))
435 Symbol
::Symtab { .. }
=> None
,
439 pub fn filename(&self) -> Option
<&Path
> {
441 Symbol
::Frame { location, .. }
=> {
442 let file
= location
.as_ref()?
.file?
;
443 Some(Path
::new(file
))
445 Symbol
::Symtab { .. }
=> None
,
449 pub fn lineno(&self) -> Option
<u32> {
451 Symbol
::Frame { location, .. }
=> location
.as_ref()?
.line
,
452 Symbol
::Symtab { .. }
=> None
,
456 pub fn colno(&self) -> Option
<u32> {
458 Symbol
::Frame { location, .. }
=> location
.as_ref()?
.column
,
459 Symbol
::Symtab { .. }
=> None
,