1 //! System V ABI unwind information.
2 
3 use crate::isa::unwind::UnwindInst;
4 use crate::machinst::Reg;
5 use crate::result::CodegenResult;
6 use crate::{CodegenError, binemit::CodeOffset};
7 use alloc::vec::Vec;
8 use gimli::write::{Address, FrameDescriptionEntry};
9 
10 #[cfg(feature = "enable-serde")]
11 use serde_derive::{Deserialize, Serialize};
12 
13 type Register = u16;
14 
15 /// Enumerate the errors possible in mapping Cranelift registers to their DWARF equivalent.
16 #[expect(missing_docs, reason = "self-describing variants")]
17 #[derive(Debug, PartialEq, Eq)]
18 pub 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.
26 impl std::error::Error for RegisterMappingError {}
27 
28 impl 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))]
49 pub(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 
69 impl 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 
96 impl 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.
134 pub(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))]
156 pub 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.
162 pub(crate) fn caller_sp_to_cfa_offset() -> u32 {
163     // Currently we define them to always be equal.
164     0
165 }
166 
167 pub(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 
277 impl 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 }
289