unwind/lib.rs
1//! An interface to [libunwind].
2//!
3//! libunwind provides access to the call chain of a process. It supports both local and remote
4//! processes.
5//!
6//! # Examples
7//!
8//! Printing a backtrace of the current thread:
9//!
10//! ```
11//! use unwind::{Cursor, RegNum, get_context};
12//!
13//! get_context!(context);
14//! let mut cursor = Cursor::local(context).unwrap();
15//!
16//! loop {
17//! let ip = cursor.register(RegNum::IP).unwrap();
18//!
19//! match (cursor.procedure_info(), cursor.procedure_name()) {
20//! (Ok(ref info), Ok(ref name)) if ip == info.start_ip() + name.offset() => {
21//! println!(
22//! "{:#016x} - {} ({:#016x}) + {:#x}",
23//! ip,
24//! name.name(),
25//! info.start_ip(),
26//! name.offset()
27//! );
28//! }
29//! _ => println!("{:#016x} - ????", ip),
30//! }
31//!
32//! if !cursor.step().unwrap() {
33//! break;
34//! }
35//! }
36//! ```
37//!
38//! [libunwind]: http://www.nongnu.org/libunwind/
39#![doc(html_root_url = "https://sfackler.github.io/rstack/doc")]
40#![warn(missing_docs)]
41
42use foreign_types::Opaque;
43#[cfg(feature = "ptrace")]
44use foreign_types::{foreign_type, ForeignType};
45use libc::{c_char, c_int, c_void};
46use std::error;
47use std::ffi::CStr;
48use std::fmt;
49use std::marker::PhantomData;
50use std::marker::PhantomPinned;
51use std::mem::MaybeUninit;
52use std::ops::{Deref, DerefMut};
53use std::pin::Pin;
54use std::result;
55use unwind_sys::*;
56
57#[doc(hidden)]
58pub mod private {
59 pub use std::mem::MaybeUninit;
60 pub use std::pin::Pin;
61 pub use unwind_sys::unw_tdep_getcontext;
62}
63
64/// The result type returned by functions in this crate.
65pub type Result<T> = result::Result<T, Error>;
66
67/// An error returned from libunwind.
68#[derive(Copy, Clone, Debug, PartialEq, Eq)]
69pub struct Error(c_int);
70
71impl Error {
72 /// Unspecified error.
73 pub const UNSPEC: Error = Error(-UNW_EUNSPEC);
74
75 /// Out of memory.
76 pub const NOMEM: Error = Error(-UNW_ENOMEM);
77
78 /// Bad register number.
79 pub const BADREG: Error = Error(-UNW_EBADREG);
80
81 /// Attempt to write read-only register.
82 pub const READONLYREG: Error = Error(-UNW_EREADONLYREG);
83
84 /// Stop unwinding.
85 pub const STOPUNWIND: Error = Error(-UNW_ESTOPUNWIND);
86
87 /// Invalid IP.
88 pub const INVALIDIP: Error = Error(-UNW_EINVALIDIP);
89
90 /// Bad frame.
91 pub const BADFRAME: Error = Error(-UNW_EBADFRAME);
92
93 /// Unsupported operation or bad value.
94 pub const INVAL: Error = Error(-UNW_EINVAL);
95
96 /// Unwind info has unsupported value.
97 pub const BADVERSION: Error = Error(-UNW_EBADVERSION);
98
99 /// No unwind info found.
100 pub const NOINFO: Error = Error(-UNW_ENOINFO);
101}
102
103impl fmt::Display for Error {
104 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
105 unsafe {
106 let err = unw_strerror(self.0);
107 let err = CStr::from_ptr(err).to_string_lossy();
108 fmt.write_str(&err)
109 }
110 }
111}
112
113impl error::Error for Error {}
114
115/// The byteorder of an address space.
116#[derive(Copy, Clone)]
117pub struct Byteorder(c_int);
118
119impl Byteorder {
120 /// The default byte order of the unwind target.
121 pub const DEFAULT: Byteorder = Byteorder(0);
122
123 /// Little endian.
124 pub const LITTLE_ENDIAN: Byteorder = Byteorder(1234);
125
126 /// Big endian.
127 pub const BIG_ENDIAN: Byteorder = Byteorder(4321);
128
129 /// PDP endian.
130 pub const PDP_ENDIAN: Byteorder = Byteorder(3412);
131}
132
133#[cfg(feature = "ptrace")]
134foreign_type! {
135 /// The unwind state used by the ptrace accessors.
136 ///
137 /// The `ptrace` Cargo feature must be enabled to use this type.
138 pub unsafe type PTraceState {
139 type CType = c_void;
140 fn drop = _UPT_destroy;
141 }
142}
143
144#[cfg(feature = "ptrace")]
145impl PTraceState {
146 /// Constructs a new `PTraceState` for the specified PID.
147 ///
148 /// The process must already be attached and suspended before unwinding can be performed.
149 pub fn new(pid: u32) -> Result<PTraceState> {
150 unsafe {
151 let ptr = _UPT_create(pid as _);
152 if ptr.is_null() {
153 // this is documented to only fail on OOM
154 Err(Error(-UNW_ENOMEM))
155 } else {
156 Ok(PTraceState::from_ptr(ptr))
157 }
158 }
159 }
160}
161
162/// A collection of functions used to unwind an arbitrary process.
163pub struct Accessors<T>(unw_accessors_t, PhantomData<T>);
164
165#[cfg(feature = "ptrace")]
166impl Accessors<PTraceStateRef> {
167 /// Returns `Accessors` which use the ptrace system call to unwind a remote process.
168 ///
169 /// The `ptrace` Cargo feature must be enabled to use this type.
170 pub fn ptrace() -> &'static Accessors<PTraceStateRef> {
171 unsafe { &*(&_UPT_accessors as *const unw_accessors_t as *const Accessors<PTraceStateRef>) }
172 }
173}
174
175/// An address space upon which unwinding can be performed.
176pub struct AddressSpace<T>(unw_addr_space_t, PhantomData<T>);
177
178impl<T> Drop for AddressSpace<T> {
179 fn drop(&mut self) {
180 unsafe {
181 unw_destroy_addr_space(self.0);
182 }
183 }
184}
185
186impl<T> Deref for AddressSpace<T> {
187 type Target = AddressSpaceRef<T>;
188
189 fn deref(&self) -> &AddressSpaceRef<T> {
190 unsafe { &*(self.0 as *const AddressSpaceRef<T>) }
191 }
192}
193
194impl<T> DerefMut for AddressSpace<T> {
195 fn deref_mut(&mut self) -> &mut AddressSpaceRef<T> {
196 unsafe { &mut *(self.0 as *mut AddressSpaceRef<T>) }
197 }
198}
199
200impl<T> AddressSpace<T> {
201 /// Creates a new `AddressSpace`.
202 pub fn new(accessors: &Accessors<T>, byteorder: Byteorder) -> Result<AddressSpace<T>> {
203 unsafe {
204 let ptr = unw_create_addr_space(
205 &accessors.0 as *const unw_accessors_t as *mut unw_accessors_t,
206 byteorder.0,
207 );
208 if ptr.is_null() {
209 Err(Error(-UNW_EUNSPEC))
210 } else {
211 Ok(AddressSpace(ptr, PhantomData))
212 }
213 }
214 }
215}
216
217/// A borrowed reference to an [`AddressSpace`].
218///
219/// [`AddressSpace`]: struct.AddressSpace.html
220pub struct AddressSpaceRef<T>(Opaque, PhantomData<T>);
221
222impl<T> AddressSpaceRef<T> {
223 fn as_ptr(&self) -> unw_addr_space_t {
224 self as *const _ as *mut _
225 }
226}
227
228/// An identifier of a processor register.
229#[derive(Copy, Clone)]
230pub struct RegNum(c_int);
231
232impl RegNum {
233 /// A generic identifier for the register storing the instruction pointer.
234 pub const IP: RegNum = RegNum(UNW_REG_IP);
235
236 /// A generic identifier for the register storing the stack pointer.
237 pub const SP: RegNum = RegNum(UNW_REG_SP);
238}
239
240#[cfg(not(pre16))]
241#[cfg(target_arch = "x86_64")]
242mod x86_64;
243
244/// Information about a procedure.
245#[derive(Copy, Clone)]
246pub struct ProcedureInfo {
247 start_ip: u64,
248 end_ip: u64,
249}
250
251impl ProcedureInfo {
252 /// Returns the starting address of the procedure.
253 pub fn start_ip(&self) -> u64 {
254 self.start_ip
255 }
256
257 /// Returns the ending address of the procedure.
258 pub fn end_ip(&self) -> u64 {
259 self.end_ip
260 }
261}
262
263/// The name of a procedure.
264#[derive(Clone)]
265pub struct ProcedureName {
266 name: String,
267 offset: u64,
268}
269
270impl ProcedureName {
271 /// Returns the name of the procedure.
272 pub fn name(&self) -> &str {
273 &self.name
274 }
275
276 /// Returns the offset of the frame's instruction pointer from the starting address of the named
277 /// procedure.
278 pub fn offset(&self) -> u64 {
279 self.offset
280 }
281}
282
283/// A snapshot of the machine-state of a process.
284///
285/// A pinned context can be created with the `get_context!` macro.
286pub struct Context(#[doc(hidden)] pub unw_context_t, PhantomPinned);
287
288/// Creates a `Context` pinned to the stack.
289///
290/// This is a macro rather than a function due to the implementation of the libunwind library.
291///
292/// # Example
293///
294/// ```
295/// # use unwind::{get_context, Context};
296/// # use std::pin::Pin;
297///
298/// get_context!(context);
299/// let _: Pin<&mut Context> = context;
300/// ```
301#[macro_export]
302macro_rules! get_context {
303 ($name:ident) => {
304 // This is implemented using the same strategy as futures::pin_mut where the Pin shadows the original value,
305 // preventing it from being referenced and therefore moved.
306 let mut $name = $crate::private::MaybeUninit::<$crate::Context>::uninit();
307 unsafe {
308 $crate::private::unw_tdep_getcontext!(&mut (*$name.as_mut_ptr()).0);
309 }
310 let $name = unsafe { $crate::private::Pin::new_unchecked(&mut *$name.as_mut_ptr()) };
311 };
312}
313
314/// A cursor into a frame of a stack.
315///
316/// The cursor starts at the current (topmost) frame, and can be advanced downwards through the
317/// stack. While a cursor cannot be run "backwards", it can be cloned, and one of the copies
318/// advanced while the other continues to refer to the previous frame.
319#[derive(Clone)]
320pub struct Cursor<'a>(unw_cursor_t, PhantomData<&'a ()>);
321
322impl<'a> Cursor<'a> {
323 /// Creates a cursor over the stack of the calling thread.
324 pub fn local(context: Pin<&'a mut Context>) -> Result<Cursor<'a>> {
325 unsafe {
326 let mut cursor = MaybeUninit::uninit();
327 let ret = unw_init_local(cursor.as_mut_ptr(), &mut context.get_unchecked_mut().0);
328 if ret != UNW_ESUCCESS {
329 return Err(Error(ret));
330 }
331
332 Ok(Cursor(cursor.assume_init(), PhantomData))
333 }
334 }
335
336 /// Creates a cursor over the stack of a "remote" process.
337 pub fn remote<T>(address_space: &'a AddressSpaceRef<T>, state: &'a T) -> Result<Cursor<'a>> {
338 unsafe {
339 let mut cursor = MaybeUninit::uninit();
340 let ret = unw_init_remote(
341 cursor.as_mut_ptr(),
342 address_space.as_ptr(),
343 state as *const T as *mut c_void,
344 );
345 if ret == UNW_ESUCCESS {
346 Ok(Cursor(cursor.assume_init(), PhantomData))
347 } else {
348 Err(Error(ret))
349 }
350 }
351 }
352
353 /// Steps the cursor into the next older stack frame.
354 ///
355 /// A return value of `false` indicates that the cursor is at the last frame of the stack.
356 pub fn step(&mut self) -> Result<bool> {
357 unsafe {
358 // libunwind 1.1 seems to get confused and walks off the end of the stack. The last IP
359 // it reports is 0, so we'll stop if we're there.
360 if cfg!(pre12) && self.register(RegNum::IP).unwrap_or(1) == 0 {
361 return Ok(false);
362 }
363
364 let ret = unw_step(&mut self.0);
365 if ret > 0 {
366 Ok(true)
367 } else if ret == 0 {
368 Ok(false)
369 } else {
370 Err(Error(ret))
371 }
372 }
373 }
374
375 /// Returns the value of an integral register at the current frame.
376 ///
377 /// Based on the calling convention, some registers may not be available in a stack frame.
378 pub fn register(&mut self, num: RegNum) -> Result<u64> {
379 unsafe {
380 let mut val = 0;
381 let ret = unw_get_reg(&self.0 as *const _ as *mut _, num.0, &mut val);
382 if ret == UNW_ESUCCESS {
383 Ok(val as u64)
384 } else {
385 Err(Error(ret))
386 }
387 }
388 }
389
390 /// Returns information about the procedure at the current frame.
391 pub fn procedure_info(&mut self) -> Result<ProcedureInfo> {
392 unsafe {
393 let mut info = MaybeUninit::uninit();
394 let ret = unw_get_proc_info(&self.0 as *const _ as *mut _, info.as_mut_ptr());
395 if ret == UNW_ESUCCESS {
396 let info = info.assume_init();
397 Ok(ProcedureInfo {
398 start_ip: info.start_ip as u64,
399 end_ip: info.end_ip as u64,
400 })
401 } else {
402 Err(Error(ret))
403 }
404 }
405 }
406
407 /// Returns the name of the procedure of the current frame.
408 ///
409 /// The name is copied into the provided buffer, and is null-terminated. If the buffer is too
410 /// small to hold the full name, [`Error::NOMEM`] is returned and the buffer contains the
411 /// portion of the name that fits (including the null terminator).
412 ///
413 /// The offset of the instruction pointer from the beginning of the identified procedure is
414 /// copied into the `offset` parameter.
415 ///
416 /// The `procedure_name` method provides a higher level wrapper over this method.
417 ///
418 /// In certain contexts, particularly when the binary being unwound has been stripped, the
419 /// unwinder may not have enough information to properly identify the procedure and will simply
420 /// return the first label before the frame's instruction pointer. The offset will always be
421 /// relative to this label.
422 ///
423 /// [`Error::NOMEM`]: struct.Error.html#associatedconstant.NOMEM
424 pub fn procedure_name_raw(&mut self, buf: &mut [u8], offset: &mut u64) -> Result<()> {
425 unsafe {
426 let mut raw_off = 0;
427 let ret = unw_get_proc_name(
428 &self.0 as *const _ as *mut _,
429 buf.as_mut_ptr() as *mut c_char,
430 buf.len(),
431 &mut raw_off,
432 );
433 *offset = raw_off as u64;
434 if ret == UNW_ESUCCESS {
435 Ok(())
436 } else {
437 Err(Error(ret))
438 }
439 }
440 }
441
442 /// Returns the name of the procedure of the current frame.
443 ///
444 /// In certain contexts, particularly when the binary being unwound has been stripped, the
445 /// unwinder may not have enough information to properly identify the procedure and will simply
446 /// return the first label before the frame's instruction pointer. The offset will always be
447 /// relative to this label.
448 pub fn procedure_name(&mut self) -> Result<ProcedureName> {
449 let mut buf = vec![0; 256];
450 loop {
451 let mut offset = 0;
452 match self.procedure_name_raw(&mut buf, &mut offset) {
453 Ok(()) => {
454 let len = buf.iter().position(|b| *b == 0).unwrap();
455 buf.truncate(len);
456 let name = String::from_utf8_lossy(&buf).into_owned();
457 return Ok(ProcedureName { name, offset });
458 }
459 Err(Error::NOMEM) => {
460 let len = buf.len() * 2;
461 buf.resize(len, 0);
462 }
463 Err(e) => return Err(e),
464 }
465 }
466 }
467
468 /// Determines if the current frame is a signal frame.
469 ///
470 /// Signal frames are unique in several ways. More register state is available than normal, and
471 /// the instruction pointer references the currently executing instruction rather than the next
472 /// instruction.
473 pub fn is_signal_frame(&mut self) -> Result<bool> {
474 unsafe {
475 let ret = unw_is_signal_frame(&self.0 as *const _ as *mut _);
476 if ret < 0 {
477 Err(Error(ret))
478 } else {
479 Ok(ret != 0)
480 }
481 }
482 }
483}