framehop/
code_address.rs

1use core::num::NonZeroU64;
2
3/// An absolute code address for a stack frame. Can either be taken directly from the
4/// instruction pointer ("program counter"), or from a return address.
5///
6/// These addresses are "AVMAs", i.e. Actual Virtual Memory Addresses, i.e. addresses
7/// in the virtual memory of the profiled process.
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum FrameAddress {
10    /// This address is the instruction pointer / program counter. This is what unwinding
11    /// starts with.
12    InstructionPointer(u64),
13
14    /// This is a return address, i.e. the address to which the CPU will jump to when
15    /// returning from a function. This is the address of the instruction *after* the
16    /// call instruction.
17    ///
18    /// Unwinding produces a list of return addresses.
19    ReturnAddress(NonZeroU64),
20}
21
22impl FrameAddress {
23    /// Create a [`FrameAddress::InstructionPointer`].
24    pub fn from_instruction_pointer(ip: u64) -> Self {
25        FrameAddress::InstructionPointer(ip)
26    }
27
28    /// Create a [`FrameAddress::ReturnAddress`]. This returns `None` if the given
29    /// address is zero.
30    pub fn from_return_address(return_address: u64) -> Option<Self> {
31        Some(FrameAddress::ReturnAddress(NonZeroU64::new(
32            return_address,
33        )?))
34    }
35
36    /// The raw address (AVMA).
37    pub fn address(self) -> u64 {
38        match self {
39            FrameAddress::InstructionPointer(address) => address,
40            FrameAddress::ReturnAddress(address) => address.into(),
41        }
42    }
43
44    /// The address (AVMA) that should be used for lookup.
45    ///
46    /// If this address is taken directly from the instruction pointer, then the lookup
47    /// address is just the raw address.
48    ///
49    /// If this address is a return address, then the lookup address is that address **minus
50    /// one byte**. This adjusted address will point inside the call instruction. This
51    /// subtraction of one byte is needed if you want to look up unwind information or
52    /// debug information, because you usually want the information for the call, not for
53    /// the next instruction after the call.
54    ///
55    /// Furthermore, this distinction matters if a function calls a noreturn function as
56    /// the last thing it does: If the call is the final instruction of the function, then
57    /// the return address will point *after* the function, into the *next* function.
58    /// If, during unwinding, you look up unwind information for that next function, you'd
59    /// get incorrect unwinding.
60    /// This has been observed in practice with `+[NSThread exit]`.
61    pub fn address_for_lookup(self) -> u64 {
62        match self {
63            FrameAddress::InstructionPointer(address) => address,
64            FrameAddress::ReturnAddress(address) => u64::from(address) - 1,
65        }
66    }
67
68    /// Returns whether this address is a return address.
69    pub fn is_return_address(self) -> bool {
70        match self {
71            FrameAddress::InstructionPointer(_) => false,
72            FrameAddress::ReturnAddress(_) => true,
73        }
74    }
75}