libunwind_rs/
lib.rs

1//! #libunwind-rs
2//!
3//! `libunwind-rs`  is a library providing safe Rust API for retrieving backtrace from local and
4//! remote process, and also from coredumps. Crate is build on a top of [libunwind] library.
5//!
6//! [libunwind]: http://www.nongnu.org/libunwind/
7extern crate num_derive;
8
9use foreign_types::{foreign_type, ForeignType};
10use libc::{c_char, c_void};
11use libunwind_sys::*;
12use num_derive::FromPrimitive;
13use num_traits::FromPrimitive;
14use std::ffi::CStr;
15use std::ffi::CString;
16use std::fmt;
17use std::mem::MaybeUninit;
18use std::path::Path;
19
20/// Error codes.  The unwind routines return the *negated* values of
21/// these error codes on error and a non-negative value on success.
22#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)]
23pub enum Error {
24    ///no error
25    Succsess = 0,
26    /// unspecified (general) error
27    Unspec = -1,
28    /// out of memory
29    NoMem = -2,
30    /// bad register number
31    BadReg = -3,
32    /// attempt to write read-only register
33    ReadOnlyReg = -4,
34    /// stop unwinding
35    StopUnwind = -5,
36    /// invalid IP
37    InvalidIp = -6,
38    ///bad frame
39    BadFrame = -7,
40    /// unsupported operation or bad value
41    InVal = -8,
42    /// unwind info has unsupported version
43    BadVersion = -9,
44    /// no unwind info found
45    NoInfo = -10,
46}
47
48impl fmt::Display for Error {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        unsafe {
51            let e = CStr::from_ptr(unw_strerror(*self as i32));
52            write!(f, "{}", e.to_string_lossy())
53        }
54    }
55}
56
57///These are backend callback routines that provide access to the
58///state of a "remote" process.  This can be used, for example, to
59///unwind another process through the ptrace() interface.
60pub struct Accessors(unw_accessors_t);
61
62impl Accessors {
63    /// Method returns Accessors for ptrace()
64    #[cfg(feature = "ptrace")]
65    pub fn ptrace() -> &'static Accessors {
66        unsafe { &*(&_UPT_accessors as *const unw_accessors_t as *const Accessors) }
67    }
68    /// Method returns Accessors for coredump
69    pub fn coredump() -> &'static Accessors {
70        unsafe { &*(&raw const _UCD_accessors as *const unw_accessors_t as *const Accessors) }
71    }
72}
73///The endianness (byte order) of a stream of bytes
74pub enum Byteorder {
75    Default = 0,
76    LitleEndian = 1234,
77    BigEndian = 3214,
78    PdpEndian = 3412,
79}
80
81foreign_type! {
82/// Struct represents an address space of unwinding procces
83    pub unsafe type AddressSpace  {
84        type CType = libunwind_sys::unw_addr_space;
85        fn drop = unw_destroy_addr_space;
86    }
87}
88
89impl AddressSpace {
90    /// Method constructs `AddressSpace` from given accessors and byteorder
91    /// # Arguments
92    ///
93    /// * `accessors` - Bunch of Accessors functions (Ptrace, Coredump)
94    ///
95    /// * `byteorder` - Endianess  of target machine
96    pub fn new(accessors: &Accessors, byteorder: Byteorder) -> Result<AddressSpace, Error> {
97        unsafe {
98            let ptr = unw_create_addr_space(
99                &accessors.0 as *const unw_accessors_t as *mut unw_accessors_t,
100                byteorder as i32,
101            );
102            if ptr.is_null() {
103                Err(Error::Unspec)
104            } else {
105                Ok(AddressSpace::from_ptr(ptr))
106            }
107        }
108    }
109}
110
111foreign_type! {
112    ///This state is used by accessors
113    pub unsafe type CoredumpState {
114        type CType = libunwind_sys::UCD_info;
115        fn drop = _UCD_destroy;
116    }
117}
118
119impl CoredumpState {
120    /// Method constructs new CoredumpState from path to core file.
121    /// # Arguments
122    ///
123    /// * `accessors` - Bunch of Accessors functions (Ptrace, Coredump)
124    ///
125    /// * `byteorder` - Endianess  of target machine
126    pub fn new(core_path: &Path) -> Result<CoredumpState, Error> {
127        unsafe {
128            let core_path = CString::new(core_path.to_str().unwrap()).unwrap();
129            let ui = _UCD_create(core_path.as_ptr());
130            if ui.is_null() {
131                Err(Error::NoMem)
132            } else {
133                Ok(CoredumpState::from_ptr(ui))
134            }
135        }
136    }
137
138    /// Method returns current thread id
139    pub fn pid(&mut self) -> i32 {
140        unsafe { _UCD_get_pid(self.0.as_ptr()) }
141    }
142    /// Method returns  the number of threads
143    pub fn num_threads(&mut self) -> i32 {
144        unsafe { _UCD_get_num_threads(self.0.as_ptr()) }
145    }
146    /// Method selects thread by provided thread id
147    /// # Arguments
148    ///
149    /// * `id` - thread identifier
150    pub fn select_thread(&mut self, id: i32) {
151        unsafe {
152            _UCD_select_thread(self.0.as_ptr(), id);
153        }
154    }
155
156    /// Method gets value for memory address
157    /// # Arguments
158    ///
159    /// * `asp` - AddressSpace struct
160    ///
161    /// * `address` - memory address to access
162    pub fn access_mem(&mut self, asp: &AddressSpace, address: usize) -> Result<usize, Error> {
163        unsafe {
164            let mut val: unw_word_t = 0;
165            let ret = _UCD_access_mem(
166                asp.0.as_ptr(),
167                address as unw_word_t,
168                &mut val,
169                0,
170                self.0.as_ptr() as *mut libc::c_void,
171            );
172            if ret == (Error::Succsess as i32) {
173                Ok(val as usize)
174            } else {
175                Err(FromPrimitive::from_i32(ret).unwrap())
176            }
177        }
178    }
179}
180
181#[cfg(feature = "ptrace")]
182foreign_type! {
183    ///This state is used by accessors
184    pub unsafe type PtraceState {
185        type CType = libc::c_void;
186        fn drop = _UPT_destroy;
187    }
188}
189
190#[cfg(feature = "ptrace")]
191impl PtraceState {
192    /// Method constructs constructs new CoredumpState from path to core file.
193    /// # Arguments
194    ///
195    /// * `pid` - Pid for remote proccess
196    pub fn new(pid: u32) -> Result<PtraceState, Error> {
197        unsafe {
198            let ptr = _UPT_create(pid as _);
199            if ptr.is_null() {
200                Err(Error::NoMem)
201            } else {
202                Ok(PtraceState::from_ptr(ptr))
203            }
204        }
205    }
206}
207
208///Information about called procedure
209#[derive(Clone, Copy)]
210pub struct ProcInfo {
211    start_ip: usize,
212    end_ip: usize,
213}
214
215impl ProcInfo {
216    ///Method returns start address of procedure
217    pub fn start(&self) -> usize {
218        self.start_ip
219    }
220
221    ///Method returns end address of procedure
222    pub fn end(&self) -> usize {
223        self.end_ip
224    }
225}
226
227#[derive(Clone)]
228pub struct Cursor(unw_cursor_t);
229impl Cursor {
230    /// Method constructs cursor for coredump unwinding.
231    /// # Arguments
232    ///
233    /// * `address_space` - configured AddressSpace
234    ///
235    /// * `state` - Configured CoredumpState
236    pub fn coredump(
237        address_space: &mut AddressSpace,
238        state: &CoredumpState,
239    ) -> Result<Cursor, Error> {
240        unsafe {
241            let mut cursor = MaybeUninit::uninit();
242            let ret = unw_init_remote(
243                cursor.as_mut_ptr(),
244                address_space.0.as_ptr(),
245                state.0.as_ptr() as *mut c_void,
246            );
247            if ret == (Error::Succsess as i32) {
248                Ok(Cursor(cursor.assume_init()))
249            } else {
250                Err(FromPrimitive::from_i32(ret).unwrap())
251            }
252        }
253    }
254
255    /// Method constructs cursor for remote  unwinding.
256    /// # Arguments
257    ///
258    /// * `address_space` - configured AddressSpace
259    ///
260    /// * `state` - Configured CoredumpState
261    #[cfg(feature = "ptrace")]
262    pub fn ptrace(address_space: &mut AddressSpace, state: &PtraceState) -> Result<Cursor, Error> {
263        unsafe {
264            let mut cursor = MaybeUninit::uninit();
265            let ret = unw_init_remote(
266                cursor.as_mut_ptr(),
267                address_space.0.as_ptr(),
268                state.0.as_ptr() as *mut c_void,
269            );
270            if ret == (Error::Succsess as i32) {
271                Ok(Cursor(cursor.assume_init()))
272            } else {
273                Err(FromPrimitive::from_i32(ret).unwrap())
274            }
275        }
276    }
277
278    /// Method constructs cursor for local  unwinding.
279    /// # Arguments
280    ///
281    /// * `f` - function to work with local cursor
282    pub fn local<F, T>(f: F) -> Result<T, Error>
283    where
284        F: FnOnce(Cursor) -> Result<T, Error>,
285    {
286        unsafe {
287            let mut context = MaybeUninit::uninit();
288            let ret = unw_getcontext(context.as_mut_ptr());
289            if ret != (Error::Succsess as i32) {
290                return Err(FromPrimitive::from_i32(ret).unwrap());
291            }
292            let mut context = context.assume_init();
293
294            let mut cursor = MaybeUninit::uninit();
295            let ret = unw_init_local(cursor.as_mut_ptr(), &mut context);
296            if ret != (Error::Succsess as i32) {
297                return Err(FromPrimitive::from_i32(ret).unwrap());
298            }
299
300            f(Cursor(cursor.assume_init()))
301        }
302    }
303
304    /// Method executes step on cursor.
305    /// # Return
306    ///
307    /// * `true`  - if step is executed
308    ///
309    /// * `false` - if cursor ends
310    ///
311    /// * `Error` - if error while steping is occured
312    pub fn step(&mut self) -> Result<bool, Error> {
313        unsafe {
314            let ret = unw_step(&mut self.0);
315            if ret > 0 {
316                Ok(true)
317            } else if ret == 0 {
318                Ok(false)
319            } else {
320                Err(FromPrimitive::from_i32(ret).unwrap())
321            }
322        }
323    }
324
325    /// Method returns register value
326    /// # Arguments
327    ///
328    /// * `id`  - register's identifier
329    pub fn register(&mut self, id: i32) -> Result<usize, Error> {
330        unsafe {
331            let mut value = 0;
332            let ret = unw_get_reg(&self.0 as *const _ as *mut _, id, &mut value);
333            if ret == (Error::Succsess as i32) {
334                Ok(value as usize)
335            } else {
336                Err(FromPrimitive::from_i32(ret).unwrap())
337            }
338        }
339    }
340
341    /// Method returns instructions pointer value
342    pub fn ip(&mut self) -> Result<usize, Error> {
343        unsafe {
344            let mut value = 0;
345            let ret = unw_get_reg(
346                &self.0 as *const _ as *mut _,
347                libunwind_sys::UNW_TDEP_IP as i32,
348                &mut value,
349            );
350            if ret == (Error::Succsess as i32) {
351                Ok(value as usize)
352            } else {
353                Err(FromPrimitive::from_i32(ret).unwrap())
354            }
355        }
356    }
357
358    /// Method returns stack pointer value
359    pub fn sp(&mut self) -> Result<usize, Error> {
360        unsafe {
361            let mut value = 0;
362            let ret = unw_get_reg(
363                &self.0 as *const _ as *mut _,
364                libunwind_sys::UNW_TDEP_SP as i32,
365                &mut value,
366            );
367            if ret == (Error::Succsess as i32) {
368                Ok(value as usize)
369            } else {
370                Err(FromPrimitive::from_i32(ret).unwrap())
371            }
372        }
373    }
374
375    /// Method returns procedure information at crurrent stack frame
376    pub fn proc_info(&mut self) -> Result<ProcInfo, Error> {
377        unsafe {
378            let mut info = MaybeUninit::uninit();
379            let ret = unw_get_proc_info(&self.0 as *const _ as *mut _, info.as_mut_ptr());
380            if ret == (Error::Succsess as i32) {
381                let info = info.assume_init();
382                Ok(ProcInfo {
383                    start_ip: info.start_ip as usize,
384                    end_ip: info.end_ip as usize,
385                })
386            } else {
387                Err(FromPrimitive::from_i32(ret).unwrap())
388            }
389        }
390    }
391
392    /// Method returns procedure information at crurrent stack frame
393    pub fn proc_name(&mut self) -> Result<String, Error> {
394        unsafe {
395            let mut name_vec = vec![0; 256];
396            let mut offset = 0;
397            let ret = unw_get_proc_name(
398                &self.0 as *const _ as *mut _,
399                name_vec.as_mut_ptr() as *mut c_char,
400                name_vec.len(),
401                &mut offset,
402            );
403            if ret == (Error::Succsess as i32) {
404                let name = CStr::from_ptr(name_vec.as_mut_ptr());
405                Ok(name.to_str().unwrap().to_string())
406            } else {
407                Err(FromPrimitive::from_i32(ret).unwrap())
408            }
409        }
410    }
411
412    /// Method returns true if frame is signal frame
413    pub fn is_signal_frame(&mut self) -> Result<bool, Error> {
414        unsafe {
415            let ret = unw_is_signal_frame(&self.0 as *const _ as *mut _);
416            if ret < 0 {
417                Err(FromPrimitive::from_i32(ret).unwrap())
418            } else {
419                Ok(ret != 0)
420            }
421        }
422    }
423}
424
425#[cfg(test)]
426mod tests {
427    use crate::*;
428    use std::path::PathBuf;
429
430    /// Common function to unwind a core dump and return the backtrace string
431    fn unwind_core_dump(core_filename: &str) -> String {
432        let mut core_path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
433        core_path_buf.push(core_filename);
434
435        let mut state = CoredumpState::new(&core_path_buf).unwrap();
436        let mut address_space =
437            AddressSpace::new(Accessors::coredump(), Byteorder::Default).unwrap();
438        let mut cursor = Cursor::coredump(&mut address_space, &state).unwrap();
439
440        let mut backtrace = String::new();
441        loop {
442            let ip = cursor.ip().unwrap();
443            let sp = cursor.sp().unwrap();
444            if let Err(_e) = state.access_mem(&address_space, sp) {
445                assert!(false);
446            }
447
448            let name = cursor.proc_name().unwrap_or_else(|_| "<unknown>".to_string());
449            backtrace.push_str(&format!("0x{:x} in {:?} ()\n", ip, name));
450            match cursor.step() {
451                Ok(ret) => {
452                    if ret == false {
453                        break;
454                    }
455                }
456                Err(_) => {
457                    break;
458                }
459            }
460        }
461
462        backtrace
463    }
464
465    #[test]
466    #[cfg(target_arch = "x86_64")]
467    fn test_core_unwind() {
468        let backtrace = unwind_core_dump("data/core.test_callstack");
469        assert!(backtrace.contains("0x40054b"), "{}", true);
470        assert!(backtrace.contains("0x400527"), "{}", true);
471        assert!(backtrace.contains("0x4004fd"), "{}", true);
472        assert!(backtrace.contains("0x400579"), "{}", true);
473    }
474
475    #[test]
476    #[cfg(target_arch = "x86_64")]
477    fn test_core_unwind_heap_error() {
478        let backtrace = unwind_core_dump("data/core.test_heapError");
479        assert!(backtrace.contains("0x7f90e05c0428"), "{}", true);
480        assert!(backtrace.contains("0x7f90e060b37a"), "{}", true);
481    }
482
483    #[test]
484    #[cfg(target_arch = "x86_64")]
485    fn test_core_unwind_canary() {
486        let backtrace = unwind_core_dump("data/core.test_canary");
487        assert!(backtrace.contains("0x7fc14b36b428"), "{}", true);
488        assert!(backtrace.contains("0x7fc14b44f15c"), "{}", true);
489    }
490
491    #[test]
492    #[cfg(all(target_arch = "x86_64", feature = "ptrace"))]
493    fn test_remote_unwind() {
494        use libc::c_void;
495        use std::io;
496        use std::process::Command;
497        use std::ptr;
498        use std::thread;
499        use std::time::Duration;
500
501        let mut test_callstack_path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
502        test_callstack_path_buf.push("data/test_callstack_remote");
503        let mut child = Command::new(test_callstack_path_buf.to_str().unwrap())
504            .spawn()
505            .expect("failed to execute child");
506        thread::sleep(Duration::from_millis(10));
507        unsafe {
508            let ret = libc::ptrace(
509                libc::PTRACE_ATTACH,
510                child.id() as libc::pid_t,
511                ptr::null_mut::<c_void>(),
512                ptr::null_mut::<c_void>(),
513            );
514            if ret != 0 {
515                panic!("{}", io::Error::last_os_error());
516            }
517            loop {
518                let mut status = 0;
519                let ret = libc::waitpid(child.id() as libc::pid_t, &mut status, 0);
520                if ret < 0 {
521                    panic!("{}", io::Error::last_os_error());
522                }
523                if libc::WIFSTOPPED(status) {
524                    break;
525                }
526            }
527        }
528        let state = PtraceState::new(child.id()).unwrap();
529        let mut address_space = AddressSpace::new(Accessors::ptrace(), Byteorder::Default).unwrap();
530        let mut cursor = Cursor::ptrace(&mut address_space, &state).unwrap();
531
532        let mut backtrace = String::new();
533        loop {
534            let ip = cursor.ip().unwrap();
535            let name = cursor.proc_name().unwrap();
536            backtrace.push_str(&format!("0x{:x} in {:?} ()\n", ip, name));
537            let ret = cursor.step().unwrap();
538            if ret == false {
539                break;
540            }
541        }
542        assert!(backtrace.contains("main"), true);
543        assert!(backtrace.contains("first"), true);
544        assert!(backtrace.contains("second"), true);
545        assert!(backtrace.contains("third"), true);
546        child.kill().unwrap();
547    }
548
549    #[test]
550    #[cfg(target_arch = "x86_64")]
551    fn test_local_unwind() {
552        let backtrace = Cursor::local(|mut cursor| {
553            let mut backtrace = String::new();
554            loop {
555                let ip = cursor.ip().unwrap();
556                let name = cursor.proc_name().unwrap();
557                backtrace.push_str(&format!("0x{:x} in {:?} ()\n", ip, name));
558                let ret = cursor.step().unwrap();
559                if ret == false {
560                    break;
561                }
562            }
563            Ok(backtrace)
564        })
565        .unwrap();
566
567        assert!(backtrace.contains("test_local_unwind"), "{}", true);
568        assert!(
569            backtrace.contains("start_thread") || backtrace.contains("start"),
570            "{}", true
571        );
572    }
573}