cranelift_codegen/isa/
unwind.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
//! Represents information relating to function unwinding.

use crate::machinst::RealReg;

#[cfg(feature = "enable-serde")]
use serde_derive::{Deserialize, Serialize};

#[cfg(feature = "unwind")]
pub mod systemv;

#[cfg(feature = "unwind")]
pub mod winx64;

#[cfg(feature = "unwind")]
pub mod winarm64;

/// CFA-based unwind information used on SystemV.
pub type CfaUnwindInfo = systemv::UnwindInfo;

/// Expected unwind info type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum UnwindInfoKind {
    /// No unwind info.
    None,
    /// SystemV CIE/FDE unwind info.
    #[cfg(feature = "unwind")]
    SystemV,
    /// Windows X64 Unwind info
    #[cfg(feature = "unwind")]
    Windows,
}

/// Represents unwind information for a single function.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum UnwindInfo {
    /// Windows x64 ABI unwind information.
    #[cfg(feature = "unwind")]
    WindowsX64(winx64::UnwindInfo),
    /// System V ABI unwind information.
    #[cfg(feature = "unwind")]
    SystemV(CfaUnwindInfo),
    /// Windows Arm64 ABI unwind information.
    #[cfg(feature = "unwind")]
    WindowsArm64(winarm64::UnwindInfo),
}

/// Unwind pseudoinstruction used in VCode backends: represents that
/// at the present location, an action has just been taken.
///
/// VCode backends always emit unwind info that is relative to a frame
/// pointer, because we are planning to allow for dynamic frame allocation,
/// and because it makes the design quite a lot simpler in general: we don't
/// have to be precise about SP adjustments throughout the body of the function.
///
/// We include only unwind info for prologues at this time. Note that unwind
/// info for epilogues is only necessary if one expects to unwind while within
/// the last few instructions of the function (after FP has been restored) or
/// if one wishes to instruction-step through the epilogue and see a backtrace
/// at every point. This is not necessary for correct operation otherwise and so
/// we simplify the world a bit by omitting epilogue information. (Note that
/// some platforms also don't require or have a way to describe unwind
/// information for epilogues at all: for example, on Windows, the `UNWIND_INFO`
/// format only stores information for the function prologue.)
///
/// Because we are defining an abstraction over multiple unwind formats (at
/// least Windows/fastcall and System V) and multiple architectures (at least
/// x86-64 and aarch64), we have to be a little bit flexible in how we describe
/// the frame. However, it turns out that a least-common-denominator prologue
/// works for all of the cases we have to worry about today!
///
/// We assume the stack looks something like this:
///
///
/// ```plain
///                  +----------------------------------------------+
///                  | stack arg area, etc (according to ABI)       |
///                  | ...                                          |
///   SP at call --> +----------------------------------------------+
///                  | return address (pushed by HW or SW)          |
///                  +----------------------------------------------+
///                  | old frame pointer (FP)                       |
///   FP in this --> +----------------------------------------------+
///   function       | clobbered callee-save registers              |
///                  | ...                                          |
///   start of   --> +----------------------------------------------+
///   clobbers       | (rest of function's frame, irrelevant here)  |
///                  | ...                                          |
///   SP in this --> +----------------------------------------------+
///   function
/// ```
///
/// We assume that the prologue consists of:
///
/// * `PushFrameRegs`: A push operation that adds the old FP to the stack (and
///    maybe the link register, on architectures that do not push return addresses
///    in hardware)
/// * `DefineFrame`: An update that sets FP to SP to establish a new frame
/// * `SaveReg`: A number of stores or pushes to the stack to save clobbered registers
///
/// Each of these steps has a corresponding pseudo-instruction. At each step,
/// we need some information to determine where the current stack frame is
/// relative to SP or FP. When the `PushFrameRegs` occurs, we need to know how
/// much SP was decremented by, so we can allow the unwinder to continue to find
/// the caller's frame. When we define the new frame, we need to know where FP
/// is in relation to "SP at call" and also "start of clobbers", because
/// different unwind formats define one or the other of those as the anchor by
/// which we define the frame. Finally, when registers are saved, we need to
/// know which ones, and where.
///
/// Different unwind formats work differently; here is a whirlwind tour of how
/// they define frames to help understanding:
///
/// - Windows unwind information defines a frame that must start below the
///   clobber area, because all clobber-save offsets are non-negative. We set it
///   at the "start of clobbers" in the figure above. The `UNWIND_INFO` contains
///   a "frame pointer offset" field; when we define the new frame, the frame is
///   understood to be the value of FP (`RBP`) *minus* this offset. In other
///   words, the FP is *at the frame pointer offset* relative to the
///   start-of-clobber-frame. We use the "FP offset down to clobber area" offset
///   to generate this info.
///
/// - System V unwind information defines a frame in terms of the CFA
///   (call-frame address), which is equal to the "SP at call" above. SysV
///   allows negative offsets, so there is no issue defining clobber-save
///   locations in terms of CFA. The format allows us to define CFA flexibly in
///   terms of any register plus an offset; we define it in terms of FP plus
///   the clobber-to-caller-SP offset once FP is established.
///
/// Note that certain architectures impose limits on offsets: for example, on
/// Windows, the base of the clobber area must not be more than 240 bytes below
/// FP.
///
/// Unwind pseudoinstructions are emitted inline by ABI code as it generates
/// a prologue. Thus, for the usual case, a prologue might look like (using x64
/// as an example):
///
/// ```plain
/// push rbp
/// unwind UnwindInst::PushFrameRegs { offset_upward_to_caller_sp: 16 }
/// mov rbp, rsp
/// unwind UnwindInst::DefineNewFrame { offset_upward_to_caller_sp: 16,
///                                     offset_downward_to_clobbers: 16 }
/// sub rsp, 32
/// mov [rsp+16], r12
/// unwind UnwindInst::SaveReg { reg: R12, clobber_offset: 0 }
/// mov [rsp+24], r13
/// unwind UnwindInst::SaveReg { reg: R13, clobber_offset: 8 }
/// ...
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum UnwindInst {
    /// The frame-pointer register for this architecture has just been pushed to
    /// the stack (and on architectures where return-addresses are not pushed by
    /// hardware, the link register as well). The FP has not been set to this
    /// frame yet. The current location of SP is such that
    /// `offset_upward_to_caller_sp` is the distance to SP-at-callsite (our
    /// caller's frame).
    PushFrameRegs {
        /// The offset from the current SP (after push) to the SP at
        /// caller's callsite.
        offset_upward_to_caller_sp: u32,
    },
    /// The frame-pointer register for this architecture has just been
    /// set to the current stack location. We wish to define a new
    /// frame that is anchored on this new FP value. Offsets are provided
    /// upward to the caller's stack frame and downward toward the clobber
    /// area. We expect this pseudo-op to come after `PushFrameRegs`.
    DefineNewFrame {
        /// The offset from the current SP and FP value upward to the value of
        /// SP at the callsite that invoked us.
        offset_upward_to_caller_sp: u32,
        /// The offset from the current SP and FP value downward to the start of
        /// the clobber area.
        offset_downward_to_clobbers: u32,
    },
    /// The stack pointer was adjusted to allocate the stack.
    StackAlloc {
        /// Size to allocate.
        size: u32,
    },
    /// The stack slot at the given offset from the clobber-area base has been
    /// used to save the given register.
    ///
    /// Given that `CreateFrame` has occurred first with some
    /// `offset_downward_to_clobbers`, `SaveReg` with `clobber_offset` indicates
    /// that the value of `reg` is saved on the stack at address `FP -
    /// offset_downward_to_clobbers + clobber_offset`.
    SaveReg {
        /// The offset from the start of the clobber area to this register's
        /// stack location.
        clobber_offset: u32,
        /// The saved register.
        reg: RealReg,
    },
    /// Computes the value of the given register in the caller as stack offset.
    /// Typically used to unwind the stack pointer if the default rule does not apply.
    /// The `clobber_offset` is computed the same way as for the `SaveReg` rule.
    RegStackOffset {
        /// The offset from the start of the clobber area to this register's value.
        clobber_offset: u32,
        /// The register whose value is to be set.
        reg: RealReg,
    },
    /// Defines if the aarch64-specific pointer authentication available for ARM v8.3+ devices
    /// is enabled for certain pointers or not.
    Aarch64SetPointerAuth {
        /// Whether return addresses (hold in LR) contain a pointer-authentication code.
        return_addresses: bool,
    },
}

struct Writer<'a> {
    buf: &'a mut [u8],
    offset: usize,
}

impl<'a> Writer<'a> {
    pub fn new(buf: &'a mut [u8]) -> Self {
        Self { buf, offset: 0 }
    }

    fn write_u8(&mut self, v: u8) {
        self.buf[self.offset] = v;
        self.offset += 1;
    }

    fn write_u16_le(&mut self, v: u16) {
        self.buf[self.offset..(self.offset + 2)].copy_from_slice(&v.to_le_bytes());
        self.offset += 2;
    }

    fn write_u16_be(&mut self, v: u16) {
        self.buf[self.offset..(self.offset + 2)].copy_from_slice(&v.to_be_bytes());
        self.offset += 2;
    }

    fn write_u32_le(&mut self, v: u32) {
        self.buf[self.offset..(self.offset + 4)].copy_from_slice(&v.to_le_bytes());
        self.offset += 4;
    }

    fn write_u32_be(&mut self, v: u32) {
        self.buf[self.offset..(self.offset + 4)].copy_from_slice(&v.to_be_bytes());
        self.offset += 4;
    }
}