Rollup merge of #156973 - Darksonn:unwind-tables-module, r=nnethercote

Add uwtable annotation to modules when required

When unwind tables are enabled with `-Cforce-unwind-tables=y`, Rust will annotate all functions with the `uwtable` annotation. However, this annotation is missing on modules, which leads to incorrect unwind tables being generated by LLVM for constructors (such as `asan.module_ctor`).

This was discovered because it leads to a crash in Linux when KASAN and dynamic shadow call stack are both enabled. In this scenario, the kernel uses the unwind tables to locate the `paciasp` and `autiasp` instructions in each function and patches the machine code at boot to use the shadow call stack instructions instead. However, LLVM's AArch64PointerAuth pass emits DWARF info for `paciasp` whenever `-g` is passed, but only emits DWARF info for `autiasp` when the `uwtable` attribute is present. Since the `uwtable` annotation is missing for modules, the relevant directives are generated for only the `autiasp` instruction in `asan.module_ctor`, and not for the `paciasp` instruction. This causes the kernel's dynamic SCS logic to patch the prolouge of `asan.module_ctor`, but not the epilogue. This leads to a crash as the shadow call stack becomes unbalanced.

The fact that LLVM doesn't use the same condition for whether to emit DWARF information for both instructions may be a separate bug in LLVM.

Relevant issue: https://github.com/llvm/llvm-project/issues/188234

AI assistance was used to determine the root cause of this crash from the observed symptoms, and to write the tests. Also thanks to @samitolvanen and @maurer for debugging this issue.

Similar to this previous PR of mine: rust-lang/rust#130824
This commit is contained in:
Guillaume Gomez
2026-05-27 20:45:14 +02:00
committed by GitHub
5 changed files with 37 additions and 0 deletions
@@ -165,6 +165,8 @@ pub(crate) fn uwtable_attr(llcx: &llvm::Context, use_sync_unwind: Option<bool>)
// NOTE: We should determine if we even need async unwind tables, as they
// take have more overhead and if we can use sync unwind tables we
// probably should.
//
// Similar logic exists for the per-module uwtable annotation in `context.rs`.
let async_unwind = !use_sync_unwind.unwrap_or(false);
llvm::CreateUWTableAttr(llcx, async_unwind)
}
@@ -311,6 +311,25 @@ pub(crate) unsafe fn create_module<'ll>(
);
}
if sess.must_emit_unwind_tables() {
// This assertion checks that Max is the correct merge behavior.
// Async unwind tables are strictly more useful than sync uwtables.
const {
assert!((llvm::UWTableKind::None as u32) < (llvm::UWTableKind::Sync as u32));
assert!((llvm::UWTableKind::Sync as u32) < (llvm::UWTableKind::Async as u32));
}
llvm::add_module_flag_u32(
llmod,
llvm::ModuleFlagMergeBehavior::Max,
"uwtable",
match sess.opts.unstable_opts.use_sync_unwind {
Some(true) => llvm::UWTableKind::Sync as u32,
Some(false) | None => llvm::UWTableKind::Async as u32,
},
);
}
// Add "kcfi" module flag if KCFI is enabled. (See https://reviews.llvm.org/D119296.)
if sess.is_sanitizer_kcfi_enabled() {
llvm::add_module_flag_u32(llmod, llvm::ModuleFlagMergeBehavior::Override, "kcfi", 1);
@@ -240,6 +240,18 @@ pub(crate) enum DLLStorageClass {
DllExport = 2, // Function to be accessible from DLL.
}
/// Must match the layout of `llvm::UWTableKind`.
#[derive(Copy, Clone)]
#[repr(C)]
pub(crate) enum UWTableKind {
/// No unwind table requested
None = 0,
/// "Synchronous" unwind tables
Sync = 1,
/// "Asynchronous" unwind tables (instr precise)
Async = 2,
}
/// Must match the layout of `LLVMRustAttributeKind`.
/// Semantically a subset of the C++ enum llvm::Attribute::AttrKind,
/// though it is not ABI compatible (since it's a C++ enum)
@@ -9,3 +9,5 @@
fn foo() {
panic!();
}
// CHECK-NOT: !"uwtable"
@@ -4,3 +4,5 @@
// CHECK: attributes #{{.*}} uwtable
pub fn foo() {}
// CHECK: !{{[0-9]+}} = !{i32 7, !"uwtable", i32 2}