diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 89aae4a0..600725a4 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -10,6 +10,9 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@v2 + + - name: Install LLVM toolchain + run: sudo apt install llvm autotools-dev autoconf libunwind-dev -y - name: Install Rust toolchain uses: actions-rs/toolchain@v1.0.7 @@ -56,8 +59,32 @@ jobs: uses: actions-rs/cargo@v1.0.3 with: command: build - args: --all-features --target ${{ matrix.target }} + args: --features flamegraph,protobuf,cpp --target ${{ matrix.target }} + build_nongnu_libunwind: + name: Build with nongnu-libunwind + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install LLVM toolchain + run: sudo apt install llvm autotools-dev autoconf libunwind-dev -y + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1.0.7 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt, clippy + + - name: Run cargo build + uses: actions-rs/cargo@v1.0.3 + with: + command: build + args: --all-features + test: name: Test strategy: @@ -81,4 +108,4 @@ jobs: uses: actions-rs/cargo@v1.0.3 with: command: test - args: --all-features + args: --features flamegraph,protobuf,cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 19f9a8e5..a232ef3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Add `nongnu-libunwind` feature to dynamically link with the `nongnu-libunwind` [@yangkeao](https://github.com/YangKeao) ## [0.6.2] - 2021-12-24 ### Added diff --git a/Cargo.toml b/Cargo.toml index 432af74c..2a7bfc30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ default = ["cpp"] flamegraph = ["inferno"] protobuf = ["prost", "prost-derive", "prost-build"] cpp = ["symbolic-demangle/cpp"] +nongnu-libunwind = ["libunwind-rs"] [dependencies] backtrace = "0.3" @@ -32,6 +33,7 @@ inferno = { version = "0.10", default-features = false, features = ["nameattr"], prost = { version = "0.9", optional = true } prost-derive = { version = "0.9", optional = true } criterion = {version = "0.3", optional = true} +libunwind-rs = { version = "0.3", optional = true } [dependencies.symbolic-demangle] version = "8.0" diff --git a/src/backtrace/backtrace_rs.rs b/src/backtrace/backtrace_rs.rs new file mode 100644 index 00000000..cedaf325 --- /dev/null +++ b/src/backtrace/backtrace_rs.rs @@ -0,0 +1,18 @@ +impl super::Frame for backtrace::Frame { + type S = backtrace::Symbol; + + fn resolve_symbol(&self, cb: F) { + backtrace::resolve_frame(self, cb); + } + + fn symbol_address(&self) -> *mut libc::c_void { + self.symbol_address() + } +} + +pub fn trace bool>(cb: F) { + unsafe { backtrace::trace_unsynchronized(cb) } +} + +pub use backtrace::Frame; +pub use backtrace::Symbol; diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs new file mode 100644 index 00000000..88e59d23 --- /dev/null +++ b/src/backtrace/mod.rs @@ -0,0 +1,46 @@ +use libc::c_void; +use std::path::PathBuf; + +pub trait Symbol: Sized { + fn name(&self) -> Option>; + fn addr(&self) -> Option<*mut c_void>; + fn lineno(&self) -> Option; + fn filename(&self) -> Option; +} + +impl Symbol for backtrace::Symbol { + fn name(&self) -> Option> { + self.name().map(|name| name.as_bytes().to_vec()) + } + + fn addr(&self) -> Option<*mut libc::c_void> { + self.addr() + } + + fn lineno(&self) -> Option { + self.lineno() + } + + fn filename(&self) -> Option { + self.filename().map(|filename| filename.to_owned()) + } +} + +pub trait Frame: Sized + Clone { + type S: Symbol; + + fn resolve_symbol(&self, cb: F); + fn symbol_address(&self) -> *mut c_void; +} + +#[cfg(not(feature = "nongnu-libunwind"))] +mod backtrace_rs; + +#[cfg(not(feature = "nongnu-libunwind"))] +pub use backtrace_rs::{trace, Frame as FrameImpl, Symbol as SymbolImpl}; + +#[cfg(feature = "nongnu-libunwind")] +mod nongnu_unwind; + +#[cfg(feature = "nongnu-libunwind")] +pub use nongnu_unwind::{trace, Frame as FrameImpl, Symbol as SymbolImpl}; diff --git a/src/backtrace/nongnu_unwind.rs b/src/backtrace/nongnu_unwind.rs new file mode 100644 index 00000000..71f5db6e --- /dev/null +++ b/src/backtrace/nongnu_unwind.rs @@ -0,0 +1,49 @@ +use libc::c_void; + +use libunwind_rs::Cursor; + +// TODO: need a better Debug implementation +#[derive(Clone, Debug)] +pub struct Frame { + pub ip: usize, + pub sp: usize, + pub symbol_address: usize, +} + +impl super::Frame for Frame { + type S = backtrace::Symbol; + + fn resolve_symbol(&self, cb: F) { + backtrace::resolve(self.ip as *mut c_void, cb) + } + + fn symbol_address(&self) -> *mut libc::c_void { + self.symbol_address as *mut c_void + } +} + +pub fn trace bool>(mut cb: F) { + // TODO: come up with a better way to handle this error + let _ = Cursor::local(|mut cursor| -> Result<(), libunwind_rs::Error> { + loop { + let mut symbol_address = 0; + if let Ok(proc_info) = cursor.proc_info() { + symbol_address = proc_info.start(); + } + + if cb(&Frame { + ip: cursor.ip()?, + sp: cursor.sp()?, + symbol_address, + }) && cursor.step()? + { + continue; + } + + break; + } + Ok(()) + }); +} + +pub use backtrace::Symbol; diff --git a/src/frames.rs b/src/frames.rs index acac2d1d..5877ae03 100644 --- a/src/frames.rs +++ b/src/frames.rs @@ -6,15 +6,15 @@ use std::hash::{Hash, Hasher}; use std::os::raw::c_void; use std::path::PathBuf; -use backtrace::Frame; use smallvec::SmallVec; use symbolic_demangle::demangle; +use crate::backtrace::{Frame, FrameImpl}; use crate::{MAX_DEPTH, MAX_THREAD_NAME}; #[derive(Clone)] pub struct UnresolvedFrames { - pub frames: SmallVec<[Frame; MAX_DEPTH]>, + pub frames: SmallVec<[FrameImpl; MAX_DEPTH]>, pub thread_name: [u8; MAX_THREAD_NAME], pub thread_name_length: usize, pub thread_id: u64, @@ -39,7 +39,7 @@ impl Debug for UnresolvedFrames { } impl UnresolvedFrames { - pub fn new(frames: SmallVec<[Frame; MAX_DEPTH]>, tn: &[u8], thread_id: u64) -> Self { + pub fn new(frames: SmallVec<[FrameImpl; MAX_DEPTH]>, tn: &[u8], thread_id: u64) -> Self { let thread_name_length = tn.len(); let mut thread_name = [0; MAX_THREAD_NAME]; thread_name[0..thread_name_length].clone_from_slice(tn); @@ -96,7 +96,7 @@ pub struct Symbol { impl Symbol { pub fn raw_name(&self) -> &[u8] { - self.name.as_deref().unwrap_or(b"Unknow") + self.name.as_deref().unwrap_or(b"Unknown") } pub fn name(&self) -> String { @@ -111,7 +111,7 @@ impl Symbol { self.filename .as_ref() .map(|name| name.as_os_str().to_string_lossy()) - .unwrap_or_else(|| Cow::Borrowed("Unknow")) + .unwrap_or_else(|| Cow::Borrowed("Unknown")) } pub fn lineno(&self) -> u32 { @@ -121,13 +121,16 @@ impl Symbol { unsafe impl Send for Symbol {} -impl From<&backtrace::Symbol> for Symbol { - fn from(symbol: &backtrace::Symbol) -> Self { +impl From<&T> for Symbol +where + T: crate::backtrace::Symbol, +{ + fn from(symbol: &T) -> Self { Symbol { - name: symbol.name().map(|name| name.as_bytes().to_vec()), + name: symbol.name(), addr: symbol.addr(), lineno: symbol.lineno(), - filename: symbol.filename().map(|filename| filename.to_owned()), + filename: symbol.filename(), } } } @@ -177,9 +180,9 @@ impl From for Frames { let mut frame_iter = frames.frames.iter(); while let Some(frame) = frame_iter.next() { - let mut symbols = Vec::new(); + let mut symbols: Vec = Vec::new(); - backtrace::resolve_frame(frame, |symbol| { + frame.resolve_symbol(|symbol| { let symbol = Symbol::from(symbol); symbols.push(symbol); }); diff --git a/src/lib.rs b/src/lib.rs index 4336939d..d6ab0cc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,7 @@ pub const MAX_DEPTH: usize = 32; /// Define the MAX supported thread name length. TODO: make this variable mutable. pub const MAX_THREAD_NAME: usize = 16; +mod backtrace; mod collector; mod error; mod frames; diff --git a/src/profiler.rs b/src/profiler.rs index c7a5028a..f501e8f0 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -3,7 +3,6 @@ use std::convert::TryInto; use std::os::raw::c_int; -use backtrace::Frame; use nix::sys::signal; use parking_lot::RwLock; use smallvec::SmallVec; @@ -11,6 +10,7 @@ use smallvec::SmallVec; #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] use findshlibs::{Segment, SharedLibrary, TargetSharedLibrary}; +use crate::backtrace::{trace, FrameImpl}; use crate::collector::Collector; use crate::error::{Error, Result}; use crate::frames::UnresolvedFrames; @@ -244,20 +244,18 @@ extern "C" fn perf_signal_handler( } } - let mut bt: SmallVec<[Frame; MAX_DEPTH]> = SmallVec::with_capacity(MAX_DEPTH); + let mut bt: SmallVec<[FrameImpl; MAX_DEPTH]> = SmallVec::with_capacity(MAX_DEPTH); let mut index = 0; - unsafe { - backtrace::trace_unsynchronized(|frame| { - if index < MAX_DEPTH { - bt.push(frame.clone()); - index += 1; - true - } else { - false - } - }); - } + trace(|frame| { + if index < MAX_DEPTH { + bt.push(frame.clone()); + index += 1; + true + } else { + false + } + }); let current_thread = unsafe { libc::pthread_self() }; let mut name = [0; MAX_THREAD_NAME]; @@ -349,7 +347,7 @@ impl Profiler { // This function has to be AS-safe pub fn sample( &mut self, - backtrace: SmallVec<[Frame; MAX_DEPTH]>, + backtrace: SmallVec<[FrameImpl; MAX_DEPTH]>, thread_name: &[u8], thread_id: u64, ) {