From 527ba9b29893be6e49f25e069b392129d9f21cf1 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sat, 25 Oct 2025 15:53:02 +0200 Subject: [PATCH 1/2] Restructure `std::sys::personality` Move `dwarf` into `gcc`, since it's only needed there and will need to make GCC-specific assumptions in the next commit. --- library/std/src/sys/personality/gcc.rs | 5 ++++- .../std/src/sys/personality/{dwarf/mod.rs => gcc/dwarf.rs} | 2 -- library/std/src/sys/personality/{ => gcc}/dwarf/tests.rs | 0 library/std/src/sys/personality/{dwarf => gcc}/eh.rs | 2 +- library/std/src/sys/personality/mod.rs | 2 -- 5 files changed, 5 insertions(+), 6 deletions(-) rename library/std/src/sys/personality/{dwarf/mod.rs => gcc/dwarf.rs} (99%) rename library/std/src/sys/personality/{ => gcc}/dwarf/tests.rs (100%) rename library/std/src/sys/personality/{dwarf => gcc}/eh.rs (99%) diff --git a/library/std/src/sys/personality/gcc.rs b/library/std/src/sys/personality/gcc.rs index 019d5629d6d6e..fa526b2c6a4f6 100644 --- a/library/std/src/sys/personality/gcc.rs +++ b/library/std/src/sys/personality/gcc.rs @@ -37,9 +37,12 @@ //! and the last personality routine transfers control to the catch block. #![forbid(unsafe_op_in_unsafe_fn)] +mod dwarf; +mod eh; + use unwind as uw; -use super::dwarf::eh::{self, EHAction, EHContext}; +use self::eh::{EHAction, EHContext}; use crate::ffi::c_int; // Register ids were lifted from LLVM's TargetLowering::getExceptionPointerRegister() diff --git a/library/std/src/sys/personality/dwarf/mod.rs b/library/std/src/sys/personality/gcc/dwarf.rs similarity index 99% rename from library/std/src/sys/personality/dwarf/mod.rs rename to library/std/src/sys/personality/gcc/dwarf.rs index 2bc91951b49fd..579cf0625890d 100644 --- a/library/std/src/sys/personality/dwarf/mod.rs +++ b/library/std/src/sys/personality/gcc/dwarf.rs @@ -10,8 +10,6 @@ #[cfg(test)] mod tests; -pub mod eh; - pub struct DwarfReader { pub ptr: *const u8, } diff --git a/library/std/src/sys/personality/dwarf/tests.rs b/library/std/src/sys/personality/gcc/dwarf/tests.rs similarity index 100% rename from library/std/src/sys/personality/dwarf/tests.rs rename to library/std/src/sys/personality/gcc/dwarf/tests.rs diff --git a/library/std/src/sys/personality/dwarf/eh.rs b/library/std/src/sys/personality/gcc/eh.rs similarity index 99% rename from library/std/src/sys/personality/dwarf/eh.rs rename to library/std/src/sys/personality/gcc/eh.rs index ef5112ad74f13..01e6b73418466 100644 --- a/library/std/src/sys/personality/dwarf/eh.rs +++ b/library/std/src/sys/personality/gcc/eh.rs @@ -14,7 +14,7 @@ use core::ptr; -use super::DwarfReader; +use super::dwarf::DwarfReader; pub const DW_EH_PE_omit: u8 = 0xFF; pub const DW_EH_PE_absptr: u8 = 0x00; diff --git a/library/std/src/sys/personality/mod.rs b/library/std/src/sys/personality/mod.rs index eabef92244d01..c883ee86ea1c3 100644 --- a/library/std/src/sys/personality/mod.rs +++ b/library/std/src/sys/personality/mod.rs @@ -10,8 +10,6 @@ //! Additionally, ARM EHABI uses the personality function when generating //! backtraces. -mod dwarf; - #[cfg(not(any(test, doctest)))] cfg_select! { target_os = "emscripten" => { From 6b33cc6438ca4420bb9d51a1bd63a9b3d0728a3f Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sun, 26 Oct 2025 12:09:15 +0100 Subject: [PATCH 2/2] Ensure the personality does not panic In a cdylib that uses std and is free from panics in the code, the panic machinery will *still* be pulled in because of the personality function when using LLD. The personality function is used by unwinding to figure out what to do when unwinding through a function. Each function that participates in unwind has an associated FDE (frame descriptor entries) in `.eh_frame`. This FDE points to a CIE (common information entry), which can reference a language-specific personality function, like `rust_eh_personality` in our case. As long as there is a CIE that references the personality, the function cannot be removed. If all references to a CIE get removed (because the functions and their associated FDEs have been removed), LLD will not remove the CIE, likely due to the ordering of its passes). Binutils ld will remove it. In the case where the CIE is still around (despite not being used), it will still reference the personality function, so that will still be around. This is not great since it's a bunch of code, but also not _that_ much. But this is where panicking comes in. Before this change, the personality function internally made use of `dyn Fn`. This caused an indirect call that LLVM was not able to analyze as guaranteed free of unwinding, even during fat LTO. This meant that an `invoke` was used, with a landing pad. In an `extern "C"` function, which the personality function is, all landing pads call `panic_cannot_unwind`, which is a `panic_nounwind`, which is, obviously, a panic. And as a panic, it pulls in *all* the panic machinery, which is very big and sad. It is also completely unnecessary, because these indirect functions do not panic, as they are just a convenient abstraction provided from the outside. By restructuring the code to remove these indirect calls, LLVM is able to fully analyze everything and see that rust_eh_personality cannot panic, and therefore remove its landing pad. With this change, exporting a panic-free function from a cdylib will only contain the function and the personality (when linked with LLD at least, with binutils ld it will only contain the function), with no panic code being present at all, which is great. --- library/std/src/sys/personality/gcc.rs | 3 +- library/std/src/sys/personality/gcc/eh.rs | 19 ++++---- .../panic-free-cdylib/Cargo.toml | 11 +++++ tests/run-make-cargo/panic-free-cdylib/lib.rs | 5 +++ .../run-make-cargo/panic-free-cdylib/rmake.rs | 43 +++++++++++++++++++ 5 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 tests/run-make-cargo/panic-free-cdylib/Cargo.toml create mode 100644 tests/run-make-cargo/panic-free-cdylib/lib.rs create mode 100644 tests/run-make-cargo/panic-free-cdylib/rmake.rs diff --git a/library/std/src/sys/personality/gcc.rs b/library/std/src/sys/personality/gcc.rs index fa526b2c6a4f6..f391f70594c8c 100644 --- a/library/std/src/sys/personality/gcc.rs +++ b/library/std/src/sys/personality/gcc.rs @@ -335,8 +335,7 @@ unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) -> Result { - pub ip: *const u8, // Current instruction pointer - pub func_start: *const u8, // Pointer to the current function - pub get_text_start: &'a dyn Fn() -> *const u8, // Get pointer to the code section - pub get_data_start: &'a dyn Fn() -> *const u8, // Get pointer to the data section +pub struct EHContext { + pub(crate) ip: *const u8, // Current instruction pointer + pub(crate) func_start: *const u8, // Pointer to the current function + pub(crate) raw_context: *mut uw::_Unwind_Context, } /// Landing pad. @@ -63,7 +64,7 @@ pub enum EHAction { pub const USING_SJLJ_EXCEPTIONS: bool = cfg!(all(target_vendor = "apple", not(target_os = "watchos"), target_arch = "arm")); -pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>) -> Result { +pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext) -> Result { if lsda.is_null() { return Ok(EHAction::None); } @@ -224,7 +225,7 @@ unsafe fn read_encoded_offset(reader: &mut DwarfReader, encoding: u8) -> Result< /// [LSB-dwarf-ext]: https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/dwarfext.html unsafe fn read_encoded_pointer( reader: &mut DwarfReader, - context: &EHContext<'_>, + context: &EHContext, encoding: u8, ) -> Result<*const u8, ()> { if encoding == DW_EH_PE_omit { @@ -241,8 +242,8 @@ unsafe fn read_encoded_pointer( } context.func_start } - DW_EH_PE_textrel => (*context.get_text_start)(), - DW_EH_PE_datarel => (*context.get_data_start)(), + DW_EH_PE_textrel => unsafe { uw::_Unwind_GetTextRelBase(context.raw_context) }, + DW_EH_PE_datarel => unsafe { uw::_Unwind_GetDataRelBase(context.raw_context) }, // aligned means the value is aligned to the size of a pointer DW_EH_PE_aligned => { reader.ptr = reader.ptr.with_addr(round_up(reader.ptr.addr(), size_of::<*const u8>())?); diff --git a/tests/run-make-cargo/panic-free-cdylib/Cargo.toml b/tests/run-make-cargo/panic-free-cdylib/Cargo.toml new file mode 100644 index 0000000000000..b65f570a315a9 --- /dev/null +++ b/tests/run-make-cargo/panic-free-cdylib/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "add" +version = "0.1.0" +edition = "2024" + +[lib] +path = "lib.rs" +crate-type = ["cdylib"] + +[profile.release] +lto = "fat" diff --git a/tests/run-make-cargo/panic-free-cdylib/lib.rs b/tests/run-make-cargo/panic-free-cdylib/lib.rs new file mode 100644 index 0000000000000..94bcf336d71af --- /dev/null +++ b/tests/run-make-cargo/panic-free-cdylib/lib.rs @@ -0,0 +1,5 @@ +#![crate_type = "cdylib"] + +pub extern "C" fn add(a: u64, b: u64) -> u64 { + a + b +} diff --git a/tests/run-make-cargo/panic-free-cdylib/rmake.rs b/tests/run-make-cargo/panic-free-cdylib/rmake.rs new file mode 100644 index 0000000000000..f8aa18fea4497 --- /dev/null +++ b/tests/run-make-cargo/panic-free-cdylib/rmake.rs @@ -0,0 +1,43 @@ +// This ensures that a cdylib that uses std and panic=unwind but does not +// have any panics itself will not have *any* panic-related code in the final +// binary, at least when using fat LTO +// (since all the necessary nounwind propagation requires fat LTO). +// +// This code used to be pulled in via a landing pad in the personality function, +// (since that is `extern "C"` and therefore panics if something unwinds), so +// if this failed because you modified the personality function, ensure it contains +// no potentially unwinding calls. + +use run_make_support::{cargo, dynamic_lib_name, llvm_nm, path, rustc, target}; + +fn main() { + let target_dir = path("target"); + + // We use build-std to ensure that the sysroot does not have debug assertions, + // as this doesn't work with debug assertions. + cargo() + .args(&[ + "build", + "--manifest-path", + "Cargo.toml", + "--release", + "-Zbuild-std=std", + "--target", + &target(), + ]) + .env("CARGO_TARGET_DIR", &target_dir) + .env("RUSTC_BOOTSTRAP", "1") + .run(); + + let output_path = target_dir.join(target()).join("release").join(dynamic_lib_name("add")); + + llvm_nm() + .input(output_path) + .run() + // a collection of panic-related strings. if this appears in the output + // for other reasons than having panic symbols, I am sorry. + .assert_stdout_not_contains("panic") + .assert_stdout_not_contains("addr2line") + .assert_stdout_not_contains("backtrace") + .assert_stdout_not_contains("gimli"); +}