Skip to main content

aya_friday/maps/
stack_trace.rs

1//! A hash map of kernel or user space stack traces.
2//!
3//! See [`StackTraceMap`] for documentation and examples.
4
5use std::{
6    borrow::{Borrow, BorrowMut},
7    os::fd::AsFd as _,
8};
9
10use aya_obj::generated::BPF_F_STACK_BUILD_ID;
11
12use crate::{
13    maps::{IterableMap, MapData, MapError, MapIter, MapKeys, hash_map},
14    sys::{SyscallError, bpf_map_lookup_elem_ptr},
15};
16
17/// A hash map of kernel or user space stack traces.
18///
19/// Stack trace maps can be used to store stack traces captured by eBPF programs, which can be
20/// useful for profiling, to associate a trace to an event, etc. You can capture traces calling
21/// `stack_id = bpf_get_stackid(ctx, map, flags)` from eBPF, and then you can retrieve the traces
22/// from their stack ids.
23///
24/// # Minimum kernel version
25///
26/// The minimum kernel version required to use this feature is 4.6.
27///  
28/// # Examples
29///
30/// ```no_run
31/// # #[derive(thiserror::Error, Debug)]
32/// # enum Error {
33/// #     #[error(transparent)]
34/// #     IO(#[from] std::io::Error),
35/// #     #[error(transparent)]
36/// #     Map(#[from] aya::maps::MapError),
37/// #     #[error(transparent)]
38/// #     Ebpf(#[from] aya::EbpfError)
39/// # }
40/// # let bpf = aya::Ebpf::load(&[])?;
41/// use aya::maps::StackTraceMap;
42/// use aya::util::kernel_symbols;
43///
44/// let mut stack_traces = StackTraceMap::try_from(bpf.map("STACK_TRACES").unwrap())?;
45/// // load kernel symbols from /proc/kallsyms
46/// let ksyms = kernel_symbols()?;
47///
48/// // NOTE: you typically send stack_ids from eBPF to user space using other maps
49/// let stack_id = 1234;
50/// let mut stack_trace = stack_traces.get(&stack_id, 0)?;
51///
52/// // here we resolve symbol names using kernel symbols. If this was a user space stack (for
53/// // example captured from a uprobe), you'd have to load the symbols using some other mechanism
54/// // (eg loading the target binary debuginfo)
55/// for frame in stack_trace.frames() {
56///     if let Some(sym) = ksyms.range(..=frame.ip).next_back().map(|(_, s)| s) {
57///         println!(
58///             "{:#x} {}",
59///             frame.ip,
60///             sym
61///         );
62///     } else {
63///         println!(
64///             "{:#x}",
65///             frame.ip
66///         );
67///     }
68/// }
69///
70/// # Ok::<(), Error>(())
71/// ```
72///
73#[derive(Debug)]
74#[doc(alias = "BPF_MAP_TYPE_STACK_TRACE")]
75pub struct StackTraceMap<T> {
76    pub(crate) inner: T,
77    max_stack_depth: usize,
78}
79
80// A stack trace entry is a single `u64` instruction pointer, matching the
81// kernel layout for `BPF_MAP_TYPE_STACK_TRACE` values.
82type StackEntry = u64;
83
84impl<T: Borrow<MapData>> StackTraceMap<T> {
85    pub(crate) fn new(map: T) -> Result<Self, MapError> {
86        let data = map.borrow();
87
88        let key_size = data.obj.key_size() as usize;
89        let expected_key = size_of::<u32>();
90        if key_size != expected_key {
91            return Err(MapError::InvalidKeySize {
92                size: key_size,
93                expected: expected_key,
94            });
95        }
96
97        // BPF_F_STACK_BUILD_ID switches stack entries to
98        // `struct bpf_stack_build_id` (32 bytes), which `get` decodes as
99        // `[u64]` and would silently corrupt. Reject it.
100        let flags = data.obj.map_flags();
101        if flags & BPF_F_STACK_BUILD_ID != 0 {
102            return Err(MapError::UnsupportedMapFlags {
103                flags,
104                reason: "StackTraceMap does not support bpf_stack_build_id entries",
105            });
106        }
107
108        let value_size = data.obj.value_size() as usize;
109        let expected_stride = size_of::<StackEntry>();
110        if value_size == 0 || !value_size.is_multiple_of(expected_stride) {
111            return Err(MapError::InvalidValueStride {
112                size: value_size,
113                stride: expected_stride,
114            });
115        }
116        let max_stack_depth = value_size / expected_stride;
117
118        Ok(Self {
119            inner: map,
120            max_stack_depth,
121        })
122    }
123
124    /// Returns the stack trace with the given `stack_id`.
125    ///
126    /// # Errors
127    ///
128    /// Returns [`MapError::KeyNotFound`] if there is no stack trace with the
129    /// given `stack_id`, or [`MapError::SyscallError`] if `bpf_map_lookup_elem` fails.
130    pub fn get(&self, stack_id: &u32, flags: u64) -> Result<StackTrace, MapError> {
131        let fd = self.inner.borrow().fd().as_fd();
132
133        let mut frames: Vec<StackEntry> = vec![0; self.max_stack_depth];
134        bpf_map_lookup_elem_ptr(fd, Some(stack_id), frames.as_mut_ptr(), flags)
135            .map_err(|io_error| SyscallError {
136                call: "bpf_map_lookup_elem",
137                io_error,
138            })?
139            .ok_or(MapError::KeyNotFound)?;
140
141        let frames = frames
142            .into_iter()
143            .take_while(|ip| *ip != 0)
144            .map(|ip| StackFrame { ip })
145            .collect::<Vec<_>>();
146
147        Ok(StackTrace {
148            id: *stack_id,
149            frames,
150        })
151    }
152
153    /// An iterator visiting all (`stack_id`, `stack_trace`) pairs in arbitrary order. The
154    /// iterator item type is `Result<(u32, StackTrace), MapError>`.
155    pub fn iter(&self) -> MapIter<'_, u32, StackTrace, Self> {
156        MapIter::new(self)
157    }
158
159    /// An iterator visiting all the `stack_ids` in arbitrary order. The iterator element
160    /// type is `Result<u32, MapError>`.
161    pub fn stack_ids(&self) -> MapKeys<'_, u32> {
162        MapKeys::new(self.inner.borrow())
163    }
164}
165
166impl<T: Borrow<MapData>> IterableMap<u32, StackTrace> for StackTraceMap<T> {
167    fn map(&self) -> &MapData {
168        self.inner.borrow()
169    }
170
171    fn get(&self, key: &u32) -> Result<StackTrace, MapError> {
172        self.get(key, 0)
173    }
174}
175
176impl<'a, T: Borrow<MapData>> IntoIterator for &'a StackTraceMap<T> {
177    type Item = Result<(u32, StackTrace), MapError>;
178    type IntoIter = MapIter<'a, u32, StackTrace, StackTraceMap<T>>;
179
180    fn into_iter(self) -> Self::IntoIter {
181        self.iter()
182    }
183}
184
185impl<T: BorrowMut<MapData>> StackTraceMap<T> {
186    /// Removes the stack trace with the given `stack_id`.
187    pub fn remove(&mut self, stack_id: &u32) -> Result<(), MapError> {
188        hash_map::remove(self.inner.borrow_mut(), stack_id)
189    }
190}
191
192/// A kernel or user space stack trace.
193///
194/// See the [`StackTraceMap`] documentation for examples.
195pub struct StackTrace {
196    /// The stack trace id as returned by `bpf_get_stackid()`.
197    pub id: u32,
198    frames: Vec<StackFrame>,
199}
200
201impl StackTrace {
202    /// Returns the frames in this stack trace.
203    pub fn frames(&self) -> &[StackFrame] {
204        &self.frames
205    }
206}
207
208/// A stack frame.
209pub struct StackFrame {
210    /// The instruction pointer of this frame.
211    pub ip: u64,
212}