Skip to main content

libdd_libunwind_sys/
lib.rs

1// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
5mod libunwind_x86_64;
6
7#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
8mod libunwind_aarch64;
9
10#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
11pub use libunwind_aarch64::*;
12#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
13pub use libunwind_x86_64::*;
14
15#[cfg(all(test, target_os = "linux"))]
16mod tests {
17    use super::*;
18
19    #[test]
20    #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind
21    fn test_basic_unwind() {
22        unsafe {
23            let mut context: UnwContext = std::mem::zeroed();
24            let mut cursor: UnwCursor = std::mem::zeroed();
25
26            let ret = getcontext(&mut context);
27            assert_eq!(ret, 0, "getcontext failed");
28
29            // Initialize cursor
30            let ret = unw_init_local2(&mut cursor, &mut context, 0);
31            assert_eq!(ret, 0, "unw_init_local2 failed");
32
33            // Walk the stack
34            let mut frames = 0;
35            loop {
36                let ret = unw_step(&mut cursor);
37                if ret <= 0 {
38                    break;
39                }
40                frames += 1;
41
42                // Limit iterations to prevent infinite loops
43                if frames > 100 {
44                    break;
45                }
46            }
47
48            // Should have at least a few frames
49            assert!(frames > 0, "Expected at least one stack frame");
50        }
51    }
52
53    #[test]
54    #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind
55    fn test_get_register() {
56        unsafe {
57            let mut context: UnwContext = std::mem::zeroed();
58            let mut cursor: UnwCursor = std::mem::zeroed();
59
60            assert_eq!(getcontext(&mut context), 0);
61            assert_eq!(unw_init_local2(&mut cursor, &mut context, 0), 0);
62
63            // Get instruction pointer
64            let mut ip: UnwWord = 0;
65            let ret = unw_get_reg(&mut cursor, UNW_REG_IP, &mut ip);
66            assert_eq!(ret, 0, "Failed to get IP register");
67            assert_ne!(ip, 0, "IP should not be zero");
68
69            // Get stack pointer
70            let mut sp: UnwWord = 0;
71            let ret = unw_get_reg(&mut cursor, UNW_REG_SP, &mut sp);
72            assert_eq!(ret, 0, "Failed to get SP register");
73            assert_ne!(sp, 0, "SP should not be zero");
74        }
75    }
76
77    #[test]
78    #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind
79    fn test_backtrace2() {
80        unsafe {
81            let mut context: UnwContext = std::mem::zeroed();
82            assert_eq!(getcontext(&mut context), 0);
83
84            // unw_backtrace2 expects an array of void pointers
85            let mut frames: [*mut ::std::os::raw::c_void; 100] = [std::ptr::null_mut(); 100];
86            let ret = unw_backtrace2(frames.as_mut_ptr(), 100, &mut context, 0);
87
88            // Return value should be >= 0 (number of frames captured)
89            assert!(ret >= 0, "unw_backtrace2 failed with error: {}", ret);
90
91            let frame_count = ret as usize;
92            assert!(frame_count > 0, "Expected at least one frame");
93
94            // Print captured frames
95            for (i, &frame) in frames.iter().enumerate().take(frame_count) {
96                let frame_ptr = frame as usize;
97                println!("Frame {}: 0x{:016x}", i, frame_ptr);
98            }
99        }
100    }
101
102    #[test]
103    #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind
104    fn test_get_proc_name() {
105        unsafe {
106            let mut context: UnwContext = std::mem::zeroed();
107            let mut cursor: UnwCursor = std::mem::zeroed();
108
109            assert_eq!(getcontext(&mut context), 0);
110            assert_eq!(
111                unw_init_local2(&mut cursor, &mut context, UNW_INIT_LOCAL_ONLY_IP),
112                0
113            );
114
115            let mut name: [libc::c_char; 100] = [0; 100];
116            let ret = unw_get_proc_name(&mut cursor, name.as_mut_ptr(), 100, std::ptr::null_mut());
117            assert_eq!(ret, 0, "unw_get_proc_name failed");
118            let fn_name = std::ffi::CStr::from_ptr(name.as_ptr()).to_string_lossy();
119            assert!(!fn_name.is_empty(), "Name should not be empty");
120            // name is managed: _ZN15libdd_libunwind5tests18test_get_proc_name17hec15ec5ad6978a00E
121            // we should just chekc that test_get_proc_name is part of it
122            assert!(
123                fn_name.contains("test_get_proc_name"),
124                "Name should contain 'test_get_proc_name'"
125            );
126        }
127    }
128}