Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
language: rust

rust:
- stable
- beta
- nightly

sudo: false

dist: trusty

addons:
sources:
# Provides clang-3.9
- llvm-toolchain-trusty-3.9
apt:
packages:
# Required for `bindgen`, which is required by `findshlibs`, which is
# required by the `gimli` feature.
- clang-3.9

before_script:
- pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH
- export LIBCLANG_PATH=/usr/lib/llvm-3.9/lib

script:
- cargo test
- cargo test --no-default-features
Expand All @@ -19,15 +36,19 @@ script:
- cargo test --no-default-features --features 'serialize-rustc'
- cargo test --no-default-features --features 'serialize-rustc serialize-serde'
- cargo test --no-default-features --features 'cpp_demangle'
- cargo test --no-default-features --features 'gimli-symbolize'
- cd ./cpp_smoke_test && cargo test && cd ..
- cargo clean && cargo build
- rustdoc --test README.md -L target/debug/deps -L target/debug
- cargo doc --no-deps

notifications:
email:
on_success: never

after_success:
- travis-cargo --only nightly doc-upload

env:
global:
# serde-codegen has historically needed a large stack to expand
Expand Down
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ rustc-serialize = { version = "0.3", optional = true }
# Optionally demangle C++ frames' symbols in backtraces.
cpp_demangle = { default-features = false, version = "0.2.3", optional = true }

addr2line = { version = "0.5.0", optional = true }
findshlibs = { version = "0.3.3", optional = true }

[target.'cfg(windows)'.dependencies]
dbghelp-sys = { version = "0.2", optional = true }
kernel32-sys = { version = "0.2", optional = true }
Expand Down Expand Up @@ -75,9 +78,15 @@ default = ["libunwind", "libbacktrace", "coresymbolication", "dladdr", "dbghelp"
# enough on OSX.
# - coresymbolication: this feature uses the undocumented core symbolication
# framework on OS X to symbolize.
# - gimli-symbolize: use the `gimli-rs/addr2line` crate to symbolicate
# addresses into file, line, and name using DWARF debug information. At
# the moment, this is only possible when targetting Linux, since macOS
# splits DWARF out into a separate object file. Enabling this feature
# means one less C dependency.
libbacktrace = ["backtrace-sys"]
dladdr = []
coresymbolication = []
gimli-symbolize = ["addr2line", "findshlibs"]

#=======================================
# Methods of serialization
Expand Down
9 changes: 9 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ extern crate rustc_demangle;
#[cfg(feature = "cpp_demangle")]
extern crate cpp_demangle;

#[cfg(all(feature = "gimli-symbolize",
unix,
target_os = "linux"))]
extern crate addr2line;
#[cfg(all(feature = "gimli-symbolize",
unix,
target_os = "linux"))]
extern crate findshlibs;

#[allow(dead_code)] // not used everywhere
#[cfg(unix)]
#[macro_use]
Expand Down
170 changes: 170 additions & 0 deletions src/symbolize/gimli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
use addr2line;
use findshlibs::{self, Segment, SharedLibrary};
use std::cell::RefCell;
use std::env;
use std::os::raw::c_void;
use std::path::{Path, PathBuf};
use std::u32;

use SymbolName;

const MAPPINGS_CACHE_SIZE: usize = 4;

thread_local! {
// A very small, very simple LRU cache for debug info mappings.
//
// The hit rate should be very high, since the typical stack doesn't cross
// between many shared libraries.
//
// The `addr2line::Mapping` structures are pretty expensive to create. Its
// cost is expected to be amortized by subsequent `locate` queries, which
// leverage the structures built when constructing `addr2line::Mapping`s to
// get nice speedups. If we didn't have this cache, that amortization would
// never happen, and symbolicating backtraces would be ssssllllooooowwww.
static MAPPINGS_CACHE: RefCell<Vec<(PathBuf, addr2line::Mapping)>>
= RefCell::new(Vec::with_capacity(MAPPINGS_CACHE_SIZE));
}

fn with_mapping_for_path<F>(path: PathBuf, mut f: F)
where
F: FnMut(&mut addr2line::Mapping)
{
MAPPINGS_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();

let idx = cache.iter().position(|&(ref p, _)| p == &path);

// Invariant: after this conditional completes without early returning
// from an error, the cache entry for this path is at index 0.

if let Some(idx) = idx {
// When the mapping is already in the cache, move it to the front.
if idx != 0 {
let entry = cache.remove(idx);
cache.insert(0, entry);
}
} else {
// When the mapping is not in the cache, create a new mapping,
// insert it into the front of the cache, and evict the oldest cache
// entry if necessary.
let opts = addr2line::Options::default()
.with_functions();

let mapping = match opts.build(&path) {
Err(_) => return,
Ok(m) => m,
};

if cache.len() == MAPPINGS_CACHE_SIZE {
cache.pop();
}

cache.insert(0, (path, mapping));
}

f(&mut cache[0].1);
});
}

pub fn resolve(addr: *mut c_void, cb: &mut FnMut(&super::Symbol)) {
// First, find the file containing the segment that the given AVMA (after
// relocation) address falls within. Use the containing segment to compute
// the SVMA (before relocation) address.
//
// Note that the OS APIs that `SharedLibrary::each` is implemented with hold
// a lock for the duration of the `each` call, so we want to keep this
// section as short as possible to avoid contention with other threads
// capturing backtraces.
let addr = findshlibs::Avma(addr as *mut u8 as *const u8);
let mut so_info = None;
findshlibs::TargetSharedLibrary::each(|so| {
use findshlibs::IterationControl::*;

for segment in so.segments() {
if segment.contains_avma(so, addr) {
let addr = so.avma_to_svma(addr);
let path = so.name().to_string_lossy();
so_info = Some((addr, path.to_string()));
return Break;
}
}

Continue
});
let (addr, path) = match so_info {
None => return,
Some((a, p)) => (a, p),
};

// Second, fixup the path. Empty path means that this address falls within
// the main executable, not a shared library.
let path = if path.is_empty() {
match env::current_exe() {
Err(_) => return,
Ok(p) => p,
}
} else {
PathBuf::from(path)
};

// Finally, get a cached mapping or create a new mapping for this file, and
// evaluate the DWARF info to find the file/line/name for this address.
with_mapping_for_path(path, |mapping| {
let (file, line, func) = match mapping.locate(addr.0 as u64) {
Ok(None) | Err(_) => return,
Ok(Some((file, line, func))) => (file, line, func),
};

let sym = super::Symbol {
inner: Symbol::new(addr.0 as usize,
file,
line,
func.map(|f| f.to_string()))
};

cb(&sym);
});
}

pub struct Symbol {
addr: usize,
file: PathBuf,
line: Option<u64>,
name: Option<String>,
}

impl Symbol {
fn new(addr: usize,
file: PathBuf,
line: Option<u64>,
name: Option<String>)
-> Symbol {
Symbol {
addr,
file,
line,
name,
}
}

pub fn name(&self) -> Option<SymbolName> {
self.name.as_ref().map(|s| SymbolName::new(s.as_bytes()))
}

pub fn addr(&self) -> Option<*mut c_void> {
Some(self.addr as *mut c_void)
}

pub fn filename(&self) -> Option<&Path> {
Some(self.file.as_ref())
}

pub fn lineno(&self) -> Option<u32> {
self.line
.and_then(|l| if l > (u32::MAX as u64) {
None
} else {
Some(l as u32)
})
}
}
6 changes: 6 additions & 0 deletions src/symbolize/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,12 @@ cfg_if! {
mod dbghelp;
use self::dbghelp::resolve as resolve_imp;
use self::dbghelp::Symbol as SymbolImp;
} else if #[cfg(all(feature = "gimli-symbolize",
unix,
target_os = "linux"))] {
mod gimli;
use self::gimli::resolve as resolve_imp;
use self::gimli::Symbol as SymbolImp;
} else if #[cfg(all(feature = "libbacktrace",
unix,
not(target_os = "emscripten"),
Expand Down
5 changes: 4 additions & 1 deletion tests/smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ static CORESYMBOLICATION: bool = cfg!(all(any(target_os = "macos", target_os = "
static DLADDR: bool = cfg!(all(unix, feature = "dladdr"));
static DBGHELP: bool = cfg!(all(windows, feature = "dbghelp"));
static MSVC: bool = cfg!(target_env = "msvc");
static GIMLI_SYMBOLIZE: bool = cfg!(all(feature = "gimli-symbolize",
unix,
target_os = "linux"));

#[test]
fn smoke_test_frames() {
Expand Down Expand Up @@ -71,7 +74,7 @@ fn smoke_test_frames() {
}

let mut resolved = 0;
let can_resolve = DLADDR || LIBBACKTRACE || CORESYMBOLICATION || DBGHELP;
let can_resolve = DLADDR || LIBBACKTRACE || CORESYMBOLICATION || DBGHELP || GIMLI_SYMBOLIZE;

let mut name = None;
let mut addr = None;
Expand Down