tracefp/
lib.rs

1//! # tracefp
2//!
3//! A stack backtracking library based on frame-pointer.
4//!
5//! # Requirements
6//!
7//! When compiling your project, set the following environment variables:
8//!
9//! - `CFLAGS` += `-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer`
10//! - `CXXFLAGS` += `-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer`
11//! - `RUSTFLAGS` += `-Cforce-frame-pointers=yes`
12//!
13//! And add the following parameters to `cargo build`:
14//!
15//! ```shell
16//! rustup component add rust-src
17//! cargo build -Z build-std --target x86_64-unknown-linux-gnu
18//! ```
19//!
20//! Where `x86_64-unknown-linux-gnu` can be replaced with other values, and `build-std` currently only supports nightly Rust.
21//!
22//! > NOTE: When you're using this library on **macOS + aarch64**, you don't need to do anything, as all libraries for this platform turn on frame-pointer by default.
23//!
24//! # Examples
25//!
26//! ## Stack backtrace
27//!
28//! ```rust
29//! fn main() {
30//!     func1_inlined();
31//! }
32//!
33//! #[inline(always)]
34//! fn func1_inlined() {
35//!     func2()
36//! }
37//!
38//! fn func2() {
39//!     tracefp::trace(|pc| {
40//!         println!("{:#x}", pc);
41//!         backtrace::resolve(pc as _, |s| {
42//!             println!("    {:?}", s.name());
43//!         });
44//!         true
45//!     });
46//! }
47//! ```
48//!
49//! Sample output:
50//!
51//! ```text
52//! 0x0
53//! 0x100d7348b
54//!     Some(hello::func2::h53002ef4ebe4d7d7)
55//! 0x100d73337
56//!     Some(hello::func1_inlined::h30751d2ee2774466)
57//!     Some(hello::main::h994e0b3179971102)
58//! 0x100d72ddf
59//!     Some(core::ops::function::FnOnce::call_once::h3dec9d79421d8d27)
60//! 0x100d71723
61//!     Some(std::sys_common::backtrace::__rust_begin_short_backtrace::h9755a7454510e50f)
62//! 0x100d716db
63//!     Some(std::rt::lang_start::{{closure}}::ha86392d061932837)
64//! 0x100e4065f
65//!     Some(core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once::h8eb3ac20f80eabfa)
66//!     Some(std::panicking::try::do_call::ha6ddf2c638427188)
67//!     Some(std::panicking::try::hda8741de507c1ad0)
68//!     Some(std::panic::catch_unwind::h82424a01f258bd39)
69//!     Some(std::rt::lang_start_internal::{{closure}}::h67e296ed5b030b7b)
70//!     Some(std::panicking::try::do_call::hd3dd7e7e10f6424e)
71//!     Some(std::panicking::try::ha0a7bd8122e3fb7c)
72//!     Some(std::panic::catch_unwind::h809b0e1092e9475d)
73//!     Some(std::rt::lang_start_internal::h358b6d58e23c88c7)
74//! 0x100d716a3
75//!     Some(std::rt::lang_start::h1342399ebba7a37d)
76//! 0x100d734df
77//!     Some("_main")
78//! 0x101059087
79//! 0xd82e7fffffffffff
80//! ```
81//!
82//! ## Stack backtrace in signal handler
83//!
84//! ```rust
85//! use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, SIGPROF};
86//!
87//! fn main() {
88//!     // Register perf signal handler.
89//!     let h = SigHandler::SigAction(perf_signal_handler);
90//!     let a = SigAction::new(h, SaFlags::SA_SIGINFO, SigSet::empty());
91//!     unsafe {
92//!         sigaction(SIGPROF, &a).unwrap();
93//!     }
94//!
95//!     // Send a SIGPROF signal to the current process.
96//!     unsafe {
97//!         libc::kill(libc::getpid(), libc::SIGPROF);
98//!     }
99//!
100//!     // Block until the signal handler finishes executing.
101//!     loop {}
102//! }
103//!
104//! #[no_mangle]
105//! pub extern "C" fn perf_signal_handler(_: libc::c_int, _: *mut libc::siginfo_t, ucontext: *mut libc::c_void) {
106//!     tracefp::trace_from_ucontext(ucontext, |pc| {
107//!         println!("{:#x}", pc);
108//!         backtrace::resolve(pc as _, |s| {
109//!             println!("    {:?}", s.name());
110//!         });
111//!         true
112//!     });
113//!     std::process::exit(0);
114//! }
115//! ```
116//!
117//! Sample output:
118//!
119//! ```text
120//! 0x1c32e4824
121//!     Some("_thread_get_state")
122//! 0x100409093
123//!     Some(core::ops::function::FnOnce::call_once::h775fb44fbbe53d95)
124//! 0x1004090eb
125//!     Some(std::sys_common::backtrace::__rust_begin_short_backtrace::h3acd0b11747c5033)
126//! 0x100408a2b
127//!     Some(std::rt::lang_start::{{closure}}::hf7b77a4d60d2f840)
128//! 0x1004d7faf
129//!     Some(core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once::h8eb3ac20f80eabfa)
130//!     Some(std::panicking::try::do_call::ha6ddf2c638427188)
131//!     Some(std::panicking::try::hda8741de507c1ad0)
132//!     Some(std::panic::catch_unwind::h82424a01f258bd39)
133//!     Some(std::rt::lang_start_internal::{{closure}}::h67e296ed5b030b7b)
134//!     Some(std::panicking::try::do_call::hd3dd7e7e10f6424e)
135//!     Some(std::panicking::try::ha0a7bd8122e3fb7c)
136//!     Some(std::panic::catch_unwind::h809b0e1092e9475d)
137//!     Some(std::rt::lang_start_internal::h358b6d58e23c88c7)
138//! 0x1004089f3
139//!     Some(std::rt::lang_start::hd321e36029dcfdd2)
140//! 0x1004093d7
141//!     Some("_main")
142//! 0x1007bd087
143//! 0x921a7fffffffffff
144//! ```
145
146/// Inspects the current call-stack, passing all active PCs into the closure
147/// provided to calculate a stack trace.
148///
149/// The closure's return value is an indication of whether the backtrace should
150/// continue. A return value of `false` will terminate the backtrace and return
151/// immediately.
152pub fn trace<F>(f: F)
153where
154    F: FnMut(u64) -> bool,
155{
156    let mut ucontext: libc::ucontext_t = unsafe { std::mem::zeroed() };
157    #[cfg(target_os = "macos")]
158    {
159        let mut mcontext: libc::__darwin_mcontext64 = unsafe { std::mem::zeroed() };
160        ucontext.uc_mcontext = &mut mcontext as *mut libc::__darwin_mcontext64;
161    }
162    let ucontext = &mut ucontext as *mut libc::ucontext_t as *mut libc::c_void;
163    unsafe {
164        if getcontext(ucontext) != 0 {
165            return;
166        }
167    }
168    trace_from_ucontext(ucontext, f)
169}
170
171/// Inspects the call-stack from `ucontext`, passing all active PCs into the closure
172/// provided to calculate a stack trace.
173///
174/// The closure's return value is an indication of whether the backtrace should
175/// continue. A return value of `false` will terminate the backtrace and return
176/// immediately.
177pub fn trace_from_ucontext<F>(ucontext: *mut libc::c_void, mut f: F)
178where
179    F: FnMut(u64) -> bool,
180{
181    let Registers { mut pc, mut fp } = match Registers::from_ucontext(ucontext) {
182        Some(v) => v,
183        None => return,
184    };
185    if !f(pc) {
186        return;
187    }
188    while fp != 0 {
189        pc = match load::<u64>(fp + 8) {
190            Some(v) => v,
191            None => return,
192        };
193        pc -= 1;
194        if !f(pc) {
195            return;
196        }
197        fp = match load::<u64>(fp) {
198            Some(v) => v,
199            None => return,
200        };
201    }
202}
203
204extern "C" {
205    // getcontext() in libc.
206    //
207    // We declare here instead of using `libc::getcontext()` directly because
208    // `libc::getcontext()` is not found on macOS.
209    fn getcontext(_ucontext: *mut libc::c_void) -> libc::c_int;
210}
211
212// Register context for stack backtracking.
213#[derive(Debug, Copy, Clone)]
214struct Registers {
215    pc: u64,
216    fp: u64,
217}
218
219impl Registers {
220    #[cfg(all(target_arch = "x86_64", target_os = "linux"))]
221    fn from_ucontext(ucontext: *mut libc::c_void) -> Option<Self> {
222        let ucontext = ucontext as *mut libc::ucontext_t;
223        if ucontext.is_null() {
224            return None;
225        }
226        let mcontext = unsafe { (*ucontext).uc_mcontext };
227        Some(Self {
228            pc: mcontext.gregs[libc::REG_RIP as usize] as u64,
229            fp: mcontext.gregs[libc::REG_RBP as usize] as u64,
230        })
231    }
232
233    #[cfg(all(target_arch = "x86_64", target_os = "macos"))]
234    fn from_ucontext(ucontext: *mut libc::c_void) -> Option<Self> {
235        let ucontext = ucontext as *mut libc::ucontext_t;
236        if ucontext.is_null() {
237            return None;
238        }
239        unsafe {
240            let mcontext = (*ucontext).uc_mcontext;
241            if mcontext.is_null() {
242                return None;
243            }
244            Some(Self {
245                pc: (*mcontext).__ss.__rip,
246                fp: (*mcontext).__ss.__rbx,
247            })
248        }
249    }
250
251    #[cfg(all(target_arch = "aarch64", target_os = "linux"))]
252    fn from_ucontext(ucontext: *mut libc::c_void) -> Option<Self> {
253        let ucontext = ucontext as *mut libc::ucontext_t;
254        if ucontext.is_null() {
255            return None;
256        }
257        let mcontext = unsafe { (*ucontext).uc_mcontext };
258        Some(Self {
259            pc: mcontext.pc,
260            fp: mcontext.regs[29],
261        })
262    }
263
264    #[cfg(all(target_arch = "aarch64", target_os = "macos"))]
265    fn from_ucontext(ucontext: *mut libc::c_void) -> Option<Self> {
266        let ucontext = ucontext as *mut libc::ucontext_t;
267        if ucontext.is_null() {
268            return None;
269        }
270        unsafe {
271            let mcontext = (*ucontext).uc_mcontext;
272            if mcontext.is_null() {
273                return None;
274            }
275            Some(Self {
276                pc: (*mcontext).__ss.__pc,
277                fp: (*mcontext).__ss.__fp,
278            })
279        }
280    }
281}
282
283// Load the value at the `address`.
284//
285// Note that although `load` is not unsafe, it is implemented by unsafe
286// internally and simply attempts to read the specified address. So the
287// correctness of the address needs to be guaranteed by the caller.
288#[inline]
289#[cfg(not(feature = "memory-access-check"))]
290fn load<T: Copy>(address: u64) -> Option<T> {
291    unsafe { Some(*(address as *const T)) }
292}
293
294// Load the value at the `address`.
295//
296// A memory accessibility check will be performed before accessing the
297// target address.
298#[inline]
299#[cfg(feature = "memory-access-check")]
300fn load<T: Copy>(address: u64) -> Option<T> {
301    if access_check::can_access(address) {
302        unsafe { Some(*(address as *const T)) }
303    } else {
304        None
305    }
306}
307
308#[cfg(feature = "memory-access-check")]
309mod access_check {
310    use std::mem::MaybeUninit;
311
312    thread_local! {
313        static CAN_ACCESS_PIPE: [libc::c_int; 2] = {
314            unsafe {
315                let mut fds = MaybeUninit::<[libc::c_int; 2]>::uninit();
316                let res = create_pipe(fds.as_mut_ptr() as *mut libc::c_int);
317                if res == 0 {
318                    [fds.assume_init()[0], fds.assume_init()[1]]
319                } else {
320                    [-1, -1]
321                }
322            }
323        };
324    }
325
326    /// Check whether the target address is valid.
327    pub fn can_access(address: u64) -> bool {
328        CAN_ACCESS_PIPE.with(|pipes| unsafe {
329            // The pipe initialization failed at that time.
330            if pipes[0] == -1 || pipes[1] == -1 {
331                return false;
332            }
333            // Clear data that already exists in the pipe.
334            let mut buffer = [0u8; 8];
335            let can_read = loop {
336                let size = libc::read(pipes[0], buffer.as_mut_ptr() as _, buffer.len() as _);
337                if size == -1 {
338                    match errno() {
339                        libc::EINTR => continue,
340                        libc::EAGAIN => break true,
341                        _ => break false,
342                    }
343                } else if size > 0 {
344                    break true;
345                }
346            };
347            if !can_read {
348                return false;
349            }
350            // Try to write "data" to the pipe, let the kernel access the address, if
351            // the address is invalid, we will fail the write.
352            loop {
353                let size = libc::write(pipes[1], address as _, 1);
354                if size == -1 {
355                    match errno() {
356                        libc::EINTR => continue,
357                        libc::EAGAIN => break true,
358                        _ => break false,
359                    }
360                } else if size > 0 {
361                    break true;
362                }
363            }
364        })
365    }
366
367    #[inline]
368    #[cfg(target_os = "linux")]
369    unsafe fn create_pipe(fds: *mut libc::c_int) -> libc::c_int {
370        libc::pipe2(fds, libc::O_CLOEXEC | libc::O_NONBLOCK)
371    }
372
373    #[cfg(target_os = "macos")]
374    unsafe fn create_pipe(fds: *mut libc::c_int) -> libc::c_int {
375        let res = libc::pipe(fds);
376        if res != 0 {
377            return res;
378        }
379        let fds = fds as *mut [libc::c_int; 2];
380        for n in 0..2 {
381            let mut flags = libc::fcntl((*fds)[n], libc::F_GETFD);
382            flags |= libc::O_CLOEXEC;
383            let res = libc::fcntl((*fds)[n], libc::F_SETFD, flags);
384            if res != 0 {
385                return res;
386            }
387            let mut flags = libc::fcntl((*fds)[n], libc::F_GETFL);
388            flags |= libc::O_NONBLOCK;
389            let res = libc::fcntl((*fds)[n], libc::F_SETFL, flags);
390            if res != 0 {
391                return res;
392            }
393        }
394        0
395    }
396
397    #[inline]
398    #[cfg(target_os = "linux")]
399    fn errno() -> libc::c_int {
400        unsafe { (*libc::__errno_location()) as libc::c_int }
401    }
402
403    #[inline]
404    #[cfg(target_os = "macos")]
405    fn errno() -> libc::c_int {
406        unsafe { (*libc::__error()) as libc::c_int }
407    }
408
409    #[cfg(test)]
410    mod tests {
411        use super::*;
412
413        #[test]
414        fn test_can_access() {
415            let v1 = 1;
416            let v2 = Box::new(1);
417            assert!(can_access(&v1 as *const i32 as u64));
418            assert!(can_access(v2.as_ref() as *const i32 as u64));
419            assert!(!can_access(0));
420            assert!(!can_access(u64::MAX));
421        }
422    }
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428
429    #[test]
430    fn test_load() {
431        let val = i8::MIN;
432        let loc = &val as *const i8 as u64;
433        assert_eq!(load::<i8>(loc), Some(val));
434        let val = u64::MAX;
435        let loc = &val as *const u64 as u64;
436        assert_eq!(load::<u64>(loc), Some(val));
437    }
438}