Skip to content

Commit fd5451e

Browse files
committed
Support Mach-O backtraces without dsymutil
1 parent a6dd47b commit fd5451e

File tree

5 files changed

+108
-7
lines changed

5 files changed

+108
-7
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@ cpp_demangle = { default-features = false, version = "0.3.0", optional = true }
3838
addr2line = { version = "0.13.0", optional = true, default-features = false }
3939
miniz_oxide = { version = "0.4.0", optional = true, default-features = false }
4040
[dependencies.object]
41+
git = "https://github.com/philipc/object"
42+
branch = "object-map"
4143
version = "0.21"
4244
optional = true
4345
default-features = false
44-
features = ['read_core', 'elf', 'macho', 'pe', 'unaligned']
46+
features = ['read_core', 'elf', 'macho', 'pe', 'unaligned', 'archive']
4547

4648
[target.'cfg(windows)'.dependencies]
4749
winapi = { version = "0.3.3", optional = true }

src/symbolize/gimli.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ impl Cache {
547547
.next()
548548
}
549549

550-
fn mapping_for_lib<'a>(&'a mut self, lib: usize) -> Option<&'a Context<'a>> {
550+
fn mapping_for_lib<'a>(&'a mut self, lib: usize) -> Option<&'a mut Context<'a>> {
551551
let idx = self.mappings.iter().position(|(idx, _)| *idx == lib);
552552

553553
// Invariant: after this conditional completes without early returning
@@ -573,10 +573,10 @@ impl Cache {
573573
self.mappings.insert(0, (lib, mapping));
574574
}
575575

576-
let cx: &'a Context<'static> = &self.mappings[0].1.cx;
576+
let cx: &'a mut Context<'static> = &mut self.mappings[0].1.cx;
577577
// don't leak the `'static` lifetime, make sure it's scoped to just
578578
// ourselves
579-
Some(unsafe { mem::transmute::<&'a Context<'static>, &'a Context<'a>>(cx) })
579+
Some(unsafe { mem::transmute::<&'a mut Context<'static>, &'a mut Context<'a>>(cx) })
580580
}
581581
}
582582

@@ -613,7 +613,18 @@ pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol))
613613
});
614614
}
615615
}
616-
616+
if let Some((object_cx, object_addr)) = cx.object.search_object_map(addr as u64) {
617+
if let Ok(mut frames) = object_cx.dwarf.find_frames(object_addr) {
618+
while let Ok(Some(frame)) = frames.next() {
619+
any_frames = true;
620+
call(Symbol::Frame {
621+
addr: addr as *mut c_void,
622+
location: frame.location,
623+
name: frame.function.map(|f| f.name.slice()),
624+
});
625+
}
626+
}
627+
}
617628
if !any_frames {
618629
if let Some(name) = cx.object.search_symtab(addr as u64) {
619630
call(Symbol::Symtab {

src/symbolize/gimli/coff.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,8 @@ impl<'a> Object<'a> {
102102
};
103103
self.symbols[i].1.name(self.strings).ok()
104104
}
105+
106+
pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context, u64)> {
107+
None
108+
}
105109
}

src/symbolize/gimli/elf.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ impl<'a> Object<'a> {
162162
None
163163
}
164164
}
165+
166+
pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context, u64)> {
167+
None
168+
}
165169
}
166170

167171
fn decompress_zlib(input: &[u8], output: &mut [u8]) -> Option<()> {

src/symbolize/gimli/macho.rs

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,21 @@ pub struct Object<'a> {
142142
data: Bytes<'a>,
143143
dwarf: Option<&'a [MachSection]>,
144144
syms: Vec<(&'a [u8], u64)>,
145+
object_map: Option<object::ObjectMap<'a>>,
146+
object_mappings: Vec<Option<Option<Mapping>>>,
145147
}
146148

147149
impl<'a> Object<'a> {
148150
fn parse(mach: &'a Mach, endian: NativeEndian, data: Bytes<'a>) -> Option<Object<'a>> {
151+
let is_object = mach.filetype(endian) == object::macho::MH_OBJECT;
149152
let mut dwarf = None;
150153
let mut syms = Vec::new();
151154
let mut commands = mach.load_commands(endian, data).ok()?;
155+
let mut object_map = None;
156+
let mut object_mappings = Vec::new();
152157
while let Ok(Some(command)) = commands.next() {
153158
if let Some((segment, section_data)) = MachSegment::from_command(command).ok()? {
154-
if segment.name() == b"__DWARF" {
159+
if segment.name() == b"__DWARF" || (is_object && segment.name() == b"") {
155160
dwarf = segment.sections(endian, section_data).ok();
156161
}
157162
} else if let Some(symtab) = command.symtab().ok()? {
@@ -160,14 +165,19 @@ impl<'a> Object<'a> {
160165
.iter()
161166
.filter_map(|nlist: &MachNlist| {
162167
let name = nlist.name(endian, symbols.strings()).ok()?;
163-
if name.len() > 0 && !nlist.is_undefined() {
168+
if name.len() > 0 && nlist.is_definition() {
164169
Some((name, u64::from(nlist.n_value(endian))))
165170
} else {
166171
None
167172
}
168173
})
169174
.collect();
170175
syms.sort_unstable_by_key(|(_, addr)| *addr);
176+
if !is_object {
177+
let map = symbols.object_map(endian);
178+
object_mappings.resize_with(map.objects().len(), || None);
179+
object_map = Some(map);
180+
}
171181
}
172182
}
173183

@@ -176,6 +186,8 @@ impl<'a> Object<'a> {
176186
data,
177187
dwarf,
178188
syms,
189+
object_map,
190+
object_mappings,
179191
})
180192
}
181193

@@ -201,4 +213,72 @@ impl<'a> Object<'a> {
201213
let (sym, _addr) = self.syms.get(i)?;
202214
Some(sym)
203215
}
216+
217+
/// Try to load a context for an object file.
218+
///
219+
/// If dsymutil was not run, then the DWARF may be found in the source object files.
220+
pub(super) fn search_object_map<'b>(&'b mut self, addr: u64) -> Option<(&Context<'b>, u64)> {
221+
// `object_map` contains a map from addresses to symbols and object paths.
222+
// Look up the address and get a mapping for the object.
223+
let object_map = self.object_map.as_ref()?;
224+
let symbol = object_map.get(addr)?;
225+
let object_index = symbol.object_index();
226+
let mapping = self.object_mappings.get_mut(object_index)?;
227+
if mapping.is_none() {
228+
// No cached mapping, so create it.
229+
*mapping = Some(object_mapping(object_map.objects().get(object_index)?));
230+
}
231+
let cx: &'b Context<'static> = &mapping.as_ref()?.as_ref()?.cx;
232+
// Don't leak the `'static` lifetime, make sure it's scoped to just ourselves.
233+
let cx = unsafe { core::mem::transmute::<&'b Context<'static>, &'b Context<'b>>(cx) };
234+
235+
// We must translate the address in order to be able to look it up
236+
// in the DWARF in the object file.
237+
let object_symbol = cx.object.syms.iter().find(|sym| sym.0 == symbol.name())?;
238+
let object_addr = addr
239+
.wrapping_sub(symbol.address())
240+
.wrapping_add(object_symbol.1);
241+
Some((cx, object_addr))
242+
}
243+
}
244+
245+
fn object_mapping(path: &[u8]) -> Option<Mapping> {
246+
use super::mystd::ffi::OsStr;
247+
use super::mystd::os::unix::prelude::*;
248+
249+
// `N_OSO` symbol names can be either `/path/to/object.o` or `/path/to/archive.a(object.o)`.
250+
if let Some((archive_path, member_name)) = split_archive_path(path) {
251+
let map = super::mmap(Path::new(OsStr::from_bytes(archive_path)))?;
252+
let archive = object::read::archive::ArchiveFile::parse(&map).ok()?;
253+
for member in archive.members() {
254+
let member = member.ok()?;
255+
if member.name() == member_name {
256+
let (macho, data) = find_header(Bytes(member.data()))?;
257+
let endian = macho.endian().ok()?;
258+
let object = Object::parse(macho, endian, data)?;
259+
let stash = Stash::new();
260+
let inner = super::cx(&stash, object)?;
261+
return Some(mk!(Mapping { map, inner, stash }));
262+
}
263+
}
264+
None
265+
} else {
266+
let map = super::mmap(Path::new(OsStr::from_bytes(path)))?;
267+
let (macho, data) = find_header(Bytes(&map))?;
268+
let endian = macho.endian().ok()?;
269+
let object = Object::parse(macho, endian, data)?;
270+
let stash = Stash::new();
271+
let inner = super::cx(&stash, object)?;
272+
Some(mk!(Mapping { map, inner, stash }))
273+
}
274+
}
275+
276+
fn split_archive_path(path: &[u8]) -> Option<(&[u8], &[u8])> {
277+
let (last, path) = path.split_last()?;
278+
if *last != b')' {
279+
return None;
280+
}
281+
let index = path.iter().position(|&x| x == b'(')?;
282+
let (archive, rest) = path.split_at(index);
283+
Some((archive, &rest[1..]))
204284
}

0 commit comments

Comments
 (0)