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