Coverage Report

Created: 2024-10-16 07:58

/rust/registry/src/index.crates.io-6f17d22bba15001f/cranelift-codegen-0.91.1/src/isa/unwind/winx64.rs
Line
Count
Source (jump to first uncovered line)
1
//! Windows x64 ABI unwind information.
2
3
use crate::result::{CodegenError, CodegenResult};
4
use alloc::vec::Vec;
5
use log::warn;
6
#[cfg(feature = "enable-serde")]
7
use serde::{Deserialize, Serialize};
8
9
use crate::binemit::CodeOffset;
10
use crate::isa::unwind::UnwindInst;
11
12
/// Maximum (inclusive) size of a "small" stack allocation
13
const SMALL_ALLOC_MAX_SIZE: u32 = 128;
14
/// Maximum (inclusive) size of a "large" stack allocation that can represented in 16-bits
15
const LARGE_ALLOC_16BIT_MAX_SIZE: u32 = 524280;
16
17
struct Writer<'a> {
18
    buf: &'a mut [u8],
19
    offset: usize,
20
}
21
22
impl<'a> Writer<'a> {
23
0
    pub fn new(buf: &'a mut [u8]) -> Self {
24
0
        Self { buf, offset: 0 }
25
0
    }
26
27
0
    fn write_u8(&mut self, v: u8) {
28
0
        self.buf[self.offset] = v;
29
0
        self.offset += 1;
30
0
    }
31
32
0
    fn write_u16_le(&mut self, v: u16) {
33
0
        self.buf[self.offset..(self.offset + 2)].copy_from_slice(&v.to_le_bytes());
34
0
        self.offset += 2;
35
0
    }
36
37
0
    fn write_u32_le(&mut self, v: u32) {
38
0
        self.buf[self.offset..(self.offset + 4)].copy_from_slice(&v.to_le_bytes());
39
0
        self.offset += 4;
40
0
    }
41
}
42
43
/// The supported unwind codes for the x64 Windows ABI.
44
///
45
/// See: <https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64>
46
/// Only what is needed to describe the prologues generated by the Cranelift x86 ISA are represented here.
47
/// Note: the Cranelift x86 ISA RU enum matches the Windows unwind GPR encoding values.
48
#[allow(dead_code)]
49
#[derive(Clone, Debug, PartialEq, Eq)]
50
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
51
pub(crate) enum UnwindCode {
52
    PushRegister {
53
        instruction_offset: u8,
54
        reg: u8,
55
    },
56
    SaveReg {
57
        instruction_offset: u8,
58
        reg: u8,
59
        stack_offset: u32,
60
    },
61
    SaveXmm {
62
        instruction_offset: u8,
63
        reg: u8,
64
        stack_offset: u32,
65
    },
66
    StackAlloc {
67
        instruction_offset: u8,
68
        size: u32,
69
    },
70
    SetFPReg {
71
        instruction_offset: u8,
72
    },
73
}
74
75
impl UnwindCode {
76
0
    fn emit(&self, writer: &mut Writer) {
77
0
        enum UnwindOperation {
78
0
            PushNonvolatileRegister = 0,
79
0
            LargeStackAlloc = 1,
80
0
            SmallStackAlloc = 2,
81
0
            SetFPReg = 3,
82
0
            SaveNonVolatileRegister = 4,
83
0
            SaveNonVolatileRegisterFar = 5,
84
0
            SaveXmm128 = 8,
85
0
            SaveXmm128Far = 9,
86
0
        }
87
0
88
0
        match self {
89
            Self::PushRegister {
90
0
                instruction_offset,
91
0
                reg,
92
0
            } => {
93
0
                writer.write_u8(*instruction_offset);
94
0
                writer.write_u8((*reg << 4) | (UnwindOperation::PushNonvolatileRegister as u8));
95
0
            }
96
            Self::SaveReg {
97
0
                instruction_offset,
98
0
                reg,
99
0
                stack_offset,
100
            }
101
            | Self::SaveXmm {
102
0
                instruction_offset,
103
0
                reg,
104
0
                stack_offset,
105
            } => {
106
0
                let is_xmm = match self {
107
0
                    Self::SaveXmm { .. } => true,
108
0
                    _ => false,
109
                };
110
0
                let (op_small, op_large) = if is_xmm {
111
0
                    (UnwindOperation::SaveXmm128, UnwindOperation::SaveXmm128Far)
112
                } else {
113
0
                    (
114
0
                        UnwindOperation::SaveNonVolatileRegister,
115
0
                        UnwindOperation::SaveNonVolatileRegisterFar,
116
0
                    )
117
                };
118
0
                writer.write_u8(*instruction_offset);
119
0
                let scaled_stack_offset = stack_offset / 16;
120
0
                if scaled_stack_offset <= core::u16::MAX as u32 {
121
0
                    writer.write_u8((*reg << 4) | (op_small as u8));
122
0
                    writer.write_u16_le(scaled_stack_offset as u16);
123
0
                } else {
124
0
                    writer.write_u8((*reg << 4) | (op_large as u8));
125
0
                    writer.write_u16_le(*stack_offset as u16);
126
0
                    writer.write_u16_le((stack_offset >> 16) as u16);
127
0
                }
128
            }
129
            Self::StackAlloc {
130
0
                instruction_offset,
131
0
                size,
132
0
            } => {
133
0
                // Stack allocations on Windows must be a multiple of 8 and be at least 1 slot
134
0
                assert!(*size >= 8);
135
0
                assert!((*size % 8) == 0);
136
137
0
                writer.write_u8(*instruction_offset);
138
0
                if *size <= SMALL_ALLOC_MAX_SIZE {
139
0
                    writer.write_u8(
140
0
                        ((((*size - 8) / 8) as u8) << 4) | UnwindOperation::SmallStackAlloc as u8,
141
0
                    );
142
0
                } else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE {
143
0
                    writer.write_u8(UnwindOperation::LargeStackAlloc as u8);
144
0
                    writer.write_u16_le((*size / 8) as u16);
145
0
                } else {
146
0
                    writer.write_u8((1 << 4) | (UnwindOperation::LargeStackAlloc as u8));
147
0
                    writer.write_u32_le(*size);
148
0
                }
149
            }
150
0
            Self::SetFPReg { instruction_offset } => {
151
0
                writer.write_u8(*instruction_offset);
152
0
                writer.write_u8(UnwindOperation::SetFPReg as u8);
153
0
            }
154
        }
155
0
    }
156
157
0
    fn node_count(&self) -> usize {
158
0
        match self {
159
0
            Self::StackAlloc { size, .. } => {
160
0
                if *size <= SMALL_ALLOC_MAX_SIZE {
161
0
                    1
162
0
                } else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE {
163
0
                    2
164
                } else {
165
0
                    3
166
                }
167
            }
168
0
            Self::SaveXmm { stack_offset, .. } | Self::SaveReg { stack_offset, .. } => {
169
0
                if *stack_offset <= core::u16::MAX as u32 {
170
0
                    2
171
                } else {
172
0
                    3
173
                }
174
            }
175
0
            _ => 1,
176
        }
177
0
    }
178
}
179
180
pub(crate) enum MappedRegister {
181
    Int(u8),
182
    Xmm(u8),
183
}
184
185
/// Maps UnwindInfo register to Windows x64 unwind data.
186
pub(crate) trait RegisterMapper<Reg> {
187
    /// Maps a Reg to a Windows unwind register number.
188
    fn map(reg: Reg) -> MappedRegister;
189
}
190
191
/// Represents Windows x64 unwind information.
192
///
193
/// For information about Windows x64 unwind info, see:
194
/// <https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64>
195
#[derive(Clone, Debug, PartialEq, Eq)]
196
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
197
pub struct UnwindInfo {
198
    pub(crate) flags: u8,
199
    pub(crate) prologue_size: u8,
200
    pub(crate) frame_register: Option<u8>,
201
    pub(crate) frame_register_offset: u8,
202
    pub(crate) unwind_codes: Vec<UnwindCode>,
203
}
204
205
impl UnwindInfo {
206
    /// Gets the emit size of the unwind information, in bytes.
207
0
    pub fn emit_size(&self) -> usize {
208
0
        let node_count = self.node_count();
209
0
210
0
        // Calculation of the size requires no SEH handler or chained info
211
0
        assert!(self.flags == 0);
212
213
        // Size of fixed part of UNWIND_INFO is 4 bytes
214
        // Then comes the UNWIND_CODE nodes (2 bytes each)
215
        // Then comes 2 bytes of padding for the unwind codes if necessary
216
        // Next would come the SEH data, but we assert above that the function doesn't have SEH data
217
218
0
        4 + (node_count * 2) + if (node_count & 1) == 1 { 2 } else { 0 }
219
0
    }
220
221
    /// Emits the unwind information into the given mutable byte slice.
222
    ///
223
    /// This function will panic if the slice is not at least `emit_size` in length.
224
0
    pub fn emit(&self, buf: &mut [u8]) {
225
0
        const UNWIND_INFO_VERSION: u8 = 1;
226
0
227
0
        let node_count = self.node_count();
228
0
        assert!(node_count <= 256);
229
230
0
        let mut writer = Writer::new(buf);
231
0
232
0
        writer.write_u8((self.flags << 3) | UNWIND_INFO_VERSION);
233
0
        writer.write_u8(self.prologue_size);
234
0
        writer.write_u8(node_count as u8);
235
236
0
        if let Some(reg) = self.frame_register {
237
0
            writer.write_u8((self.frame_register_offset << 4) | reg);
238
0
        } else {
239
0
            writer.write_u8(0);
240
0
        }
241
242
        // Unwind codes are written in reverse order (prologue offset descending)
243
0
        for code in self.unwind_codes.iter().rev() {
244
0
            code.emit(&mut writer);
245
0
        }
246
247
        // To keep a 32-bit alignment, emit 2 bytes of padding if there's an odd number of 16-bit nodes
248
0
        if (node_count & 1) == 1 {
249
0
            writer.write_u16_le(0);
250
0
        }
251
252
        // Ensure the correct number of bytes was emitted
253
0
        assert_eq!(writer.offset, self.emit_size());
254
0
    }
255
256
0
    fn node_count(&self) -> usize {
257
0
        self.unwind_codes
258
0
            .iter()
259
0
            .fold(0, |nodes, c| nodes + c.node_count())
260
0
    }
261
}
262
263
const UNWIND_RBP_REG: u8 = 5;
264
265
0
pub(crate) fn create_unwind_info_from_insts<MR: RegisterMapper<crate::machinst::Reg>>(
266
0
    insts: &[(CodeOffset, UnwindInst)],
267
0
) -> CodegenResult<UnwindInfo> {
268
0
    let mut unwind_codes = vec![];
269
0
    let mut frame_register_offset = 0;
270
0
    let mut max_unwind_offset = 0;
271
0
    for &(instruction_offset, ref inst) in insts {
272
0
        let instruction_offset = ensure_unwind_offset(instruction_offset)?;
273
0
        match inst {
274
0
            &UnwindInst::PushFrameRegs { .. } => {
275
0
                unwind_codes.push(UnwindCode::PushRegister {
276
0
                    instruction_offset,
277
0
                    reg: UNWIND_RBP_REG,
278
0
                });
279
0
            }
280
            &UnwindInst::DefineNewFrame {
281
0
                offset_downward_to_clobbers,
282
0
                ..
283
0
            } => {
284
0
                frame_register_offset = ensure_unwind_offset(offset_downward_to_clobbers)?;
285
0
                unwind_codes.push(UnwindCode::SetFPReg { instruction_offset });
286
            }
287
0
            &UnwindInst::StackAlloc { size } => {
288
0
                unwind_codes.push(UnwindCode::StackAlloc {
289
0
                    instruction_offset,
290
0
                    size,
291
0
                });
292
0
            }
293
            &UnwindInst::SaveReg {
294
0
                clobber_offset,
295
0
                reg,
296
0
            } => match MR::map(reg.into()) {
297
0
                MappedRegister::Int(reg) => {
298
0
                    unwind_codes.push(UnwindCode::SaveReg {
299
0
                        instruction_offset,
300
0
                        reg,
301
0
                        stack_offset: clobber_offset,
302
0
                    });
303
0
                }
304
0
                MappedRegister::Xmm(reg) => {
305
0
                    unwind_codes.push(UnwindCode::SaveXmm {
306
0
                        instruction_offset,
307
0
                        reg,
308
0
                        stack_offset: clobber_offset,
309
0
                    });
310
0
                }
311
            },
312
            &UnwindInst::Aarch64SetPointerAuth { .. } => {
313
0
                unreachable!("no aarch64 on x64");
314
            }
315
        }
316
0
        max_unwind_offset = instruction_offset;
317
    }
318
319
0
    Ok(UnwindInfo {
320
0
        flags: 0,
321
0
        prologue_size: max_unwind_offset,
322
0
        frame_register: Some(UNWIND_RBP_REG),
323
0
        frame_register_offset,
324
0
        unwind_codes,
325
0
    })
326
0
}
327
328
0
fn ensure_unwind_offset(offset: u32) -> CodegenResult<u8> {
329
0
    if offset > 255 {
330
0
        warn!("function prologues cannot exceed 255 bytes in size for Windows x64");
331
0
        return Err(CodegenError::CodeTooLarge);
332
0
    }
333
0
    Ok(offset as u8)
334
0
}