-
Notifications
You must be signed in to change notification settings - Fork 108
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* basic framepointer backtrace Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * implement the framepointer unwind and check Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * support aarch64 on linux Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * fix struct layout representation Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * fix addr to frame_pointer Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * format the codes Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * add back lib.rs Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * initialize the last_frame_pointer with a proper value Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * fix cargo clippy Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * fix the compilation on arm Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * only build frame pointer with nightly toolchain Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * only compile on ubuntu-latest Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * add changelog Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * add a addr validator through pipe Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * add benchmark for addr_validate Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * fine tune the clippy Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * only allow frame pointer in linux Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * extend the check length Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * only validate on linux, also build on macos Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * fix according to comments Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * fix grammar Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * build on macos Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * add NON_BLOCK for the pipe in macos Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * encapsulate set_flags function Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * support aarch64 macos Signed-off-by: YangKeao <yangkeao@chunibyo.icu> * remove addr_validate conditional flag Signed-off-by: YangKeao <yangkeao@chunibyo.icu>
- Loading branch information
Showing
12 changed files
with
433 additions
and
31 deletions.
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, | ||
} | ||
} | ||
}); | ||
|
||
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) { | ||
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.