From a07467633ebe709a8aa70ffd1f2185e1ece7d586 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sat, 1 Mar 2025 15:50:06 +0100 Subject: [PATCH] Create personality stub function for no_std panic=abort crates **This is a very WIP state. It has no tests, no platform gates, and probably many bugs.** Every crate that may unwind (being compiled with panic=unwind) needs a reference to a personality function in its object code. The personality function is defined in `std`. For crates downstream of `std` (with `std` in the cstore), they will resolve to the personality lang item of std and reference that function from there (using its symbol_name `rust_eh_personality`). For `#![no_std]` crates, they will also reference the `rust_eh_personality` symbol (directly, not via weak lang item machinery). But when `std` is never linked into a crate graph containing panic=unwind crates (which happens on all panic=unwind default targets because of core), the symbol isn't present and we get a linker error. This PR solves this problem by injecting a stub for `rust_eh_personality` into the final link of binaries where the previous conditions were fulfilled. This is implemented in a way that's very similar to the allocator shim. Because we don't want to insta-stabilize this functionality, the stub doesn't just abort, it will first do a volatile read of the `__rust_personality_stub_is_unstable` symbol (once again similar to the allocator) to ensure that this functionality cannot be relied on without providing this symbol. Example code that now works on x86_64-unknown-linux-gnu: ```rust fn panic_handler(_: &core::panic::PanicInfo<'_>) -> ! { loop {} } unsafe extern "C" { fn write(fd: i32, buf: *const u8, count: usize); } static __rust_personality_stub_is_unstable: u8 = 0; fn main() -> i32 { // bring in some code that requires the personality function [1].sort_unstable(); let msg = b"meow\n"; unsafe { write(1, msg.as_ptr(), msg.len()); } 0 } ``` When compiled with `-Cpanic=abort`, this would previously result in a linker error because the `rust_eh_personality` symbol was not found. Now, it compiles and runs successfully. --- compiler/rustc_codegen_llvm/src/context.rs | 1 + compiler/rustc_codegen_llvm/src/lib.rs | 10 ++ .../src/personality_stub.rs | 102 ++++++++++++++++++ compiler/rustc_codegen_ssa/src/back/link.rs | 21 ++++ compiler/rustc_codegen_ssa/src/back/write.rs | 20 ++++ compiler/rustc_codegen_ssa/src/base.rs | 34 ++++++ compiler/rustc_codegen_ssa/src/lib.rs | 11 ++ .../rustc_codegen_ssa/src/traits/backend.rs | 2 + 8 files changed, 201 insertions(+) create mode 100644 compiler/rustc_codegen_llvm/src/personality_stub.rs diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index ed8426ae19746..e3815233153af 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -687,6 +687,7 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { get_fn(self, instance) } + /// Get a function pointer for the exception handling personality function. fn eh_personality(&self) -> &'ll Value { // The exception handling personality function. // diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index c88372db4913b..61b2c6c6cfec4 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -77,6 +77,7 @@ mod intrinsic; pub mod llvm; mod llvm_util; mod mono_item; +mod personality_stub; mod type_; mod type_of; mod va_arg; @@ -122,6 +123,15 @@ impl ExtraBackendMethods for LlvmCodegenBackend { } module_llvm } + fn codegen_personality_stub<'tcx>(&self, tcx: TyCtxt<'tcx>, module_name: &str) -> Self::Module { + let mut module_llvm = ModuleLlvm::new_metadata(tcx, module_name); + + unsafe { + personality_stub::codegen(tcx, &mut module_llvm, module_name); + } + + module_llvm + } fn compile_codegen_unit( &self, tcx: TyCtxt<'_>, diff --git a/compiler/rustc_codegen_llvm/src/personality_stub.rs b/compiler/rustc_codegen_llvm/src/personality_stub.rs new file mode 100644 index 0000000000000..e620962df741d --- /dev/null +++ b/compiler/rustc_codegen_llvm/src/personality_stub.rs @@ -0,0 +1,102 @@ +use rustc_middle::ty::TyCtxt; +use rustc_session::config::DebugInfo; + +use crate::common::AsCCharPtr; +use crate::llvm::{self, Context, False, Module}; +use crate::{ModuleLlvm, attributes, debuginfo}; + +const FEATURE_GATE_SYMBOL: &str = "__rust_personality_stub_is_unstable"; + +pub(crate) unsafe fn codegen(tcx: TyCtxt<'_>, module_llvm: &mut ModuleLlvm, module_name: &str) { + let llcx = &*module_llvm.llcx; + let llmod = module_llvm.llmod(); + + // rust alloc error handler + create_stub_personality(tcx, llcx, llmod); + + if tcx.sess.opts.debuginfo != DebugInfo::None { + let dbg_cx = debuginfo::CodegenUnitDebugContext::new(llmod); + debuginfo::metadata::build_compile_unit_di_node(tcx, module_name, &dbg_cx); + dbg_cx.finalize(tcx.sess); + } +} + +fn create_stub_personality(tcx: TyCtxt<'_>, llcx: &Context, llmod: &Module) { + unsafe { + let llfn = declare_stub_personality(tcx, llcx, llmod); + + let i8_ty = llvm::LLVMInt8TypeInContext(llcx); + + let feature_gate = llvm::LLVMRustGetOrInsertGlobal( + llmod, + FEATURE_GATE_SYMBOL.as_c_char_ptr(), + FEATURE_GATE_SYMBOL.len(), + i8_ty, + ); + + let (trap_fn, trap_ty) = trap_intrinsic(llcx, llmod); + + let llbb = llvm::LLVMAppendBasicBlockInContext(llcx, llfn, c"entry".as_ptr()); + let llbuilder = llvm::LLVMCreateBuilderInContext(llcx); + llvm::LLVMPositionBuilderAtEnd(llbuilder, llbb); + + let load = llvm::LLVMBuildLoad2(llbuilder, i8_ty, feature_gate, c"".as_ptr()); + llvm::LLVMSetVolatile(load, llvm::True); + + llvm::LLVMBuildCallWithOperandBundles( + llbuilder, + trap_ty, + trap_fn, + [].as_ptr(), + 0, + [].as_ptr(), + 0, + c"".as_ptr(), + ); + + llvm::LLVMBuildUnreachable(llbuilder); + + llvm::LLVMDisposeBuilder(llbuilder); + } +} + +fn declare_stub_personality<'ll>( + tcx: TyCtxt<'_>, + llcx: &'ll Context, + llmod: &'ll Module, +) -> &'ll llvm::Value { + let name = "rust_eh_personality"; + unsafe { + let no_return = llvm::AttributeKind::NoReturn.create_attr(llcx); + + let ty = llvm::LLVMFunctionType(llvm::LLVMVoidTypeInContext(llcx), [].as_ptr(), 0, False); + let llfn = llvm::LLVMRustGetOrInsertFunction(llmod, name.as_c_char_ptr(), name.len(), ty); + attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &[no_return]); + llvm::set_visibility(llfn, llvm::Visibility::from_generic(tcx.sess.default_visibility())); + + if tcx.sess.must_emit_unwind_tables() { + let uwtable = + attributes::uwtable_attr(llcx, tcx.sess.opts.unstable_opts.use_sync_unwind); + attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &[uwtable]); + } + + llfn + } +} + +fn trap_intrinsic<'ll>( + llcx: &'ll Context, + llmod: &'ll Module, +) -> (&'ll llvm::Value, &'ll llvm::Type) { + let name = "llvm.trap"; + + unsafe { + let no_return = llvm::AttributeKind::NoReturn.create_attr(llcx); + let llty = llvm::LLVMFunctionType(llvm::LLVMVoidTypeInContext(llcx), [].as_ptr(), 0, False); + let llfn = llvm::LLVMRustGetOrInsertFunction(llmod, name.as_c_char_ptr(), name.len(), llty); + llvm::SetFunctionCallConv(llfn, llvm::CCallConv); + attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &[no_return]); + + (llfn, llty) + } +} diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 5054ae561c043..eb1dce01ce67e 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -208,6 +208,9 @@ pub fn link_binary( if let Some(ref allocator_module) = codegen_results.allocator_module { remove_temps_from_module(allocator_module); } + if let Some(ref personality_stub_module) = codegen_results.personality_stub_module { + remove_temps_from_module(personality_stub_module); + } // Remove the temporary files if output goes to stdout for temp in tempfiles_for_stdout_output { @@ -333,6 +336,11 @@ fn link_rlib<'a>( if let Some(obj) = obj { ab.add_file(obj); } + let obj = + codegen_results.personality_stub_module.as_ref().and_then(|m| m.object.as_ref()); + if let Some(obj) = obj { + ab.add_file(obj); + } } } @@ -2197,6 +2205,18 @@ fn add_local_crate_allocator_objects(cmd: &mut dyn Linker, codegen_results: &Cod } } +/// Add object files for the personality stub linked once if necessary for the whole crate tree. +fn add_local_crate_personality_stub_objects( + cmd: &mut dyn Linker, + codegen_results: &CodegenResults, +) { + if let Some(obj) = + codegen_results.personality_stub_module.as_ref().and_then(|m| m.object.as_ref()) + { + cmd.add_object(obj); + } +} + /// Add object files containing metadata for the current crate. fn add_local_crate_metadata_objects( cmd: &mut dyn Linker, @@ -2380,6 +2400,7 @@ fn linker_with_args( add_local_crate_regular_objects(cmd, codegen_results); add_local_crate_metadata_objects(cmd, crate_type, codegen_results); add_local_crate_allocator_objects(cmd, codegen_results); + add_local_crate_personality_stub_objects(cmd, codegen_results); // Avoid linking to dynamic libraries unless they satisfy some undefined symbols // at the point at which they are specified on the command line. diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index c8bb229998ea3..22689896e8574 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -141,6 +141,7 @@ impl ModuleConfig { || match kind { ModuleKind::Regular => sess.opts.output_types.contains_key(&OutputType::Object), ModuleKind::Allocator => false, + ModuleKind::PersonalityStub => false, ModuleKind::Metadata => sess.opts.output_types.contains_key(&OutputType::Metadata), }; @@ -346,6 +347,7 @@ pub struct CodegenContext { pub regular_module_config: Arc, pub metadata_module_config: Arc, pub allocator_module_config: Arc, + pub personality_stub_module_config: Arc, pub tm_factory: TargetMachineFactoryFn, pub msvc_imps_needed: bool, pub is_pe_coff: bool, @@ -390,6 +392,7 @@ impl CodegenContext { ModuleKind::Regular => &self.regular_module_config, ModuleKind::Metadata => &self.metadata_module_config, ModuleKind::Allocator => &self.allocator_module_config, + ModuleKind::PersonalityStub => &self.personality_stub_module_config, } } } @@ -444,6 +447,7 @@ fn generate_lto_work( struct CompiledModules { modules: Vec, allocator_module: Option, + personality_stub_module: Option, } fn need_bitcode_in_object(tcx: TyCtxt<'_>) -> bool { @@ -481,6 +485,7 @@ pub(crate) fn start_async_codegen( let regular_config = ModuleConfig::new(ModuleKind::Regular, tcx, no_builtins); let metadata_config = ModuleConfig::new(ModuleKind::Metadata, tcx, no_builtins); let allocator_config = ModuleConfig::new(ModuleKind::Allocator, tcx, no_builtins); + let personality_stub_config = ModuleConfig::new(ModuleKind::PersonalityStub, tcx, no_builtins); let (shared_emitter, shared_emitter_main) = SharedEmitter::new(); let (codegen_worker_send, codegen_worker_receive) = channel(); @@ -495,6 +500,7 @@ pub(crate) fn start_async_codegen( Arc::new(regular_config), Arc::new(metadata_config), Arc::new(allocator_config), + Arc::new(personality_stub_config), coordinator_send.clone(), ); @@ -708,6 +714,11 @@ fn produce_final_output_artifacts( ensure_removed(sess.dcx(), path); } } + if let Some(ref personality_stub_module) = compiled_modules.personality_stub_module { + if let Some(ref path) = personality_stub_module.bytecode { + ensure_removed(sess.dcx(), path); + } + } } } @@ -1113,6 +1124,7 @@ fn start_executing_work( regular_config: Arc, metadata_config: Arc, allocator_config: Arc, + personality_stub_config: Arc, tx_to_llvm_workers: Sender>, ) -> thread::JoinHandle> { let coordinator_send = tx_to_llvm_workers; @@ -1206,6 +1218,7 @@ fn start_executing_work( regular_module_config: regular_config, metadata_module_config: metadata_config, allocator_module_config: allocator_config, + personality_stub_module_config: personality_stub_config, tm_factory: backend.target_machine_factory(tcx.sess, ol, backend_features), msvc_imps_needed: msvc_imps_needed(tcx), is_pe_coff: tcx.sess.target.is_like_windows, @@ -1371,6 +1384,7 @@ fn start_executing_work( let mut autodiff_items = Vec::new(); let mut compiled_modules = vec![]; let mut compiled_allocator_module = None; + let mut compiled_personality_stub_module = None; let mut needs_link = Vec::new(); let mut needs_fat_lto = Vec::new(); let mut needs_thin_lto = Vec::new(); @@ -1659,6 +1673,10 @@ fn start_executing_work( assert!(compiled_allocator_module.is_none()); compiled_allocator_module = Some(compiled_module); } + ModuleKind::PersonalityStub => { + assert!(compiled_personality_stub_module.is_none()); + compiled_personality_stub_module = Some(compiled_module); + } ModuleKind::Metadata => bug!("Should be handled separately"), } } @@ -1725,6 +1743,7 @@ fn start_executing_work( Ok(CompiledModules { modules: compiled_modules, allocator_module: compiled_allocator_module, + personality_stub_module: compiled_personality_stub_module, }) }) .expect("failed to spawn coordinator thread"); @@ -2088,6 +2107,7 @@ impl OngoingCodegen { modules: compiled_modules.modules, allocator_module: compiled_modules.allocator_module, + personality_stub_module: compiled_modules.personality_stub_module, metadata_module: self.metadata_module, }, work_products, diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 63f2f8fa3d177..99d1d3bc575f7 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -28,6 +28,7 @@ use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; use rustc_session::Session; use rustc_session::config::{self, CrateType, EntryFnType, OutputType}; use rustc_span::{DUMMY_SP, Symbol, sym}; +use rustc_target::spec::PanicStrategy; use rustc_trait_selection::infer::{BoundRegionConversionTime, TyCtxtInferExt}; use rustc_trait_selection::traits::{ObligationCause, ObligationCtxt}; use tracing::{debug, info}; @@ -592,6 +593,18 @@ pub fn allocator_kind_for_codegen(tcx: TyCtxt<'_>) -> Option { if any_dynamic_crate { None } else { tcx.allocator_kind(()) } } +/// Decide whether to inject an aborting stub personality function into this crate. +pub fn needs_stub_personality_function(tcx: TyCtxt<'_>) -> bool { + let missing_personality = tcx.lang_items().eh_personality().is_none(); + let is_abort = tcx.sess.panic_strategy() == PanicStrategy::Abort; + let is_not_panic_abort = tcx + .crates(()) + .iter() + .any(|cnum| tcx.required_panic_strategy(*cnum) != Some(PanicStrategy::Abort)); + let any_non_rlib = tcx.crate_types().iter().any(|ctype| *ctype != CrateType::Rlib); + missing_personality && is_abort && is_not_panic_abort && any_non_rlib +} + pub fn codegen_crate( backend: B, tcx: TyCtxt<'_>, @@ -692,6 +705,27 @@ pub fn codegen_crate( ); } + if needs_stub_personality_function(tcx) { + let llmod_id = cgu_name_builder + .build_cgu_name(LOCAL_CRATE, &["crate"], Some("personality_stub")) + .to_string(); + let module_llvm = tcx.sess.time("write_personality_stub_module", || { + backend.codegen_personality_stub(tcx, &llmod_id) + }); + + ongoing_codegen.wait_for_signal_to_codegen_item(); + ongoing_codegen.check_for_errors(tcx.sess); + + // These modules are generally cheap and won't throw off scheduling. + let cost = 0; + submit_codegened_module_to_llvm( + &backend, + &ongoing_codegen.coordinator.sender, + ModuleCodegen::new_personality_stub(llmod_id, module_llvm), + cost, + ); + } + if !autodiff_fncs.is_empty() { ongoing_codegen.submit_autodiff_items(autodiff_fncs); } diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 4e758bfdec394..7b2b37d5c2385 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -98,6 +98,15 @@ impl ModuleCodegen { } } + pub fn new_personality_stub(name: impl Into, module: M) -> Self { + Self { + name: name.into(), + module_llvm: module, + kind: ModuleKind::PersonalityStub, + thin_lto_buffer: None, + } + } + pub fn into_compiled_module( self, emit_obj: bool, @@ -165,6 +174,7 @@ pub enum ModuleKind { Regular, Metadata, Allocator, + PersonalityStub, } bitflags::bitflags! { @@ -233,6 +243,7 @@ pub struct CrateInfo { pub struct CodegenResults { pub modules: Vec, pub allocator_module: Option, + pub personality_stub_module: Option, pub metadata_module: Option, pub metadata: rustc_metadata::EncodedMetadata, pub crate_info: CrateInfo, diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs index ebcf118b90380..994b695f78e40 100644 --- a/compiler/rustc_codegen_ssa/src/traits/backend.rs +++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs @@ -108,6 +108,8 @@ pub trait ExtraBackendMethods: alloc_error_handler_kind: AllocatorKind, ) -> Self::Module; + fn codegen_personality_stub<'tcx>(&self, tcx: TyCtxt<'tcx>, module_name: &str) -> Self::Module; + /// This generates the codegen unit and returns it along with /// a `u64` giving an estimate of the unit's processing cost. fn compile_codegen_unit(