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}