1//! Unwind info generation (`.eh_frame`)23use cranelift_codegen::FinalizedMachExceptionHandler;4use cranelift_codegen::ir::Endianness;5use cranelift_codegen::isa::unwind::UnwindInfo;6use cranelift_module::DataId;7use cranelift_object::ObjectProduct;8use gimli::write::{Address, CieId, EhFrame, FrameTable, Section};9use gimli::{Encoding, Format, RunTimeEndian};1011use super::emit::{DebugRelocName, address_for_data, address_for_func};12use super::gcc_except_table::{13 Action, ActionKind, ActionTable, CallSite, CallSiteTable, GccExceptTable, TypeInfoTable,14};15use super::object::WriteDebugInfo;16use crate::prelude::*;1718pub(crate) const EXCEPTION_HANDLER_CLEANUP: u32 = 0;19pub(crate) const EXCEPTION_HANDLER_CATCH: u32 = 1;2021pub(crate) struct UnwindContext {22 endian: RunTimeEndian,23 frame_table: FrameTable,24 cie_id: Option<CieId>,25}2627impl UnwindContext {28 pub(crate) fn new(module: &mut dyn Module, pic_eh_frame: bool) -> Self {29 let endian = match module.isa().endianness() {30 Endianness::Little => RunTimeEndian::Little,31 Endianness::Big => RunTimeEndian::Big,32 };33 let mut frame_table = FrameTable::default();3435 let cie_id = if let Some(mut cie) = module.isa().create_systemv_cie() {36 let ptr_encoding = if pic_eh_frame {37 gimli::DwEhPe(gimli::DW_EH_PE_pcrel.0 | gimli::DW_EH_PE_sdata4.0)38 } else {39 gimli::DW_EH_PE_absptr40 };4142 cie.fde_address_encoding = ptr_encoding;4344 // FIXME only add personality function and lsda when necessary: https://github.com/rust-lang/rust/blob/1f76d219c906f0112bb1872f33aa977164c53fa6/compiler/rustc_codegen_ssa/src/mir/mod.rs#L200-L20445 if cfg!(feature = "unwinding") {46 let code_ptr_encoding = if pic_eh_frame {47 if module.isa().triple().architecture == target_lexicon::Architecture::X86_64 {48 gimli::DwEhPe(49 gimli::DW_EH_PE_indirect.050 | gimli::DW_EH_PE_pcrel.051 | gimli::DW_EH_PE_sdata4.0,52 )53 } else if let target_lexicon::Architecture::Aarch64(_) =54 module.isa().triple().architecture55 {56 gimli::DwEhPe(57 gimli::DW_EH_PE_indirect.058 | gimli::DW_EH_PE_pcrel.059 | gimli::DW_EH_PE_sdata8.0,60 )61 } else {62 todo!()63 }64 } else {65 gimli::DwEhPe(gimli::DW_EH_PE_indirect.0 | gimli::DW_EH_PE_absptr.0)66 };6768 cie.lsda_encoding = Some(ptr_encoding);6970 // FIXME use eh_personality lang item instead71 let personality = module72 .declare_function(73 "rust_eh_personality",74 Linkage::Import,75 &Signature {76 params: vec![77 AbiParam::new(types::I32),78 AbiParam::new(types::I32),79 AbiParam::new(types::I64),80 AbiParam::new(module.target_config().pointer_type()),81 AbiParam::new(module.target_config().pointer_type()),82 ],83 returns: vec![AbiParam::new(types::I32)],84 call_conv: module.target_config().default_call_conv,85 },86 )87 .unwrap();8889 // Use indirection here to support PIC the case where rust_eh_personality is defined in90 // another DSO.91 let personality_ref = module92 .declare_data("DW.ref.rust_eh_personality", Linkage::Local, false, false)93 .unwrap();9495 let mut personality_ref_data = DataDescription::new();96 // Note: Must not use define_zeroinit. The unwinder can't handle this being in the .bss97 // section.98 let pointer_bytes = usize::from(module.target_config().pointer_bytes());99 personality_ref_data.define(vec![0; pointer_bytes].into_boxed_slice());100 let personality_func_ref =101 module.declare_func_in_data(personality, &mut personality_ref_data);102 personality_ref_data.write_function_addr(0, personality_func_ref);103104 module.define_data(personality_ref, &personality_ref_data).unwrap();105106 cie.personality = Some((code_ptr_encoding, address_for_data(personality_ref)));107 }108109 Some(frame_table.add_cie(cie))110 } else {111 None112 };113114 UnwindContext { endian, frame_table, cie_id }115 }116117 pub(crate) fn add_function(118 &mut self,119 module: &mut dyn Module,120 func_id: FuncId,121 context: &Context,122 ) {123 let triple = module.isa().triple();124 if matches!(triple.operating_system, target_lexicon::OperatingSystem::MacOSX { .. })125 && triple.architecture == target_lexicon::Architecture::X86_64126 {127 // The object crate doesn't currently support DW_GNU_EH_PE_absptr, which macOS128 // requires for unwinding tables. See gimli-rs/object#415.129 return;130 }131132 let Some(unwind_info) =133 context.compiled_code().unwrap().create_unwind_info(module.isa()).unwrap()134 else {135 return;136 };137138 match unwind_info {139 UnwindInfo::SystemV(unwind_info) => {140 let mut fde = unwind_info.to_fde(address_for_func(func_id));141142 // FIXME only add personality function and lsda when necessary: https://github.com/rust-lang/rust/blob/1f76d219c906f0112bb1872f33aa977164c53fa6/compiler/rustc_codegen_ssa/src/mir/mod.rs#L200-L204143 if cfg!(feature = "unwinding") {144 // FIXME use unique symbol name derived from function name145 let lsda = module.declare_anonymous_data(false, false).unwrap();146147 let encoding = Encoding {148 format: Format::Dwarf32,149 version: 1,150 address_size: module.isa().frontend_config().pointer_bytes(),151 };152153 let mut gcc_except_table_data = GccExceptTable {154 call_sites: CallSiteTable(vec![]),155 actions: ActionTable::new(),156 type_info: TypeInfoTable::new(gimli::DW_EH_PE_udata4),157 };158159 let catch_type = gcc_except_table_data.type_info.add(Address::Constant(0));160 let catch_action = gcc_except_table_data161 .actions162 .add(Action { kind: ActionKind::Catch(catch_type), next_action: None });163164 for call_site in context.compiled_code().unwrap().buffer.call_sites() {165 if call_site.exception_handlers.is_empty() {166 gcc_except_table_data.call_sites.0.push(CallSite {167 start: u64::from(call_site.ret_addr - 1),168 length: 1,169 landing_pad: 0,170 action_entry: None,171 });172 }173 for &handler in call_site.exception_handlers {174 match handler {175 FinalizedMachExceptionHandler::Tag(tag, landingpad) => {176 match tag.as_u32() {177 EXCEPTION_HANDLER_CLEANUP => {178 gcc_except_table_data.call_sites.0.push(CallSite {179 start: u64::from(call_site.ret_addr - 1),180 length: 1,181 landing_pad: u64::from(landingpad),182 action_entry: None,183 })184 }185 EXCEPTION_HANDLER_CATCH => {186 gcc_except_table_data.call_sites.0.push(CallSite {187 start: u64::from(call_site.ret_addr - 1),188 length: 1,189 landing_pad: u64::from(landingpad),190 action_entry: Some(catch_action),191 })192 }193 _ => unreachable!(),194 }195 }196 _ => unreachable!(),197 }198 }199 }200201 let mut gcc_except_table = super::emit::WriterRelocate::new(self.endian);202203 gcc_except_table_data.write(&mut gcc_except_table, encoding).unwrap();204205 let mut data = DataDescription::new();206 data.define(gcc_except_table.writer.into_vec().into_boxed_slice());207 data.set_segment_section("", ".gcc_except_table");208209 for reloc in &gcc_except_table.relocs {210 match reloc.name {211 DebugRelocName::Section(_id) => unreachable!(),212 DebugRelocName::Symbol(id) => {213 let id = id.try_into().unwrap();214 if id & 1 << 31 == 0 {215 let func_ref = module216 .declare_func_in_data(FuncId::from_u32(id), &mut data);217 data.write_function_addr(reloc.offset, func_ref);218 } else {219 let gv = module.declare_data_in_data(220 DataId::from_u32(id & !(1 << 31)),221 &mut data,222 );223 data.write_data_addr(reloc.offset, gv, 0);224 }225 }226 };227 }228229 module.define_data(lsda, &data).unwrap();230 fde.lsda = Some(address_for_data(lsda));231 }232233 self.frame_table.add_fde(self.cie_id.unwrap(), fde);234 }235 UnwindInfo::WindowsX64(_) | UnwindInfo::WindowsArm64(_) => {236 // Windows does not have debug info for its unwind info.237 }238 unwind_info => unimplemented!("{:?}", unwind_info),239 }240 }241242 pub(crate) fn emit(self, product: &mut ObjectProduct) {243 let mut eh_frame = EhFrame::from(super::emit::WriterRelocate::new(self.endian));244 self.frame_table.write_eh_frame(&mut eh_frame).unwrap();245246 if !eh_frame.0.writer.slice().is_empty() {247 let id = eh_frame.id();248 let section_id = product.add_debug_section(id, eh_frame.0.writer.into_vec());249 let mut section_map = FxHashMap::default();250 section_map.insert(id, section_id);251252 let use_section_symbol = product.object.format() != object::BinaryFormat::MachO;253 for reloc in &eh_frame.0.relocs {254 product.add_debug_reloc(§ion_map, §ion_id, reloc, use_section_symbol);255 }256 }257 }258259 #[cfg(all(feature = "jit", windows))]260 pub(crate) unsafe fn register_jit(self, _jit_module: &cranelift_jit::JITModule) {}261262 #[cfg(all(feature = "jit", not(windows)))]263 pub(crate) unsafe fn register_jit(self, jit_module: &cranelift_jit::JITModule) {264 use std::mem::ManuallyDrop;265266 let mut eh_frame = EhFrame::from(super::emit::WriterRelocate::new(self.endian));267 self.frame_table.write_eh_frame(&mut eh_frame).unwrap();268269 if eh_frame.0.writer.slice().is_empty() {270 return;271 }272273 let mut eh_frame = eh_frame.0.relocate_for_jit(jit_module);274275 // GCC expects a terminating "empty" length, so write a 0 length at the end of the table.276 eh_frame.extend(&[0, 0, 0, 0]);277278 // FIXME support unregistering unwind tables once cranelift-jit supports deallocating279 // individual functions280 let eh_frame = ManuallyDrop::new(eh_frame);281282 // =======================================================================283 // Everything after this line up to the end of the file is loosely based on284 // https://github.com/bytecodealliance/wasmtime/blob/4471a82b0c540ff48960eca6757ccce5b1b5c3e4/crates/jit/src/unwind/systemv.rs285 #[cfg(target_os = "macos")]286 unsafe {287 // On macOS, `__register_frame` takes a pointer to a single FDE288 let start = eh_frame.as_ptr();289 let end = start.add(eh_frame.len());290 let mut current = start;291292 // Walk all of the entries in the frame table and register them293 while current < end {294 let len = std::ptr::read::<u32>(current as *const u32) as usize;295296 // Skip over the CIE297 if current != start {298 __register_frame(current);299 }300301 // Move to the next table entry (+4 because the length itself is not inclusive)302 current = current.add(len + 4);303 }304 }305 #[cfg(not(target_os = "macos"))]306 {307 // On other platforms, `__register_frame` will walk the FDEs until an entry of length 0308 unsafe { __register_frame(eh_frame.as_ptr()) };309 }310 }311}312313unsafe extern "C" {314 // libunwind import315 fn __register_frame(fde: *const u8);316}
Code quality findings 23
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error
safety
unsafe-block
pub(crate) unsafe fn register_jit(self, _jit_module: &cranelift_jit::JITModule) {}
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error
safety
unsafe-block
pub(crate) unsafe fn register_jit(self, jit_module: &cranelift_jit::JITModule) {
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error
safety
unsafe-block
unsafe {
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error
safety
unsafe-block
unsafe { __register_frame(eh_frame.as_ptr()) };
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error
safety
unsafe-block
unsafe extern "C" {
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
.unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
.unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
module.define_data(personality_ref, &personality_ref_data).unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
context.compiled_code().unwrap().create_unwind_info(module.isa()).unwrap()
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
let lsda = module.declare_anonymous_data(false, false).unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
for call_site in context.compiled_code().unwrap().buffer.call_sites() {
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
gcc_except_table_data.write(&mut gcc_except_table, encoding).unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
let id = id.try_into().unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
module.define_data(lsda, &data).unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
self.frame_table.add_fde(self.cie_id.unwrap(), fde);
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
self.frame_table.write_eh_frame(&mut eh_frame).unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
self.frame_table.write_eh_frame(&mut eh_frame).unwrap();
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info
maintainability
wildcard-import
use crate::prelude::*;
Maintainability Info: `todo!()` or `unimplemented!()` macros indicate incomplete code paths that will panic at runtime if reached. Ensure these are replaced with actual logic before production use.
info
correctness
todo-unimplemented
todo!()
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info
performance
push-without-reserve
gcc_except_table_data.call_sites.0.push(CallSite {
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info
performance
push-without-reserve
gcc_except_table_data.call_sites.0.push(CallSite {
Maintainability Info: `todo!()` or `unimplemented!()` macros indicate incomplete code paths that will panic at runtime if reached. Ensure these are replaced with actual logic before production use.
info
correctness
todo-unimplemented
unwind_info => unimplemented!("{:?}", unwind_info),
Info: Use of raw pointers (*const T, *mut T) typically requires 'unsafe' blocks for dereferencing. Ensure usage is justified (FFI, low-level optimizations) and memory safety is manually upheld.
info
safety
raw-pointer
let len = std::ptr::read::<u32>(current as *const u32) as usize;