Skip to content

Commit f4409bb

Browse files
committed
Implement DWARF loading for Gimli on OSX
This commit copies the logic in the libbacktrace submodule to load DWARF debug information on OSX, namely by probing `*.dSYM` dirs and looking for corresopnding executable files.
1 parent 4890e48 commit f4409bb

File tree

4 files changed

+111
-29
lines changed

4 files changed

+111
-29
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ cpp_demangle = { default-features = false, version = "0.2.3", optional = true }
3333
addr2line = { version = "0.9.0", optional = true }
3434
findshlibs = { version = "0.4.1", optional = true }
3535
memmap = { version = "0.7.0", optional = true }
36+
goblin = { version = "0.0.22", optional = true, default-features = false }
3637

3738
[target.'cfg(windows)'.dependencies]
3839
winapi = { version = "0.3.3", optional = true }
@@ -92,7 +93,7 @@ kernel32 = []
9293
libbacktrace = ["backtrace-sys"]
9394
dladdr = []
9495
coresymbolication = []
95-
gimli-symbolize = ["addr2line", "findshlibs", "memmap" ]
96+
gimli-symbolize = ["addr2line", "findshlibs", "memmap", "goblin"]
9697

9798
#=======================================
9899
# Methods of serialization

ci/azure-test-all.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ steps:
99
displayName: "Build backtrace"
1010
- bash: cargo test
1111
displayName: "Test backtrace"
12+
- bash: cargo test --features 'gimli-symbolize'
13+
displayName: "Test backtrace (gimli-symbolize)"
1214
- bash: cargo test --no-default-features
1315
displayName: "Test backtrace (-default)"
1416
- bash: cargo test --no-default-features --features 'std'
@@ -41,8 +43,6 @@ steps:
4143
displayName: "Test backtrace (-default + serialize-rustc + serialize-serde + std)"
4244
- bash: cargo test --no-default-features --features 'cpp_demangle std'
4345
displayName: "Test backtrace (-default + cpp_demangle + std)"
44-
- bash: cargo test --no-default-features --features 'gimli-symbolize std'
45-
displayName: "Test backtrace (-default + gimli-symbolize + std)"
4646
- bash: cargo test --no-default-features --features 'dbghelp std'
4747
displayName: "Test backtrace (-default + dbghelp + std)"
4848
- bash: cargo test --no-default-features --features 'dbghelp std verify-winapi'

src/symbolize/gimli.rs

Lines changed: 99 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,52 +9,128 @@ use crate::types::BytesOrWideString;
99
use crate::SymbolName;
1010
use addr2line;
1111
use addr2line::object::{self, Object};
12+
use core::cell::RefCell;
13+
use core::mem;
14+
use core::u32;
1215
use findshlibs::{self, Segment, SharedLibrary};
1316
use libc::c_void;
1417
use memmap::Mmap;
15-
use std::cell::RefCell;
1618
use std::env;
1719
use std::fs::File;
18-
use std::mem;
1920
use std::path::{Path, PathBuf};
2021
use std::prelude::v1::*;
21-
use std::u32;
2222

2323
const MAPPINGS_CACHE_SIZE: usize = 4;
2424

25-
type Dwarf = addr2line::Context;
2625
type Symbols<'map> = object::SymbolMap<'map>;
2726

2827
struct Mapping {
29-
dwarf: Dwarf,
28+
dwarf: addr2line::Context,
3029
// 'static lifetime is a lie to hack around lack of support for self-referential structs.
3130
symbols: Symbols<'static>,
3231
_map: Mmap,
3332
}
3433

34+
macro_rules! mk {
35+
(Mapping { $map:expr, $object:expr }) => {{
36+
Mapping {
37+
dwarf: addr2line::Context::new(&$object).ok()?,
38+
// Convert to 'static lifetimes since the symbols should
39+
// only borrow `map` and we're preserving `map` below.
40+
//
41+
// TODO: how do we know that `symbol_map` *only* borrows `map`?
42+
symbols: unsafe { mem::transmute::<Symbols, Symbols<'static>>($object.symbol_map()) },
43+
_map: $map,
44+
}
45+
}};
46+
}
47+
48+
fn mmap(path: &Path) -> Option<Mmap> {
49+
let file = File::open(path).ok()?;
50+
// TODO: not completely safe, see https://github.com/danburkert/memmap-rs/issues/25
51+
unsafe { Mmap::map(&file).ok() }
52+
53+
}
54+
3555
impl Mapping {
36-
fn new(path: &PathBuf) -> Option<Mapping> {
37-
let file = File::open(path).ok()?;
38-
// TODO: not completely safe, see https://github.com/danburkert/memmap-rs/issues/25
39-
let map = unsafe { Mmap::map(&file).ok()? };
40-
let (dwarf, symbols) = {
41-
let object = object::ElfFile::parse(&*map).ok()?;
42-
let dwarf = addr2line::Context::new(&object).ok()?;
43-
let symbols = object.symbol_map();
44-
// Convert to 'static lifetimes.
45-
(dwarf, unsafe { mem::transmute(symbols) })
46-
};
47-
Some(Mapping {
48-
dwarf,
49-
symbols,
50-
_map: map,
51-
})
56+
fn new(path: &Path) -> Option<Mapping> {
57+
if cfg!(target_os = "macos") {
58+
Mapping::new_find_dsym(path)
59+
} else {
60+
let map = mmap(path)?;
61+
let object = object::ElfFile::parse(&map).ok()?;
62+
Some(mk!(Mapping { map, object }))
63+
}
64+
}
65+
66+
fn new_find_dsym(path: &Path) -> Option<Mapping> {
67+
// First up we need to load the unique UUID which is stored in the macho
68+
// header of the file we're reading, specified at `path`.
69+
let map = mmap(path)?;
70+
let object = object::MachOFile::parse(&map).ok()?;
71+
let uuid = get_uuid(&object)?;
72+
73+
// Next we need to look for a `*.dSYM` file. For now we just probe the
74+
// containing directory and look around for something that matches
75+
// `*.dSYM`. Once it's found we root through the dwarf resources that it
76+
// contains and try to find a macho file which has a matching UUID as
77+
// the one of our own file. If we find a match that's the dwarf file we
78+
// want to return.
79+
let parent = path.parent()?;
80+
for entry in parent.read_dir().ok()? {
81+
let entry = entry.ok()?;
82+
let filename = match entry.file_name().into_string() {
83+
Ok(name) => name,
84+
Err(_) => continue,
85+
};
86+
if !filename.ends_with(".dSYM") {
87+
continue;
88+
}
89+
let candidates = entry.path().join("Contents/Resources/DWARF");
90+
if let Some(mapping) = load_dsym(&candidates, &uuid) {
91+
return Some(mapping);
92+
}
93+
}
94+
95+
// Looks like nothing matched our UUID, so let's at least return our own
96+
// file. This should have the symbol table for at least some
97+
// symbolication purposes.
98+
return Some(mk!(Mapping { map, object }));
99+
100+
fn load_dsym(dir: &Path, uuid: &[u8]) -> Option<Mapping> {
101+
for entry in dir.read_dir().ok()? {
102+
let entry = entry.ok()?;
103+
let map = mmap(&entry.path())?;
104+
let object = object::MachOFile::parse(&map).ok()?;
105+
let entry_uuid = get_uuid(&object)?;
106+
if &entry_uuid[..] != uuid {
107+
continue;
108+
}
109+
return Some(mk!(Mapping { map, object }));
110+
}
111+
112+
None
113+
}
114+
115+
fn get_uuid(object: &object::MachOFile) -> Option<[u8; 16]> {
116+
use goblin::mach::load_command::CommandVariant;
117+
118+
object
119+
.macho()
120+
.load_commands
121+
.iter()
122+
.filter_map(|cmd| match cmd.command {
123+
CommandVariant::Uuid(u) => Some(u.uuid),
124+
_ => None,
125+
})
126+
.next()
127+
}
52128
}
53129

54130
// Ensure the 'static lifetimes don't leak.
55131
fn rent<F>(&self, mut f: F)
56132
where
57-
F: FnMut(&Dwarf, &Symbols),
133+
F: FnMut(&addr2line::Context, &Symbols),
58134
{
59135
f(&self.dwarf, &self.symbols)
60136
}
@@ -77,7 +153,7 @@ thread_local! {
77153

78154
fn with_mapping_for_path<F>(path: PathBuf, f: F)
79155
where
80-
F: FnMut(&Dwarf, &Symbols),
156+
F: FnMut(&addr2line::Context, &Symbols),
81157
{
82158
MAPPINGS_CACHE.with(|cache| {
83159
let mut cache = cache.borrow_mut();

src/symbolize/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -438,9 +438,14 @@ cfg_if::cfg_if! {
438438
mod dbghelp;
439439
use self::dbghelp::resolve as resolve_imp;
440440
use self::dbghelp::Symbol as SymbolImp;
441-
} else if #[cfg(all(feature = "std",
442-
feature = "gimli-symbolize",
443-
target_os = "linux"))] {
441+
} else if #[cfg(all(
442+
feature = "std",
443+
feature = "gimli-symbolize",
444+
any(
445+
target_os = "linux",
446+
target_os = "macos",
447+
),
448+
))] {
444449
mod gimli;
445450
use self::gimli::resolve as resolve_imp;
446451
use self::gimli::Symbol as SymbolImp;

0 commit comments

Comments
 (0)