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}