cranelift_codegen/isa/unwind/
systemv.rs

1//! System V ABI unwind information.
2
3use crate::isa::unwind::UnwindInst;
4use crate::machinst::Reg;
5use crate::result::CodegenResult;
6use crate::{CodegenError, binemit::CodeOffset};
7use alloc::vec::Vec;
8use gimli::write::{Address, FrameDescriptionEntry};
9
10#[cfg(feature = "enable-serde")]
11use serde_derive::{Deserialize, Serialize};
12
13type Register = u16;
14
15/// Enumerate the errors possible in mapping Cranelift registers to their DWARF equivalent.
16#[allow(missing_docs)]
17#[derive(Debug, PartialEq, Eq)]
18pub enum RegisterMappingError {
19    MissingBank,
20    UnsupportedArchitecture,
21    UnsupportedRegisterBank(&'static str),
22}
23
24// This is manually implementing Error and Display instead of using thiserror to reduce the amount
25// of dependencies used by Cranelift.
26impl std::error::Error for RegisterMappingError {}
27
28impl std::fmt::Display for RegisterMappingError {
29    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
30        match self {
31            RegisterMappingError::MissingBank => write!(f, "unable to find bank for register info"),
32            RegisterMappingError::UnsupportedArchitecture => write!(
33                f,
34                "register mapping is currently only implemented for x86_64"
35            ),
36            RegisterMappingError::UnsupportedRegisterBank(bank) => {
37                write!(f, "unsupported register bank: {bank}")
38            }
39        }
40    }
41}
42
43// This mirrors gimli's CallFrameInstruction, but is serializable
44// This excludes CfaExpression, Expression, ValExpression due to
45// https://github.com/gimli-rs/gimli/issues/513.
46// TODO: if gimli ever adds serialization support, remove this type
47#[derive(Clone, Debug, PartialEq, Eq)]
48#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
49pub(crate) enum CallFrameInstruction {
50    Cfa(Register, i32),
51    CfaRegister(Register),
52    CfaOffset(i32),
53    Restore(Register),
54    Undefined(Register),
55    SameValue(Register),
56    Offset(Register, i32),
57    ValOffset(Register, i32),
58    Register(Register, Register),
59    RememberState,
60    RestoreState,
61    ArgsSize(u32),
62    /// Enables or disables pointer authentication on aarch64 platforms post ARMv8.3.  This
63    /// particular item maps to gimli::ValExpression(RA_SIGN_STATE, lit0/lit1).
64    Aarch64SetPointerAuth {
65        return_addresses: bool,
66    },
67}
68
69impl From<gimli::write::CallFrameInstruction> for CallFrameInstruction {
70    fn from(cfi: gimli::write::CallFrameInstruction) -> Self {
71        use gimli::write::CallFrameInstruction;
72
73        match cfi {
74            CallFrameInstruction::Cfa(reg, offset) => Self::Cfa(reg.0, offset),
75            CallFrameInstruction::CfaRegister(reg) => Self::CfaRegister(reg.0),
76            CallFrameInstruction::CfaOffset(offset) => Self::CfaOffset(offset),
77            CallFrameInstruction::Restore(reg) => Self::Restore(reg.0),
78            CallFrameInstruction::Undefined(reg) => Self::Undefined(reg.0),
79            CallFrameInstruction::SameValue(reg) => Self::SameValue(reg.0),
80            CallFrameInstruction::Offset(reg, offset) => Self::Offset(reg.0, offset),
81            CallFrameInstruction::ValOffset(reg, offset) => Self::ValOffset(reg.0, offset),
82            CallFrameInstruction::Register(reg1, reg2) => Self::Register(reg1.0, reg2.0),
83            CallFrameInstruction::RememberState => Self::RememberState,
84            CallFrameInstruction::RestoreState => Self::RestoreState,
85            CallFrameInstruction::ArgsSize(size) => Self::ArgsSize(size),
86            _ => {
87                // Cranelift's unwind support does not generate `CallFrameInstruction`s with
88                // Expression at this moment, and it is not trivial to
89                // serialize such instructions.
90                panic!("CallFrameInstruction with Expression not supported");
91            }
92        }
93    }
94}
95
96impl From<CallFrameInstruction> for gimli::write::CallFrameInstruction {
97    fn from(cfi: CallFrameInstruction) -> gimli::write::CallFrameInstruction {
98        use CallFrameInstruction as ClifCfi;
99        use gimli::{Register, write::CallFrameInstruction as GimliCfi, write::Expression};
100
101        match cfi {
102            ClifCfi::Cfa(reg, offset) => GimliCfi::Cfa(Register(reg), offset),
103            ClifCfi::CfaRegister(reg) => GimliCfi::CfaRegister(Register(reg)),
104            ClifCfi::CfaOffset(offset) => GimliCfi::CfaOffset(offset),
105            ClifCfi::Restore(reg) => GimliCfi::Restore(Register(reg)),
106            ClifCfi::Undefined(reg) => GimliCfi::Undefined(Register(reg)),
107            ClifCfi::SameValue(reg) => GimliCfi::SameValue(Register(reg)),
108            ClifCfi::Offset(reg, offset) => GimliCfi::Offset(Register(reg), offset),
109            ClifCfi::ValOffset(reg, offset) => GimliCfi::ValOffset(Register(reg), offset),
110            ClifCfi::Register(reg1, reg2) => GimliCfi::Register(Register(reg1), Register(reg2)),
111            ClifCfi::RememberState => GimliCfi::RememberState,
112            ClifCfi::RestoreState => GimliCfi::RestoreState,
113            ClifCfi::ArgsSize(size) => GimliCfi::ArgsSize(size),
114            ClifCfi::Aarch64SetPointerAuth { return_addresses } => {
115                // To enable pointer authentication for return addresses in dwarf directives, we
116                // use a small dwarf expression that sets the value of the pseudo-register
117                // RA_SIGN_STATE (RA stands for return address) to 0 or 1. This behavior is
118                // documented in
119                // https://github.com/ARM-software/abi-aa/blob/master/aadwarf64/aadwarf64.rst#41dwarf-register-names.
120                let mut expr = Expression::new();
121                expr.op(if return_addresses {
122                    gimli::DW_OP_lit1
123                } else {
124                    gimli::DW_OP_lit0
125                });
126                const RA_SIGN_STATE: Register = Register(34);
127                GimliCfi::ValExpression(RA_SIGN_STATE, expr)
128            }
129        }
130    }
131}
132
133/// Maps UnwindInfo register to gimli's index space.
134pub(crate) trait RegisterMapper<Reg> {
135    /// Maps Reg.
136    fn map(&self, reg: Reg) -> Result<Register, RegisterMappingError>;
137    /// Gets the frame pointer register, if any.
138    fn fp(&self) -> Option<Register> {
139        None
140    }
141    /// Gets the link register, if any.
142    fn lr(&self) -> Option<Register> {
143        None
144    }
145    /// What is the offset from saved FP to saved LR?
146    fn lr_offset(&self) -> Option<u32> {
147        None
148    }
149}
150
151/// Represents unwind information for a single System V ABI function.
152///
153/// This representation is not ISA specific.
154#[derive(Clone, Debug, PartialEq, Eq)]
155#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
156pub struct UnwindInfo {
157    instructions: Vec<(u32, CallFrameInstruction)>,
158    len: u32,
159}
160
161/// Offset from the caller's SP to CFA as we define it.
162pub(crate) fn caller_sp_to_cfa_offset() -> u32 {
163    // Currently we define them to always be equal.
164    0
165}
166
167pub(crate) fn create_unwind_info_from_insts<MR: RegisterMapper<Reg>>(
168    insts: &[(CodeOffset, UnwindInst)],
169    code_len: usize,
170    mr: &MR,
171) -> CodegenResult<UnwindInfo> {
172    let mut instructions = vec![];
173
174    let mut cfa_offset = 0;
175    let mut clobber_offset_to_cfa = 0;
176    for &(instruction_offset, ref inst) in insts {
177        match inst {
178            &UnwindInst::PushFrameRegs {
179                offset_upward_to_caller_sp,
180            } => {
181                // Define CFA in terms of current SP (SP changed and we haven't
182                // set FP yet).
183                instructions.push((
184                    instruction_offset,
185                    CallFrameInstruction::CfaOffset(offset_upward_to_caller_sp as i32),
186                ));
187                // Note that we saved the old FP value on the stack.  Use of this
188                // operation implies that the target defines a FP register.
189                instructions.push((
190                    instruction_offset,
191                    CallFrameInstruction::Offset(
192                        mr.fp().unwrap(),
193                        -(offset_upward_to_caller_sp as i32),
194                    ),
195                ));
196                // If there is a link register on this architecture, note that
197                // we saved it as well.
198                if let Some(lr) = mr.lr() {
199                    instructions.push((
200                        instruction_offset,
201                        CallFrameInstruction::Offset(
202                            lr,
203                            -(offset_upward_to_caller_sp as i32)
204                                + mr.lr_offset().expect("LR offset not provided") as i32,
205                        ),
206                    ));
207                }
208            }
209            &UnwindInst::DefineNewFrame {
210                offset_upward_to_caller_sp,
211                offset_downward_to_clobbers,
212            } => {
213                // Define CFA in terms of FP. Note that we assume it was already
214                // defined correctly in terms of the current SP, and FP has just
215                // been set to the current SP, so we do not need to change the
216                // offset, only the register.  (This is done only if the target
217                // defines a frame pointer register.)
218                if let Some(fp) = mr.fp() {
219                    instructions.push((instruction_offset, CallFrameInstruction::CfaRegister(fp)));
220                }
221                // Record initial CFA offset.  This will be used with later
222                // StackAlloc calls if we do not have a frame pointer.
223                cfa_offset = offset_upward_to_caller_sp;
224                // Record distance from CFA downward to clobber area so we can
225                // express clobber offsets later in terms of CFA.
226                clobber_offset_to_cfa = offset_upward_to_caller_sp + offset_downward_to_clobbers;
227            }
228            &UnwindInst::StackAlloc { size } => {
229                // If we do not use a frame pointer, we need to update the
230                // CFA offset whenever the stack pointer changes.
231                if mr.fp().is_none() {
232                    cfa_offset += size;
233                    instructions.push((
234                        instruction_offset,
235                        CallFrameInstruction::CfaOffset(cfa_offset as i32),
236                    ));
237                }
238            }
239            &UnwindInst::SaveReg {
240                clobber_offset,
241                reg,
242            } => {
243                let reg = mr
244                    .map(reg.into())
245                    .map_err(|e| CodegenError::RegisterMappingError(e))?;
246                let off = (clobber_offset as i32) - (clobber_offset_to_cfa as i32);
247                instructions.push((instruction_offset, CallFrameInstruction::Offset(reg, off)));
248            }
249            &UnwindInst::RegStackOffset {
250                clobber_offset,
251                reg,
252            } => {
253                let reg = mr
254                    .map(reg.into())
255                    .map_err(|e| CodegenError::RegisterMappingError(e))?;
256                let off = (clobber_offset as i32) - (clobber_offset_to_cfa as i32);
257                instructions.push((
258                    instruction_offset,
259                    CallFrameInstruction::ValOffset(reg, off),
260                ));
261            }
262            &UnwindInst::Aarch64SetPointerAuth { return_addresses } => {
263                instructions.push((
264                    instruction_offset,
265                    CallFrameInstruction::Aarch64SetPointerAuth { return_addresses },
266                ));
267            }
268        }
269    }
270
271    Ok(UnwindInfo {
272        instructions,
273        len: code_len as u32,
274    })
275}
276
277impl UnwindInfo {
278    /// Converts the unwind information into a `FrameDescriptionEntry`.
279    pub fn to_fde(&self, address: Address) -> gimli::write::FrameDescriptionEntry {
280        let mut fde = FrameDescriptionEntry::new(address, self.len);
281
282        for (offset, inst) in &self.instructions {
283            fde.add_instruction(*offset, inst.clone().into());
284        }
285
286        fde
287    }
288}