Skip to content

Commit 6f25d6f

Browse files
authored
Merge pull request #208 from rust-lang/gimli-osx
Implement DWARF loading for Gimli on OSX
2 parents 4890e48 + f4409bb commit 6f25d6f

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)