Skip to content

Commit

Permalink
Be smarter when locating debuginfo on macOS
Browse files Browse the repository at this point in the history
  • Loading branch information
bjorn3 committed Apr 14, 2019
1 parent 08e3958 commit 5be873a
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 25 deletions.
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ edition = "2018"
license = "MIT OR Apache-2.0"
repository = "https://github.com/bjorn3/pretty_backtrace"
documentation = "https://docs.rs/pretty_backtrace"
readme = "./README.md"
description = """
Pretty colored backtraces for Rust.
"""

[dependencies]
findshlibs = "0.4.0"
moria = { git = "https://github.com/gimli-rs/moria", commit = "83e27832cd70f33ce835726d6b91f064ae0cb50f" }
object = "0.11.0"
gimli = "0.16.1"
addr2line = "0.8.0"

backtrace = "0.3.13"
Expand All @@ -21,5 +23,5 @@ regex = "1.1.0"
lazy_static = "1.2.0"
syntect = "3.0.2"
rental = "0.5.2"
findshlibs = "0.4.0"
libc = "0.2.51"
failure = "0.1.5"
128 changes: 105 additions & 23 deletions src/locate_debuginfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,123 @@ use std::fs;
use std::path::{Path, PathBuf};

use addr2line::Context;
use object::Object;

macro_rules! read_and_parse {
(let $var:ident = $file_name:expr) => {
let file = fs::read($file_name).unwrap();
let $var = object::File::parse(&file).expect("Couldn't parse binary");
};
}

pub fn get_context() -> Context {
let bin_file_name = std::env::current_exe().expect("current bin");
get_context_for_file(&bin_file_name)
}

pub fn get_context_for_file(file_name: &Path) -> Context {
let debug_file = if cfg!(target_os="macos") {
// Locate .dSYM dwarf debuginfo
let dsym_dir = fs::read_dir(file_name.parent().expect("parent"))
.unwrap()
.map(|p| p.unwrap().path())
.filter(|p| p.extension() == Some(std::ffi::OsStr::new("dSYM")))
.next()
.unwrap();
load_dsym(dsym_dir)
} else {
fs::read(file_name).unwrap()
};
read_and_parse!(let obj = file_name);
if obj.has_debug_symbols() {
return addr2line::Context::new(&obj).expect("create context");
}

if let Some(context) = macos_fastpath(file_name, &obj) {
return context;
}

let debug_file_path = spin_loop_locate_debug_symbols(file_name, &obj).unwrap();

let debug_file = object::File::parse(&debug_file).expect("parse file");
read_and_parse!(let debug_file = debug_file_path);
addr2line::Context::new(&debug_file).expect("create context")
}

fn load_dsym(dsym_dir: PathBuf) -> Vec<u8> {
let mut dir_iter = fs::read_dir(dsym_dir.join("Contents/Resources/DWARF"))
.unwrap();
/// On macOS it can take some time for spotlight to index the dSYM file. When built by cargo, we can
/// likely find the dSYM file in target/<profile>/deps or target/<profile>/examples. This function
/// will try to find it there.
///
/// # Arguments
///
/// * Path to the object file which needs its debuginfo.
/// * Parsed version of the object file.
fn macos_fastpath(path: &Path, obj: &object::File) -> Option<Context> {
// Step 1. Return if not on macOS, because debuginfo is stored in the object file itself on OSes other than macOS.
if cfg!(not(target_os = "macos")) {
return None;
}

// Step 2. Get the path to the target dir of the current build channel.
let mut target_channel_dir = path;
loop {
let parent = target_channel_dir.parent()?;
target_channel_dir = parent;

if target_channel_dir.parent().and_then(|parent| parent.file_name()) == Some(std::ffi::OsStr::new("target")) {
break; // target_dir = ???/target/<channel>
}
}

let debug_file_name = dir_iter
.next()
.unwrap()
.unwrap()
.path();
// Step 3. Check every entry in <target_channel_dir>/deps and <target_channel_dir>/examples
for dir in fs::read_dir(target_channel_dir.join("deps")).unwrap().chain(fs::read_dir(target_channel_dir.join("examples")).unwrap()) {
let dir = dir.unwrap().path();

assert!(dir_iter.next().is_none());
// Step 4. If not a dSYM dir, try next entry.
if dir.extension() != Some(std::ffi::OsStr::new("dSYM")) {
continue;
}

// Step 5. Get path to inner object file.
let mut dir_iter = fs::read_dir(dir.join("Contents/Resources/DWARF"))
.unwrap();

let debug_file_name = dir_iter
.next()
.unwrap()
.unwrap()
.path();

assert!(dir_iter.next().is_none());

// Step 6. Parse inner object file.
read_and_parse!(let dsym = debug_file_name);

// Step 7. Make sure the dSYM file matches the object file to find debuginfo for.
if obj.mach_uuid() == dsym.mach_uuid() {
return Some(addr2line::Context::new(&dsym).expect("create context"));
}
}

None
}

fs::read(debug_file_name).unwrap()
fn spin_loop_locate_debug_symbols(file_name: &Path, obj: &object::File) -> Option<PathBuf> {
let mut i = 0;
loop {
match moria::locate_debug_symbols(&obj, file_name) {
Ok(res) => {
if i != 0 {
eprintln!();
}
return Some(res);
}
Err(err) => {
if i == 0 {
if err.to_string() != "dSYM not found" {
eprintln!("{}", err);
if i != 0 {
eprintln!();
}
Err::<(), _>(err).unwrap();
}
eprint!("Searching for dSYM file");
} else if i == 60 {
eprintln!(" Couldn't find dSYM file.");
return None;
} else {
eprint!(".");
}
i += 1;
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
}
}

0 comments on commit 5be873a

Please sign in to comment.