New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unwind through frame pointer #116
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
c59e11e
basic framepointer backtrace
YangKeao 2639a57
implement the framepointer unwind and check
YangKeao 5479357
support aarch64 on linux
YangKeao 5bf4384
fix struct layout representation
YangKeao 4a5be7b
fix addr to frame_pointer
YangKeao 833b925
format the codes
YangKeao ed70355
add back lib.rs
YangKeao 6197f2e
initialize the last_frame_pointer with a proper value
YangKeao 825b61d
fix cargo clippy
YangKeao 1b2014a
fix the compilation on arm
YangKeao bf99a44
only build frame pointer with nightly toolchain
YangKeao 23c10cf
only compile on ubuntu-latest
YangKeao a29259f
add changelog
YangKeao 708d1df
add a addr validator through pipe
YangKeao e0ebabb
add benchmark for addr_validate
YangKeao 93e9f6b
fine tune the clippy
YangKeao c212ffa
only allow frame pointer in linux
YangKeao 95fd25e
extend the check length
YangKeao e7228d2
only validate on linux, also build on macos
YangKeao 004a56e
fix according to comments
YangKeao 7f21f1d
Merge remote-tracking branch 'upstream/master' into frame-pointer
YangKeao 6a177ec
fix grammar
YangKeao ecc540e
build on macos
YangKeao 95bd349
add NON_BLOCK for the pipe in macos
YangKeao ef4e2fc
encapsulate set_flags function
YangKeao c408854
support aarch64 macos
YangKeao 3214fc1
remove addr_validate conditional flag
YangKeao File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
use std::{cell::RefCell, mem::size_of}; | ||
|
||
use nix::{ | ||
errno::Errno, | ||
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) | ||
} | ||
|
||
#[inline] | ||
#[cfg(target_os = "macos")] | ||
fn create_pipe() -> nix::Result<(i32, i32)> { | ||
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()?; | ||
set_flags(read_fd)?; | ||
set_flags(write_fd)?; | ||
Ok((read_fd, write_fd)) | ||
} | ||
|
||
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) = create_pipe()?; | ||
|
||
pipes[0] = read_fd; | ||
pipes[1] = write_fd; | ||
|
||
Ok(()) | ||
}) | ||
} | ||
|
||
pub fn validate(addr: *const libc::c_void) -> bool { | ||
const CHECK_LENGTH: usize = 2 * size_of::<*const libc::c_void>() / size_of::<u8>(); | ||
|
||
// read data in the pipe | ||
let valid_read = MEM_VALIDATE_PIPE.with(|pipes| { | ||
let pipes = pipes.borrow(); | ||
loop { | ||
let mut buf = [0u8; CHECK_LENGTH]; | ||
|
||
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, | ||
} | ||
} | ||
}); | ||
YangKeao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if !valid_read && open_pipe().is_err() { | ||
return false; | ||
} | ||
|
||
MEM_VALIDATE_PIPE.with(|pipes| { | ||
let pipes = pipes.borrow(); | ||
loop { | ||
let buf = unsafe { std::slice::from_raw_parts(addr as *const u8, CHECK_LENGTH) }; | ||
|
||
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
impl super::Frame for backtrace::Frame { | ||
type S = backtrace::Symbol; | ||
|
||
fn ip(&self) -> usize { | ||
self.ip() as usize | ||
} | ||
|
||
fn resolve_symbol<F: FnMut(&Self::S)>(&self, cb: F) { | ||
backtrace::resolve_frame(self, cb); | ||
} | ||
|
||
fn symbol_address(&self) -> *mut libc::c_void { | ||
self.symbol_address() | ||
} | ||
} | ||
|
||
pub struct Trace {} | ||
|
||
impl super::Trace for Trace { | ||
type Frame = backtrace::Frame; | ||
|
||
fn trace<F: FnMut(&Self::Frame) -> bool>(_: *mut libc::c_void, cb: F) { | ||
unsafe { backtrace::trace_unsynchronized(cb) } | ||
} | ||
} | ||
|
||
pub use backtrace::Frame; | ||
pub use backtrace::Symbol; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
// Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. | ||
|
||
use std::ptr::null_mut; | ||
|
||
use libc::c_void; | ||
|
||
use crate::addr_validate::validate; | ||
|
||
#[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 ip(&self) -> usize { | ||
self.ip | ||
} | ||
|
||
fn resolve_symbol<F: FnMut(&Self::S)>(&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 struct Trace {} | ||
impl super::Trace for Trace { | ||
type Frame = Frame; | ||
|
||
fn trace<F: FnMut(&Self::Frame) -> bool>(ucontext: *mut libc::c_void, mut cb: F) { | ||
YangKeao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 = | ||
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 { | ||
let mcontext = (*ucontext).uc_mcontext; | ||
if mcontext.is_null() { | ||
0 | ||
} else { | ||
(*mcontext).__ss.__rbp as usize | ||
} | ||
}; | ||
|
||
#[cfg(all(target_arch = "aarch64", target_os = "linux"))] | ||
let frame_pointer = unsafe { (*ucontext).uc_mcontext.regs[29] as usize }; | ||
|
||
#[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; | ||
|
||
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 | ||
// 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 !last_frame_pointer.is_null() && 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 | ||
let frame = Frame { | ||
ip: unsafe { (*frame_pointer).ret }, | ||
}; | ||
|
||
if !cb(&frame) { | ||
break; | ||
} | ||
frame_pointer = unsafe { (*frame_pointer).frame_pointer }; | ||
} | ||
} | ||
} | ||
|
||
#[repr(C)] | ||
struct FramePointerLayout { | ||
frame_pointer: *mut FramePointerLayout, | ||
ret: usize, | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, addr validation is only enabled on Linux. But there is an implementation for macos?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops. It's a mistake. I'll remove the conditional flag on addr validate.