From c59e11e78e150c9c17ae0ab3998d22cf902f691e Mon Sep 17 00:00:00 2001 From: YangKeao Date: Tue, 29 Mar 2022 17:51:36 +0800 Subject: [PATCH 01/26] basic framepointer backtrace Signed-off-by: YangKeao --- Cargo.toml | 9 ++++-- src/backtrace/backtrace_rs.rs | 18 +++++++++++ src/backtrace/frame_pointer.rs | 55 ++++++++++++++++++++++++++++++++++ src/backtrace/mod.rs | 46 ++++++++++++++++++++++++++++ src/frames.rs | 25 +++++++++------- src/lib.rs | 17 +++-------- src/profiler.rs | 32 ++++++++++---------- 7 files changed, 159 insertions(+), 43 deletions(-) create mode 100644 src/backtrace/backtrace_rs.rs create mode 100644 src/backtrace/frame_pointer.rs create mode 100644 src/backtrace/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 3a16ca31..58d866fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,16 +10,21 @@ documentation = "https://docs.rs/pprof/" readme = "README.md" [features] -default = ["cpp"] +default = ["cpp", "backtrace-rs"] flamegraph = ["inferno"] + # A private feature to indicate either prost-codec or protobuf-codec is enabled. _protobuf = [] prost-codec = ["prost", "prost-derive", "prost-build", "_protobuf"] protobuf-codec = ["protobuf", "protobuf-codegen-pure", "_protobuf"] + +backtrace-rs = ["backtrace"] +frame-pointer = [] + cpp = ["symbolic-demangle/cpp"] [dependencies] -backtrace = "0.3" +backtrace = { version = "0.3", optional = true } once_cell = "1.9" libc = "^0.2.66" log = "0.4" diff --git a/src/backtrace/backtrace_rs.rs b/src/backtrace/backtrace_rs.rs new file mode 100644 index 00000000..141e6eee --- /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; \ No newline at end of file diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs new file mode 100644 index 00000000..8635514a --- /dev/null +++ b/src/backtrace/frame_pointer.rs @@ -0,0 +1,55 @@ +// Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. + +use std::ptr::null_mut; + +use libc::{backtrace, c_void}; + +#[derive(Clone, Debug)] +pub struct Frame { + pub ip: usize, +} + +extern "C" { + fn _Unwind_FindEnclosingFunction(pc: *mut c_void) -> *mut c_void; + +} + +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 { + if cfg!(target_os = "macos") || cfg!(target_os = "ios") { + self.ip as *mut c_void + } else { + unsafe { _Unwind_FindEnclosingFunction(self.ip as *mut c_void) } + } + } +} + +pub fn trace bool>(mut cb: F) { + let mut backtraces: [*mut *mut c_void; 32] = [null_mut(); 32]; + + let length = unsafe { + let ret = backtrace(backtraces.as_mut_ptr() as *mut *mut c_void, backtraces.len() as i32); + if ret < 0 { + return; + } else { + ret as usize + } + }; + + for backtrace in backtraces[0..length].iter() { + let frame = Frame { + ip: *backtrace as usize, + }; + if !cb(&frame) { + break; + } + } +} + +pub use backtrace::Symbol; \ No newline at end of file diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs new file mode 100644 index 00000000..b4d16dbb --- /dev/null +++ b/src/backtrace/mod.rs @@ -0,0 +1,46 @@ +// Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. + +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(feature = "backtrace-rs")] +// mod backtrace_rs; +// #[cfg(feature = "backtrace-rs")] +// pub use backtrace_rs::{trace, Frame as FrameImpl, Symbol as SymbolImpl}; + +#[cfg(feature = "frame-pointer")] +mod frame_pointer; +#[cfg(feature = "frame-pointer")] +pub use frame_pointer::{trace, Frame as FrameImpl, Symbol as SymbolImpl}; \ No newline at end of file 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 c54e9b09..d6ab0cc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,7 @@ //! More configuration can be passed through `ProfilerGuardBuilder`: //! //! ```rust -//! let guard = pprof::ProfilerGuardBuilder::default().frequency(1000).blocklist(&["libc", "libgcc", "pthread", "vdso"]).build().unwrap(); +//! let guard = pprof::ProfilerGuardBuilder::default().frequency(1000).blocklist(&["libc", "libgcc", "pthread"]).build().unwrap(); //! ``` //! //! The frequency means the sampler frequency, and the `blocklist` means the @@ -33,8 +33,7 @@ //! //! Skipping `libc`, `libgcc` and `libpthread` could be a solution to the //! possible deadlock inside the `_Unwind_Backtrace`, and keep the signal -//! safety. The dwarf information in "vdso" is incorrect in some distributions, -//! so it's also suggested to skip it. +//! safety. //! //! You can find more details in //! [README.md](https://github.com/tikv/pprof-rs/blob/master/README.md) @@ -45,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; @@ -61,21 +61,12 @@ pub use self::report::{Report, ReportBuilder}; #[cfg(feature = "flamegraph")] pub use inferno::flamegraph; -#[cfg(all(feature = "prost-codec", not(feature = "protobuf-codec")))] +#[cfg(feature = "protobuf")] pub mod protos { pub use prost::Message; include!(concat!(env!("OUT_DIR"), "/perftools.profiles.rs")); } -#[cfg(feature = "protobuf-codec")] -pub mod protos { - pub use protobuf::Message; - - include!(concat!(env!("OUT_DIR"), "/mod.rs")); - - pub use self::profile::*; -} - #[cfg(feature = "criterion")] pub mod criterion; diff --git a/src/profiler.rs b/src/profiler.rs index d535e942..f7dcfc05 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 once_cell::sync::Lazy; use parking_lot::RwLock; @@ -12,6 +11,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; @@ -124,7 +124,7 @@ pub struct ProfilerGuard<'a> { fn trigger_lazy() { let _ = backtrace::Backtrace::new(); - let _lock = PROFILER.read(); + let _ = PROFILER.read(); } impl ProfilerGuard<'_> { @@ -181,12 +181,12 @@ fn write_thread_name_fallback(current_thread: libc::pthread_t, name: &mut [libc: } } -#[cfg(not(all(any(target_os = "linux", target_os = "macos"), target_env = "gnu")))] +#[cfg(not(any(target_os = "linux", target_os = "macos")))] fn write_thread_name(current_thread: libc::pthread_t, name: &mut [libc::c_char]) { write_thread_name_fallback(current_thread, name); } -#[cfg(all(any(target_os = "linux", target_os = "macos"), target_env = "gnu"))] +#[cfg(any(target_os = "linux", target_os = "macos"))] fn write_thread_name(current_thread: libc::pthread_t, name: &mut [libc::c_char]) { let name_ptr = name as *mut [libc::c_char] as *mut libc::c_char; let ret = unsafe { libc::pthread_getname_np(current_thread, name_ptr, MAX_THREAD_NAME) }; @@ -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, ) { From 2639a57fcc23b64925c65e2394f152705d777604 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Wed, 30 Mar 2022 14:22:11 +0800 Subject: [PATCH 02/26] implement the framepointer unwind and check Signed-off-by: YangKeao --- src/backtrace/backtrace_rs.rs | 14 +++++- src/backtrace/frame_pointer.rs | 79 ++++++++++++++++++++++++++-------- src/backtrace/mod.rs | 24 +++++++---- src/frames.rs | 6 +-- src/profiler.rs | 16 ++++--- 5 files changed, 104 insertions(+), 35 deletions(-) diff --git a/src/backtrace/backtrace_rs.rs b/src/backtrace/backtrace_rs.rs index 141e6eee..cc62ca9f 100644 --- a/src/backtrace/backtrace_rs.rs +++ b/src/backtrace/backtrace_rs.rs @@ -1,6 +1,10 @@ impl super::Frame for backtrace::Frame { type S = backtrace::Symbol; + fn ip(&self) -> usize { + self.ip() as usize + } + fn resolve_symbol(&self, cb: F) { backtrace::resolve_frame(self, cb); } @@ -10,8 +14,14 @@ impl super::Frame for backtrace::Frame { } } -pub fn trace bool>(cb: F) { - unsafe { backtrace::trace_unsynchronized(cb) } +pub struct Trace {} + +impl super::Trace for Trace { + type Frame = backtrace::Frame; + + fn trace bool>(_: *mut libc::c_void, cb: F) { + unsafe { backtrace::trace_unsynchronized(cb) } + } } pub use backtrace::Frame; diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index 8635514a..ebc89628 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -2,7 +2,7 @@ use std::ptr::null_mut; -use libc::{backtrace, c_void}; +use libc::c_void; #[derive(Clone, Debug)] pub struct Frame { @@ -17,6 +17,10 @@ extern "C" { impl super::Frame for Frame { type S = backtrace::Symbol; + fn ip(&self) -> usize { + self.ip + } + fn resolve_symbol(&self, cb: F) { backtrace::resolve(self.ip as *mut c_void, cb); } @@ -30,26 +34,67 @@ impl super::Frame for Frame { } } -pub fn trace bool>(mut cb: F) { - let mut backtraces: [*mut *mut c_void; 32] = [null_mut(); 32]; +pub struct Trace {} +impl super::Trace for Trace { + type Frame = Frame; - let length = unsafe { - let ret = backtrace(backtraces.as_mut_ptr() as *mut *mut c_void, backtraces.len() as i32); - if ret < 0 { - return; - } else { - ret as usize - } - }; + fn trace bool>(ucontext: *mut libc::c_void, mut cb: F) { + let ucontext: *mut libc::ucontext_t = ucontext as *mut libc::ucontext_t; + + #[cfg(all(target_arch = "x86_64", target_os = "linux"))] + let frame_pointer = unsafe { (*ucontext).uc_mcontext.gregs[libc::REG_RBP as usize] as usize }; - for backtrace in backtraces[0..length].iter() { - let frame = Frame { - ip: *backtrace as usize, + #[cfg(all(target_arch = "x86_64", target_os = "macos"))] + let frame_pointer = unsafe { + let mcontext = (*ucontext).uc_mcontext; + if mcontext.is_null() { + 0 + } else { + (*mcontext).__ss.__rbp as usize + } }; - if !cb(&frame) { - break; + + // TODO: support arm64 + + let mut frame_pointer = frame_pointer as *mut FramePointerLayout; + let mut last_frame_pointer = null_mut(); + loop { + // The stack grow from high address to low address. + // for glibc, we have a `__libc_stack_end` to get the highest address of stack. + #[cfg(target_env = "gnu")] + if frame_pointer > __libc_stack_end { + break; + } + + // the frame pointer should never be smaller than the former one. + if frame_pointer < last_frame_pointer { + break; + } + last_frame_pointer = frame_pointer; + + // iterate to the next frame + let frame = Frame { + ip: unsafe { (*frame_pointer).ret }, + }; + + if !cb(&frame) { + break; + } + frame_pointer = unsafe { (*frame_pointer).frame_pointer }; } } } -pub use backtrace::Symbol; \ No newline at end of file +extern "C" { + static __libc_stack_end: *mut FramePointerLayout; +} + +#[cfg(target_arch = "x86_64")] +#[repr(C)] +struct FramePointerLayout { + frame_pointer: *mut FramePointerLayout, + ret: usize, +} + + +pub use backtrace::Symbol; diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index b4d16dbb..a7674b29 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -33,14 +33,22 @@ pub trait Frame: Sized + Clone { fn resolve_symbol(&self, cb: F); fn symbol_address(&self) -> *mut c_void; + fn ip(&self) -> usize; } -// #[cfg(feature = "backtrace-rs")] -// mod backtrace_rs; -// #[cfg(feature = "backtrace-rs")] -// pub use backtrace_rs::{trace, Frame as FrameImpl, Symbol as SymbolImpl}; -#[cfg(feature = "frame-pointer")] -mod frame_pointer; -#[cfg(feature = "frame-pointer")] -pub use frame_pointer::{trace, Frame as FrameImpl, Symbol as SymbolImpl}; \ No newline at end of file +pub trait Trace { + type Frame; + + fn trace bool>(_: *mut libc::c_void, cb: F) where Self: Sized; +} + +#[cfg(feature = "backtrace-rs")] +mod backtrace_rs; +#[cfg(feature = "backtrace-rs")] +pub use backtrace_rs::Trace as TraceImpl; + +#[cfg(all(any(target_arch = "x86_64", target_arch = "aarch64"), feature = "frame-pointer"))] +pub mod frame_pointer; +#[cfg(all(any(target_arch = "x86_64", target_arch = "aarch64"), feature = "frame-pointer"))] +pub use frame_pointer::Trace as TraceImpl; \ No newline at end of file diff --git a/src/frames.rs b/src/frames.rs index 5877ae03..4677cd75 100644 --- a/src/frames.rs +++ b/src/frames.rs @@ -9,12 +9,12 @@ use std::path::PathBuf; use smallvec::SmallVec; use symbolic_demangle::demangle; -use crate::backtrace::{Frame, FrameImpl}; +use crate::backtrace::{Frame, Trace, TraceImpl}; use crate::{MAX_DEPTH, MAX_THREAD_NAME}; #[derive(Clone)] pub struct UnresolvedFrames { - pub frames: SmallVec<[FrameImpl; MAX_DEPTH]>, + pub frames: SmallVec<[::Frame; 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<[FrameImpl; MAX_DEPTH]>, tn: &[u8], thread_id: u64) -> Self { + pub fn new(frames: SmallVec<[::Frame; 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); diff --git a/src/profiler.rs b/src/profiler.rs index f7dcfc05..f7eef2eb 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -11,7 +11,8 @@ use smallvec::SmallVec; #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] use findshlibs::{Segment, SharedLibrary, TargetSharedLibrary}; -use crate::backtrace::{trace, FrameImpl}; +use crate::backtrace::{TraceImpl, Trace, Frame}; + use crate::collector::Collector; use crate::error::{Error, Result}; use crate::frames::UnresolvedFrames; @@ -54,6 +55,7 @@ impl ProfilerGuardBuilder { pub fn frequency(self, frequency: c_int) -> Self { Self { frequency, ..self } } + #[cfg(all(any(target_arch = "x86_64", target_arch = "aarch64")))] pub fn blocklist>(self, blocklist: &[T]) -> Self { let blocklist_segments = { @@ -208,7 +210,7 @@ extern "C" fn perf_signal_handler( ) { if let Some(mut guard) = PROFILER.try_write() { if let Ok(profiler) = guard.as_mut() { - #[cfg(all(any(target_arch = "x86_64", target_arch = "aarch64")))] + #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] if !ucontext.is_null() { let ucontext: *mut libc::ucontext_t = ucontext as *mut libc::ucontext_t; @@ -244,10 +246,14 @@ extern "C" fn perf_signal_handler( } } - let mut bt: SmallVec<[FrameImpl; MAX_DEPTH]> = SmallVec::with_capacity(MAX_DEPTH); + let mut bt: SmallVec<[::Frame; MAX_DEPTH]> = SmallVec::with_capacity(MAX_DEPTH); let mut index = 0; + TraceImpl::trace(ucontext, |frame| { + let ip = Frame::ip(frame); + if profiler.is_blocklisted(ip) { + return false; + } - trace(|frame| { if index < MAX_DEPTH { bt.push(frame.clone()); index += 1; @@ -347,7 +353,7 @@ impl Profiler { // This function has to be AS-safe pub fn sample( &mut self, - backtrace: SmallVec<[FrameImpl; MAX_DEPTH]>, + backtrace: SmallVec<[::Frame; MAX_DEPTH]>, thread_name: &[u8], thread_id: u64, ) { From 5479357996533dc4611cbb196fbb2c73a4d3c959 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Wed, 30 Mar 2022 14:38:48 +0800 Subject: [PATCH 03/26] support aarch64 on linux Signed-off-by: YangKeao --- CHANGELOG.md | 3 +++ Cargo.toml | 2 +- src/backtrace/frame_pointer.rs | 14 ++++++++------ src/backtrace/mod.rs | 1 - 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5306f264..b6d8c37c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Add `frame-pointer` feature to unwind the stack with frame pointer. + ## [0.7.0] - 2022-03-08 ### Added diff --git a/Cargo.toml b/Cargo.toml index 58d866fe..9807e03c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ prost-codec = ["prost", "prost-derive", "prost-build", "_protobuf"] protobuf-codec = ["protobuf", "protobuf-codegen-pure", "_protobuf"] backtrace-rs = ["backtrace"] -frame-pointer = [] +frame-pointer = ["backtrace"] cpp = ["symbolic-demangle/cpp"] diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index ebc89628..7e6bb870 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -54,7 +54,10 @@ impl super::Trace for Trace { } }; - // TODO: support arm64 + #[cfg(all(target_arch = "aarch64", target_os = "linux"))] + let addr = unsafe { (*ucontext).uc_mcontext.regs[29] as usize }; + + // TODO: support arm64 on macos let mut frame_pointer = frame_pointer as *mut FramePointerLayout; let mut last_frame_pointer = null_mut(); @@ -62,8 +65,10 @@ impl super::Trace for Trace { // The stack grow from high address to low address. // for glibc, we have a `__libc_stack_end` to get the highest address of stack. #[cfg(target_env = "gnu")] - if frame_pointer > __libc_stack_end { - break; + unsafe { + if frame_pointer > __libc_stack_end { + break; + } } // the frame pointer should never be smaller than the former one. @@ -95,6 +100,3 @@ struct FramePointerLayout { frame_pointer: *mut FramePointerLayout, ret: usize, } - - -pub use backtrace::Symbol; diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index a7674b29..3c8ad130 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -36,7 +36,6 @@ pub trait Frame: Sized + Clone { fn ip(&self) -> usize; } - pub trait Trace { type Frame; From 5bf438462f12ad748f5ebdca8062307283427368 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Wed, 30 Mar 2022 14:54:36 +0800 Subject: [PATCH 04/26] fix struct layout representation Signed-off-by: YangKeao --- src/backtrace/frame_pointer.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index 7e6bb870..868fe7e4 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -94,7 +94,6 @@ extern "C" { static __libc_stack_end: *mut FramePointerLayout; } -#[cfg(target_arch = "x86_64")] #[repr(C)] struct FramePointerLayout { frame_pointer: *mut FramePointerLayout, From 4a5be7be9c739c594d40ef0673aae3fe6f6dbdb7 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Wed, 30 Mar 2022 14:55:15 +0800 Subject: [PATCH 05/26] fix addr to frame_pointer Signed-off-by: YangKeao --- src/backtrace/frame_pointer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index 868fe7e4..8d712c8e 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -55,7 +55,7 @@ impl super::Trace for Trace { }; #[cfg(all(target_arch = "aarch64", target_os = "linux"))] - let addr = unsafe { (*ucontext).uc_mcontext.regs[29] as usize }; + let frame_pointer = unsafe { (*ucontext).uc_mcontext.regs[29] as usize }; // TODO: support arm64 on macos From 833b92579034b08bab13e99f1a734420aee5bed1 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Wed, 30 Mar 2022 15:39:40 +0800 Subject: [PATCH 06/26] format the codes Signed-off-by: YangKeao --- src/backtrace/backtrace_rs.rs | 2 +- src/backtrace/frame_pointer.rs | 5 +++-- src/backtrace/mod.rs | 16 ++++++++++++---- src/frames.rs | 6 +++++- src/profiler.rs | 5 +++-- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/backtrace/backtrace_rs.rs b/src/backtrace/backtrace_rs.rs index cc62ca9f..1b7e3663 100644 --- a/src/backtrace/backtrace_rs.rs +++ b/src/backtrace/backtrace_rs.rs @@ -25,4 +25,4 @@ impl super::Trace for Trace { } pub use backtrace::Frame; -pub use backtrace::Symbol; \ No newline at end of file +pub use backtrace::Symbol; diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index 8d712c8e..ac43530c 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -40,9 +40,10 @@ impl super::Trace for Trace { fn trace bool>(ucontext: *mut libc::c_void, mut cb: F) { let ucontext: *mut libc::ucontext_t = ucontext as *mut libc::ucontext_t; - + #[cfg(all(target_arch = "x86_64", target_os = "linux"))] - let frame_pointer = unsafe { (*ucontext).uc_mcontext.gregs[libc::REG_RBP as usize] as usize }; + let frame_pointer = + unsafe { (*ucontext).uc_mcontext.gregs[libc::REG_RBP as usize] as usize }; #[cfg(all(target_arch = "x86_64", target_os = "macos"))] let frame_pointer = unsafe { diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index 3c8ad130..f838e858 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -39,7 +39,9 @@ pub trait Frame: Sized + Clone { pub trait Trace { type Frame; - fn trace bool>(_: *mut libc::c_void, cb: F) where Self: Sized; + fn trace bool>(_: *mut libc::c_void, cb: F) + where + Self: Sized; } #[cfg(feature = "backtrace-rs")] @@ -47,7 +49,13 @@ mod backtrace_rs; #[cfg(feature = "backtrace-rs")] pub use backtrace_rs::Trace as TraceImpl; -#[cfg(all(any(target_arch = "x86_64", target_arch = "aarch64"), feature = "frame-pointer"))] +#[cfg(all( + any(target_arch = "x86_64", target_arch = "aarch64"), + feature = "frame-pointer" +))] pub mod frame_pointer; -#[cfg(all(any(target_arch = "x86_64", target_arch = "aarch64"), feature = "frame-pointer"))] -pub use frame_pointer::Trace as TraceImpl; \ No newline at end of file +#[cfg(all( + any(target_arch = "x86_64", target_arch = "aarch64"), + feature = "frame-pointer" +))] +pub use frame_pointer::Trace as TraceImpl; diff --git a/src/frames.rs b/src/frames.rs index 4677cd75..83ed61a9 100644 --- a/src/frames.rs +++ b/src/frames.rs @@ -39,7 +39,11 @@ 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<[::Frame; 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); diff --git a/src/profiler.rs b/src/profiler.rs index f7eef2eb..7a026aa0 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -11,7 +11,7 @@ use smallvec::SmallVec; #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] use findshlibs::{Segment, SharedLibrary, TargetSharedLibrary}; -use crate::backtrace::{TraceImpl, Trace, Frame}; +use crate::backtrace::{Frame, Trace, TraceImpl}; use crate::collector::Collector; use crate::error::{Error, Result}; @@ -246,7 +246,8 @@ extern "C" fn perf_signal_handler( } } - let mut bt: SmallVec<[::Frame; MAX_DEPTH]> = SmallVec::with_capacity(MAX_DEPTH); + let mut bt: SmallVec<[::Frame; MAX_DEPTH]> = + SmallVec::with_capacity(MAX_DEPTH); let mut index = 0; TraceImpl::trace(ucontext, |frame| { let ip = Frame::ip(frame); From ed70355440b5a944bc3caf08ca9a3341d1b71bcc Mon Sep 17 00:00:00 2001 From: YangKeao Date: Sat, 2 Apr 2022 18:16:27 +0800 Subject: [PATCH 07/26] add back lib.rs Signed-off-by: YangKeao --- src/lib.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d6ab0cc7..4bc0151a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,7 @@ //! More configuration can be passed through `ProfilerGuardBuilder`: //! //! ```rust -//! let guard = pprof::ProfilerGuardBuilder::default().frequency(1000).blocklist(&["libc", "libgcc", "pthread"]).build().unwrap(); +//! let guard = pprof::ProfilerGuardBuilder::default().frequency(1000).blocklist(&["libc", "libgcc", "pthread", "vdso"]).build().unwrap(); //! ``` //! //! The frequency means the sampler frequency, and the `blocklist` means the @@ -33,7 +33,8 @@ //! //! Skipping `libc`, `libgcc` and `libpthread` could be a solution to the //! possible deadlock inside the `_Unwind_Backtrace`, and keep the signal -//! safety. +//! safety. The dwarf information in "vdso" is incorrect in some distributions, +//! so it's also suggested to skip it. //! //! You can find more details in //! [README.md](https://github.com/tikv/pprof-rs/blob/master/README.md) @@ -61,12 +62,21 @@ pub use self::report::{Report, ReportBuilder}; #[cfg(feature = "flamegraph")] pub use inferno::flamegraph; -#[cfg(feature = "protobuf")] +#[cfg(all(feature = "prost-codec", not(feature = "protobuf-codec")))] pub mod protos { pub use prost::Message; include!(concat!(env!("OUT_DIR"), "/perftools.profiles.rs")); } +#[cfg(feature = "protobuf-codec")] +pub mod protos { + pub use protobuf::Message; + + include!(concat!(env!("OUT_DIR"), "/mod.rs")); + + pub use self::profile::*; +} + #[cfg(feature = "criterion")] pub mod criterion; From 6197f2e5a8fdd0460d3070e6f9b67e6c0750bf2c Mon Sep 17 00:00:00 2001 From: YangKeao Date: Wed, 6 Apr 2022 05:20:05 +0800 Subject: [PATCH 08/26] initialize the last_frame_pointer with a proper value Signed-off-by: YangKeao --- src/backtrace/frame_pointer.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index ac43530c..1c36deff 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -1,6 +1,6 @@ // Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. -use std::ptr::null_mut; +use std::arch::asm; use libc::c_void; @@ -61,7 +61,27 @@ impl super::Trace for Trace { // TODO: support arm64 on macos let mut frame_pointer = frame_pointer as *mut FramePointerLayout; - let mut last_frame_pointer = null_mut(); + + // Initialize the `last_frame_pointer` with current frame pointer + // in case that the first frame doesn't have a frame pointer, and cause + // panic + + // So that, we get a rough scope of the stack: from `__libc_stack_end` + // to the current stack pointer + let mut last_frame_pointer; + unsafe { + #[cfg(target_arch = "x86_64")] + asm!( + "mov {last_frame_pointer}, rbp", + last_frame_pointer = out(reg) last_frame_pointer, + ); + + #[cfg(target_arch = "aarch64")] + asm!( + "mov {last_frame_pointer}, r29", + last_frame_pointer = out(reg) last_frame_pointer, + ); + } loop { // The stack grow from high address to low address. // for glibc, we have a `__libc_stack_end` to get the highest address of stack. From 825b61dbaae012d60d577cff81edabf98a2187a5 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Wed, 6 Apr 2022 16:15:34 +0800 Subject: [PATCH 09/26] fix cargo clippy Signed-off-by: YangKeao --- .github/workflows/rust.yml | 6 ++++++ README.md | 8 ++++++++ src/backtrace/frame_pointer.rs | 18 +++++++----------- src/profiler.rs | 2 +- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 299af1d7..9cad49ef 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -79,6 +79,12 @@ jobs: command: build args: --features flamegraph,protobuf-codec --target ${{ matrix.target }} + - name: Run cargo build frame pointer + uses: actions-rs/cargo@v1.0.3 + with: + command: build + args: --no-default-features --features frame-pointer --target ${{ matrix.target }} + test: name: Test strategy: diff --git a/README.md b/README.md index bfca1da9..b52ed97d 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ FRAME: backtrace::backtrace::trace::h3e91a3123a3049a5 -> FRAME: pprof::profiler: - `flamegraph` enables the flamegraph report format. - `prost-codec` enables the pprof protobuf report format through `prost`. - `protobuf-codec` enables the pprof protobuf report format through `protobuf` crate. +- `backtrace-rs` unwind the backtrace through `backtrace-rs` (which calls the `Unwind_Backtrace`). +- `frame-pointer` gets the backtrace through frame pointer. ## Flamegraph @@ -222,6 +224,12 @@ let guard = pprof::ProfilerGuardBuilder::default().frequency(1000).blocklist(&[" The `vdso` should also be added to the blocklist, because in some distribution (e.g. ubuntu 18.04), the dwarf information in vdso is incorrect. +### Frame Pointer + +The `pprof-rs` also supports unwinding through frame pointer, without the need to use `libunwind`. However, the standard library shipped with the rust compiler does not have the correct frame pointer in every function, so you need to use `cargo +nightly -Z build-std` to build the standard library from source. + +As we cannot get the stack boundaries inside the signal handler, it's also not possible to ensure the safety. If the frame pointer was set to a wrong value, the program will panic. + ### Signal Safety Signal safety is hard to guarantee. But it's not *that* hard. diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index 1c36deff..6c9bbcf7 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -84,13 +84,13 @@ impl super::Trace for Trace { } loop { // The stack grow from high address to low address. - // for glibc, we have a `__libc_stack_end` to get the highest address of stack. - #[cfg(target_env = "gnu")] - unsafe { - if frame_pointer > __libc_stack_end { - break; - } - } + // but we don't have a reasonable assumption for the hightest address + // the `__libc_stack_end` is not thread-local, and only represent the + // stack end of the main thread. For other thread, their stacks are allocated + // by the `pthread`. + // + // TODO: If we can hook the thread creation, we will have chance to get the + // stack end through `pthread_get_attr`. // the frame pointer should never be smaller than the former one. if frame_pointer < last_frame_pointer { @@ -111,10 +111,6 @@ impl super::Trace for Trace { } } -extern "C" { - static __libc_stack_end: *mut FramePointerLayout; -} - #[repr(C)] struct FramePointerLayout { frame_pointer: *mut FramePointerLayout, diff --git a/src/profiler.rs b/src/profiler.rs index 7a026aa0..9d0644c4 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -126,7 +126,7 @@ pub struct ProfilerGuard<'a> { fn trigger_lazy() { let _ = backtrace::Backtrace::new(); - let _ = PROFILER.read(); + let _profiler = PROFILER.read(); } impl ProfilerGuard<'_> { From 1b2014adce32152f44a1f6c1a4156b54272dc3d1 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Wed, 6 Apr 2022 18:07:32 +0800 Subject: [PATCH 10/26] fix the compilation on arm Signed-off-by: YangKeao --- src/backtrace/frame_pointer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index 6c9bbcf7..1d53677e 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -78,7 +78,7 @@ impl super::Trace for Trace { #[cfg(target_arch = "aarch64")] asm!( - "mov {last_frame_pointer}, r29", + "mov {last_frame_pointer}, x29", last_frame_pointer = out(reg) last_frame_pointer, ); } From bf99a44d6c0e30ee6b9ede64a5ff1c355f42abb6 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Wed, 6 Apr 2022 18:14:14 +0800 Subject: [PATCH 11/26] only build frame pointer with nightly toolchain Signed-off-by: YangKeao --- .github/workflows/rust.yml | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9cad49ef..c2d8e3c4 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -80,6 +80,7 @@ jobs: args: --features flamegraph,protobuf-codec --target ${{ matrix.target }} - name: Run cargo build frame pointer + if: ${{ matrix.toolchain == 'nightly' }} uses: actions-rs/cargo@v1.0.3 with: command: build diff --git a/README.md b/README.md index b52ed97d..5eec9b95 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ FRAME: backtrace::backtrace::trace::h3e91a3123a3049a5 -> FRAME: pprof::profiler: - `prost-codec` enables the pprof protobuf report format through `prost`. - `protobuf-codec` enables the pprof protobuf report format through `protobuf` crate. - `backtrace-rs` unwind the backtrace through `backtrace-rs` (which calls the `Unwind_Backtrace`). -- `frame-pointer` gets the backtrace through frame pointer. +- `frame-pointer` gets the backtrace through frame pointer. **only available for nightly** ## Flamegraph From 23c10cfb742cde7c1fcbda8e37ea6bd08d360544 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Wed, 6 Apr 2022 18:59:19 +0800 Subject: [PATCH 12/26] only compile on ubuntu-latest Signed-off-by: YangKeao --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c2d8e3c4..b79e23fb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -80,7 +80,7 @@ jobs: args: --features flamegraph,protobuf-codec --target ${{ matrix.target }} - name: Run cargo build frame pointer - if: ${{ matrix.toolchain == 'nightly' }} + if: ${{ matrix.toolchain == 'nightly' && matrix.os == 'ubuntu-latest' }} uses: actions-rs/cargo@v1.0.3 with: command: build From a29259f9afe2b2a6e33bfe57cd2fb9890c792dff Mon Sep 17 00:00:00 2001 From: YangKeao Date: Wed, 6 Apr 2022 19:01:35 +0800 Subject: [PATCH 13/26] add changelog Signed-off-by: YangKeao --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6d8c37c..8c50eccb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Add `frame-pointer` feature to unwind the stack with frame pointer. +- Add `frame-pointer` feature to unwind the stack with frame pointer (#116) + +### Changed +- The user has to specify one unwind implementation (`backtrace-rs` or `frame-pointer`) in the features (#116) ## [0.7.0] - 2022-03-08 From 708d1dff283dcc1565ad0bbf0fdbdd9de01435a8 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Mon, 11 Apr 2022 15:51:51 +0800 Subject: [PATCH 14/26] add a addr validator through pipe Signed-off-by: YangKeao --- src/addr_validate.rs | 91 ++++++++++++++++++++++++++++++++++ src/backtrace/frame_pointer.rs | 6 +++ src/lib.rs | 1 + 3 files changed, 98 insertions(+) create mode 100644 src/addr_validate.rs diff --git a/src/addr_validate.rs b/src/addr_validate.rs new file mode 100644 index 00000000..4d8e3457 --- /dev/null +++ b/src/addr_validate.rs @@ -0,0 +1,91 @@ +use std::cell::RefCell; + +use nix::{ + errno::Errno, + fcntl::OFlag, + unistd::{close, pipe2, read, write}, +}; + +thread_local! { + static MEM_VALIDATE_PIPE: RefCell<[i32; 2]> = RefCell::new([-1, -1]); +} + +fn open_pipe() -> nix::Result<()> { + MEM_VALIDATE_PIPE.with(|pipes| { + let mut pipes = pipes.borrow_mut(); + + // ignore the result + let _ = close(pipes[0]); + let _ = close(pipes[1]); + + let (read_fd, write_fd) = pipe2(OFlag::O_CLOEXEC | OFlag::O_NONBLOCK)?; + + pipes[0] = read_fd; + pipes[1] = write_fd; + + return Ok(()); + }) +} + +pub fn validate(addr: *const libc::c_void) -> bool { + // read data in the pipe + let valid_read = MEM_VALIDATE_PIPE.with(|pipes| { + let pipes = pipes.borrow(); + loop { + let mut buf = [0u8]; + + match read(pipes[0], &mut buf) { + Ok(bytes) => break bytes > 0, + Err(_err @ Errno::EINTR) => continue, + Err(_err @ Errno::EAGAIN) => break true, + Err(_) => break false, + } + } + }); + + if !valid_read { + if let Err(_) = open_pipe() { + return false; + } + } + + MEM_VALIDATE_PIPE.with(|pipes| { + let pipes = pipes.borrow(); + loop { + let buf = unsafe { std::slice::from_raw_parts(addr as *const u8, 1) }; + + match write(pipes[1], buf) { + Ok(bytes) => break bytes > 0, + Err(_err @ Errno::EINTR) => continue, + Err(_) => break false, + } + } + }) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn validate_stack() { + let i = 0; + + assert_eq!(validate(&i as *const _ as *const libc::c_void), true); + } + + #[test] + fn validate_heap() { + let vec = vec![0; 1000]; + + for i in vec.iter() { + assert_eq!(validate(i as *const _ as *const libc::c_void), true); + } + } + + #[test] + fn failed_validate() { + assert_eq!(validate(0 as *const libc::c_void), false); + assert_eq!(validate((-1 as i32) as usize as *const libc::c_void), false) + } +} diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index 1d53677e..d9eed436 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -4,6 +4,8 @@ use std::arch::asm; use libc::c_void; +use crate::addr_validate::validate; + #[derive(Clone, Debug)] pub struct Frame { pub ip: usize, @@ -96,6 +98,10 @@ impl super::Trace for Trace { if frame_pointer < last_frame_pointer { break; } + + if !validate(frame_pointer as *const libc::c_void) { + break; + } last_frame_pointer = frame_pointer; // iterate to the next frame diff --git a/src/lib.rs b/src/lib.rs index 4bc0151a..7d1382cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,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 addr_validate; mod backtrace; mod collector; mod error; From e0ebabb178d56665c8029b88795a9f69afb4e62b Mon Sep 17 00:00:00 2001 From: YangKeao Date: Mon, 11 Apr 2022 16:02:42 +0800 Subject: [PATCH 15/26] add benchmark for addr_validate Signed-off-by: YangKeao --- Cargo.toml | 5 +++++ benches/addr_validate.rs | 29 +++++++++++++++++++++++++++++ src/lib.rs | 2 ++ 3 files changed, 36 insertions(+) create mode 100644 benches/addr_validate.rs diff --git a/Cargo.toml b/Cargo.toml index 9807e03c..33325ad7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,5 +76,10 @@ name = "collector" path = "benches/collector.rs" harness = false +[[bench]] +name = "addr_validate" +path = "benches/addr_validate.rs" +harness = false + [package.metadata.docs.rs] all-features = true diff --git a/benches/addr_validate.rs b/benches/addr_validate.rs new file mode 100644 index 00000000..4154f33f --- /dev/null +++ b/benches/addr_validate.rs @@ -0,0 +1,29 @@ +// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. + +use criterion::{criterion_group, criterion_main, Criterion}; +use pprof::validate; + +fn bench_validate_addr(c: &mut Criterion) { + c.bench_function("validate stack addr", |b| { + let stack_addrs = [0; 100]; + + b.iter(|| { + stack_addrs.iter().for_each(|item| { + validate(item as *const _ as *const libc::c_void); + }) + }) + }); + + c.bench_function("validate heap addr", |b| { + let heap_addrs = vec![0; 100]; + + b.iter(|| { + heap_addrs.iter().for_each(|item| { + validate(item as *const _ as *const libc::c_void); + }) + }) + }); +} + +criterion_group!(benches, bench_validate_addr); +criterion_main!(benches); diff --git a/src/lib.rs b/src/lib.rs index 7d1382cd..74641959 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,7 +54,9 @@ mod profiler; mod report; mod timer; +pub use self::addr_validate::validate; pub use self::collector::{Collector, HashCounter}; + pub use self::error::{Error, Result}; pub use self::frames::{Frames, Symbol}; pub use self::profiler::{ProfilerGuard, ProfilerGuardBuilder}; From 93e9f6b869e366359e17ee410be8e4f670654da2 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Mon, 11 Apr 2022 16:04:19 +0800 Subject: [PATCH 16/26] fine tune the clippy Signed-off-by: YangKeao --- src/addr_validate.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/addr_validate.rs b/src/addr_validate.rs index 4d8e3457..a2f65d36 100644 --- a/src/addr_validate.rs +++ b/src/addr_validate.rs @@ -23,7 +23,7 @@ fn open_pipe() -> nix::Result<()> { pipes[0] = read_fd; pipes[1] = write_fd; - return Ok(()); + Ok(()) }) } @@ -43,10 +43,8 @@ pub fn validate(addr: *const libc::c_void) -> bool { } }); - if !valid_read { - if let Err(_) = open_pipe() { - return false; - } + if !valid_read && open_pipe().is_err() { + return false; } MEM_VALIDATE_PIPE.with(|pipes| { From c212ffa60943c468b1339db7e739cecd0d631511 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Mon, 11 Apr 2022 16:21:51 +0800 Subject: [PATCH 17/26] only allow frame pointer in linux Signed-off-by: YangKeao --- src/backtrace/mod.rs | 2 ++ src/lib.rs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index f838e858..702e6977 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -51,11 +51,13 @@ pub use backtrace_rs::Trace as TraceImpl; #[cfg(all( any(target_arch = "x86_64", target_arch = "aarch64"), + target_os = "linux", feature = "frame-pointer" ))] pub mod frame_pointer; #[cfg(all( any(target_arch = "x86_64", target_arch = "aarch64"), + target_os = "linux", feature = "frame-pointer" ))] pub use frame_pointer::Trace as TraceImpl; diff --git a/src/lib.rs b/src/lib.rs index 74641959..3fa6cc93 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,9 @@ 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; +#[cfg(target_os = "linux")] mod addr_validate; + mod backtrace; mod collector; mod error; @@ -54,7 +56,9 @@ mod profiler; mod report; mod timer; +#[cfg(target_os = "linux")] pub use self::addr_validate::validate; + pub use self::collector::{Collector, HashCounter}; pub use self::error::{Error, Result}; From 95fd25ea0f6a130a147cf3591ee6d7a3f25be30c Mon Sep 17 00:00:00 2001 From: YangKeao Date: Mon, 11 Apr 2022 17:30:11 +0800 Subject: [PATCH 18/26] extend the check length Signed-off-by: YangKeao --- src/addr_validate.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/addr_validate.rs b/src/addr_validate.rs index a2f65d36..522a4b24 100644 --- a/src/addr_validate.rs +++ b/src/addr_validate.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::{cell::RefCell, mem::size_of}; use nix::{ errno::Errno, @@ -28,11 +28,13 @@ fn open_pipe() -> nix::Result<()> { } pub fn validate(addr: *const libc::c_void) -> bool { + const CHECK_LENGTH: usize = 2 * size_of::<*const libc::c_void>() / size_of::(); + // read data in the pipe let valid_read = MEM_VALIDATE_PIPE.with(|pipes| { let pipes = pipes.borrow(); loop { - let mut buf = [0u8]; + let mut buf = [0u8; CHECK_LENGTH]; match read(pipes[0], &mut buf) { Ok(bytes) => break bytes > 0, @@ -50,7 +52,7 @@ pub fn validate(addr: *const libc::c_void) -> bool { MEM_VALIDATE_PIPE.with(|pipes| { let pipes = pipes.borrow(); loop { - let buf = unsafe { std::slice::from_raw_parts(addr as *const u8, 1) }; + let buf = unsafe { std::slice::from_raw_parts(addr as *const u8, CHECK_LENGTH) }; match write(pipes[1], buf) { Ok(bytes) => break bytes > 0, From e7228d26f5f02a11c32b9e0ef83969559b5b9499 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Tue, 12 Apr 2022 13:28:04 +0800 Subject: [PATCH 19/26] only validate on linux, also build on macos Signed-off-by: YangKeao --- src/backtrace/frame_pointer.rs | 2 ++ src/backtrace/mod.rs | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index d9eed436..67b2382b 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -4,6 +4,7 @@ use std::arch::asm; use libc::c_void; +#[cfg(target_os = "linux")] use crate::addr_validate::validate; #[derive(Clone, Debug)] @@ -99,6 +100,7 @@ impl super::Trace for Trace { break; } + #[cfg(target_os = "linux")] if !validate(frame_pointer as *const libc::c_void) { break; } diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index 702e6977..f838e858 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -51,13 +51,11 @@ pub use backtrace_rs::Trace as TraceImpl; #[cfg(all( any(target_arch = "x86_64", target_arch = "aarch64"), - target_os = "linux", feature = "frame-pointer" ))] pub mod frame_pointer; #[cfg(all( any(target_arch = "x86_64", target_arch = "aarch64"), - target_os = "linux", feature = "frame-pointer" ))] pub use frame_pointer::Trace as TraceImpl; From 004a56e8fd8feba1680e1815ab69349aa82edd58 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Sat, 7 May 2022 14:24:29 +0800 Subject: [PATCH 20/26] fix according to comments Signed-off-by: YangKeao --- src/addr_validate.rs | 31 ++++++++++++++++++++++++++++--- src/backtrace/frame_pointer.rs | 26 +++++--------------------- src/lib.rs | 2 -- src/profiler.rs | 1 - 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/addr_validate.rs b/src/addr_validate.rs index 522a4b24..44019815 100644 --- a/src/addr_validate.rs +++ b/src/addr_validate.rs @@ -2,14 +2,39 @@ use std::{cell::RefCell, mem::size_of}; use nix::{ errno::Errno, - fcntl::OFlag, - unistd::{close, pipe2, read, write}, + unistd::{close, read, write}, }; thread_local! { static MEM_VALIDATE_PIPE: RefCell<[i32; 2]> = RefCell::new([-1, -1]); } +#[inline] +#[cfg(target_os = "linux")] +fn create_pipe() -> nix::Result<(i32, i32)> { + use nix::fcntl::OFlag; + use nix::unistd::pipe2; + + pipe2(OFlag::O_CLOEXEC | OFlag::O_NONBLOCK) +} + +#[cfg(target_os = "macos")] +unsafe fn create_pipe() -> nix::Result<(i32, i32)> { + use nix::fcntl::{fcntl, OFlag}; + + let (read_fd, write_fd) = libc::pipe(fds)?; + + let mut flags = fcntl(read_fd, FcntlArg::F_GETFL)?; + flags |= libc::O_CLOEXEC; + fcntl(read_fd, libc::F_SETFD, flags)?; + + let mut flags = fcntl(write_fd, FcntlArg::F_GETFL)?; + flags |= libc::O_CLOEXEC; + fcntl(write_fd, libc::F_SETFD, flags)?; + + Ok((read_fd, write_fd)) +} + fn open_pipe() -> nix::Result<()> { MEM_VALIDATE_PIPE.with(|pipes| { let mut pipes = pipes.borrow_mut(); @@ -18,7 +43,7 @@ fn open_pipe() -> nix::Result<()> { let _ = close(pipes[0]); let _ = close(pipes[1]); - let (read_fd, write_fd) = pipe2(OFlag::O_CLOEXEC | OFlag::O_NONBLOCK)?; + let (read_fd, write_fd) = create_pipe()?; pipes[0] = read_fd; pipes[1] = write_fd; diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index 67b2382b..258352eb 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -43,6 +43,9 @@ impl super::Trace for Trace { fn trace bool>(ucontext: *mut libc::c_void, mut cb: F) { let ucontext: *mut libc::ucontext_t = ucontext as *mut libc::ucontext_t; + if ucontext.is_null() { + return; + } #[cfg(all(target_arch = "x86_64", target_os = "linux"))] let frame_pointer = @@ -65,26 +68,7 @@ impl super::Trace for Trace { let mut frame_pointer = frame_pointer as *mut FramePointerLayout; - // Initialize the `last_frame_pointer` with current frame pointer - // in case that the first frame doesn't have a frame pointer, and cause - // panic - - // So that, we get a rough scope of the stack: from `__libc_stack_end` - // to the current stack pointer - let mut last_frame_pointer; - unsafe { - #[cfg(target_arch = "x86_64")] - asm!( - "mov {last_frame_pointer}, rbp", - last_frame_pointer = out(reg) last_frame_pointer, - ); - - #[cfg(target_arch = "aarch64")] - asm!( - "mov {last_frame_pointer}, x29", - last_frame_pointer = out(reg) last_frame_pointer, - ); - } + let mut last_frame_pointer = 0; loop { // The stack grow from high address to low address. // but we don't have a reasonable assumption for the hightest address @@ -96,7 +80,7 @@ impl super::Trace for Trace { // stack end through `pthread_get_attr`. // the frame pointer should never be smaller than the former one. - if frame_pointer < last_frame_pointer { + if last_frame_pointer != 0 && frame_pointer < last_frame_pointer { break; } diff --git a/src/lib.rs b/src/lib.rs index 3fa6cc93..44b6a687 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,11 +56,9 @@ mod profiler; mod report; mod timer; -#[cfg(target_os = "linux")] pub use self::addr_validate::validate; pub use self::collector::{Collector, HashCounter}; - pub use self::error::{Error, Result}; pub use self::frames::{Frames, Symbol}; pub use self::profiler::{ProfilerGuard, ProfilerGuardBuilder}; diff --git a/src/profiler.rs b/src/profiler.rs index 9d0644c4..0012e245 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -12,7 +12,6 @@ use smallvec::SmallVec; use findshlibs::{Segment, SharedLibrary, TargetSharedLibrary}; use crate::backtrace::{Frame, Trace, TraceImpl}; - use crate::collector::Collector; use crate::error::{Error, Result}; use crate::frames::UnresolvedFrames; From 6a177ecc9899cfc70b1f8609bcf8d035c68b0f21 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Sat, 7 May 2022 14:31:28 +0800 Subject: [PATCH 21/26] fix grammar Signed-off-by: YangKeao --- src/backtrace/frame_pointer.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index 258352eb..ccee4ba1 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -1,6 +1,6 @@ // Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. -use std::arch::asm; +use std::ptr::null_mut; use libc::c_void; @@ -68,7 +68,7 @@ impl super::Trace for Trace { let mut frame_pointer = frame_pointer as *mut FramePointerLayout; - let mut last_frame_pointer = 0; + let mut last_frame_pointer: *mut FramePointerLayout = null_mut(); loop { // The stack grow from high address to low address. // but we don't have a reasonable assumption for the hightest address @@ -80,7 +80,7 @@ impl super::Trace for Trace { // stack end through `pthread_get_attr`. // the frame pointer should never be smaller than the former one. - if last_frame_pointer != 0 && frame_pointer < last_frame_pointer { + if !last_frame_pointer.is_null() && frame_pointer < last_frame_pointer { break; } From ecc540e31c1c2fe0da996d14f26cd1c59c4f7925 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Sat, 7 May 2022 15:05:03 +0800 Subject: [PATCH 22/26] build on macos Signed-off-by: YangKeao --- src/addr_validate.rs | 20 +++++++++++--------- src/lib.rs | 2 -- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/addr_validate.rs b/src/addr_validate.rs index 44019815..d0ff4403 100644 --- a/src/addr_validate.rs +++ b/src/addr_validate.rs @@ -18,19 +18,21 @@ fn create_pipe() -> nix::Result<(i32, i32)> { pipe2(OFlag::O_CLOEXEC | OFlag::O_NONBLOCK) } +#[inline] #[cfg(target_os = "macos")] -unsafe fn create_pipe() -> nix::Result<(i32, i32)> { - use nix::fcntl::{fcntl, OFlag}; +fn create_pipe() -> nix::Result<(i32, i32)> { + use nix::fcntl::{fcntl, FcntlArg, FdFlag}; + use nix::unistd::pipe; - let (read_fd, write_fd) = libc::pipe(fds)?; + let (read_fd, write_fd) = pipe()?; - let mut flags = fcntl(read_fd, FcntlArg::F_GETFL)?; - flags |= libc::O_CLOEXEC; - fcntl(read_fd, libc::F_SETFD, flags)?; + let mut flags = FdFlag::from_bits(fcntl(read_fd, FcntlArg::F_GETFL)?).unwrap(); + flags |= FdFlag::FD_CLOEXEC; + fcntl(read_fd, FcntlArg::F_SETFD(flags))?; - let mut flags = fcntl(write_fd, FcntlArg::F_GETFL)?; - flags |= libc::O_CLOEXEC; - fcntl(write_fd, libc::F_SETFD, flags)?; + let mut flags = FdFlag::from_bits(fcntl(write_fd, FcntlArg::F_GETFL)?).unwrap(); + flags |= FdFlag::FD_CLOEXEC; + fcntl(write_fd, FcntlArg::F_SETFD(flags))?; Ok((read_fd, write_fd)) } diff --git a/src/lib.rs b/src/lib.rs index 44b6a687..2024f654 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,6 @@ 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; -#[cfg(target_os = "linux")] mod addr_validate; mod backtrace; @@ -57,7 +56,6 @@ mod report; mod timer; pub use self::addr_validate::validate; - pub use self::collector::{Collector, HashCounter}; pub use self::error::{Error, Result}; pub use self::frames::{Frames, Symbol}; From 95bd3495db786f274741b28f918d1135efa6a820 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Mon, 9 May 2022 13:24:20 +0800 Subject: [PATCH 23/26] add NON_BLOCK for the pipe in macos Signed-off-by: YangKeao --- src/addr_validate.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/addr_validate.rs b/src/addr_validate.rs index d0ff4403..9f60171f 100644 --- a/src/addr_validate.rs +++ b/src/addr_validate.rs @@ -27,11 +27,11 @@ fn create_pipe() -> nix::Result<(i32, i32)> { let (read_fd, write_fd) = pipe()?; let mut flags = FdFlag::from_bits(fcntl(read_fd, FcntlArg::F_GETFL)?).unwrap(); - flags |= FdFlag::FD_CLOEXEC; + flags |= FdFlag::FD_CLOEXEC | FdFlag::O_NONBLOCK; fcntl(read_fd, FcntlArg::F_SETFD(flags))?; let mut flags = FdFlag::from_bits(fcntl(write_fd, FcntlArg::F_GETFL)?).unwrap(); - flags |= FdFlag::FD_CLOEXEC; + flags |= FdFlag::FD_CLOEXEC | FdFlag::O_NONBLOCK; fcntl(write_fd, FcntlArg::F_SETFD(flags))?; Ok((read_fd, write_fd)) From ef4e2fc3e704fd4f33c04e4f3e51180fa8afee9d Mon Sep 17 00:00:00 2001 From: YangKeao Date: Mon, 9 May 2022 14:14:42 +0800 Subject: [PATCH 24/26] encapsulate set_flags function Signed-off-by: YangKeao --- src/addr_validate.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/addr_validate.rs b/src/addr_validate.rs index 9f60171f..b9ac8ced 100644 --- a/src/addr_validate.rs +++ b/src/addr_validate.rs @@ -21,19 +21,23 @@ fn create_pipe() -> nix::Result<(i32, i32)> { #[inline] #[cfg(target_os = "macos")] fn create_pipe() -> nix::Result<(i32, i32)> { - use nix::fcntl::{fcntl, FcntlArg, FdFlag}; + use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag}; use nix::unistd::pipe; + use std::os::unix::io::RawFd; + + fn set_flags(fd: RawFd) -> nix::Result<()> { + let mut flags = FdFlag::from_bits(fcntl(fd, FcntlArg::F_GETFD)?).unwrap(); + flags |= FdFlag::FD_CLOEXEC; + fcntl(fd, FcntlArg::F_SETFD(flags))?; + let mut flags = OFlag::from_bits(fcntl(fd, FcntlArg::F_GETFL)?).unwrap(); + flags |= OFlag::O_NONBLOCK; + fcntl(fd, FcntlArg::F_SETFL(flags))?; + Ok(()) + }; let (read_fd, write_fd) = pipe()?; - - let mut flags = FdFlag::from_bits(fcntl(read_fd, FcntlArg::F_GETFL)?).unwrap(); - flags |= FdFlag::FD_CLOEXEC | FdFlag::O_NONBLOCK; - fcntl(read_fd, FcntlArg::F_SETFD(flags))?; - - let mut flags = FdFlag::from_bits(fcntl(write_fd, FcntlArg::F_GETFL)?).unwrap(); - flags |= FdFlag::FD_CLOEXEC | FdFlag::O_NONBLOCK; - fcntl(write_fd, FcntlArg::F_SETFD(flags))?; - + set_flags(read_fd)?; + set_flags(write_fd)?; Ok((read_fd, write_fd)) } From c40885482ded07ca576a93a554e6eaf41d0315c5 Mon Sep 17 00:00:00 2001 From: YangKeao Date: Mon, 9 May 2022 14:21:42 +0800 Subject: [PATCH 25/26] support aarch64 macos Signed-off-by: YangKeao --- src/addr_validate.rs | 2 +- src/backtrace/frame_pointer.rs | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/addr_validate.rs b/src/addr_validate.rs index b9ac8ced..10dc27c4 100644 --- a/src/addr_validate.rs +++ b/src/addr_validate.rs @@ -33,7 +33,7 @@ fn create_pipe() -> nix::Result<(i32, i32)> { flags |= OFlag::O_NONBLOCK; fcntl(fd, FcntlArg::F_SETFL(flags))?; Ok(()) - }; + } let (read_fd, write_fd) = pipe()?; set_flags(read_fd)?; diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index ccee4ba1..48615571 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -64,7 +64,15 @@ impl super::Trace for Trace { #[cfg(all(target_arch = "aarch64", target_os = "linux"))] let frame_pointer = unsafe { (*ucontext).uc_mcontext.regs[29] as usize }; - // TODO: support arm64 on macos + #[cfg(all(target_arch = "aarch64", target_os = "macos"))] + let frame_pointer = unsafe { + let mcontext = (*ucontext).uc_mcontext; + if mcontext.is_null() { + 0 + } else { + (*mcontext).__ss.__fp as usize + } + }; let mut frame_pointer = frame_pointer as *mut FramePointerLayout; From 3214fc1c0e199cbec5450511eea39faee0bf014d Mon Sep 17 00:00:00 2001 From: YangKeao Date: Mon, 9 May 2022 15:19:14 +0800 Subject: [PATCH 26/26] remove addr_validate conditional flag Signed-off-by: YangKeao --- src/backtrace/frame_pointer.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/backtrace/frame_pointer.rs b/src/backtrace/frame_pointer.rs index 48615571..1810e4a0 100644 --- a/src/backtrace/frame_pointer.rs +++ b/src/backtrace/frame_pointer.rs @@ -4,7 +4,6 @@ use std::ptr::null_mut; use libc::c_void; -#[cfg(target_os = "linux")] use crate::addr_validate::validate; #[derive(Clone, Debug)] @@ -92,7 +91,6 @@ impl super::Trace for Trace { break; } - #[cfg(target_os = "linux")] if !validate(frame_pointer as *const libc::c_void) { break; }