When targeting aarch64-unknown-none with -Zsanitizer=kernel-address, -Cforce-frame-pointers=yes, and -Cforce-unwind-tables=y, all user functions correctly receive "frame-pointer"="all" + uwtable attributes and emit stp x29, x30 prologues.
However, asan.module_ctor and asan.module_dtor synthesised by LLVM's missing both "frame-pointer" and uwtable — and emit str x30 (LR-only, no frame record).
Reproducing Issue
minimal_repro.rs
#![no_std]
#![allow(missing_docs)]
use core::sync::atomic::{AtomicUsize, Ordering};
#[panic_handler]
fn panic(_: &core::panic::PanicInfo<'_>) -> ! { loop {} }
static COUNTER: AtomicUsize = AtomicUsize::new(0);
#[no_mangle]
pub extern "C" fn use_global() -> usize {
COUNTER.fetch_add(1, Ordering::Relaxed)
}
Build
rustc --edition=2021 --crate-type=lib --target=aarch64-unknown-none \
-Cpanic=abort -Copt-level=2 -Crelocation-model=static \
-Cembed-bitcode=n -Clto=n -Ccodegen-units=1 \
-Csymbol-mangling-version=v0 -Zfunction-sections=n \
-Ctarget-feature="-neon" \
-Cforce-unwind-tables=y -Zuse-sync-unwind=n \
-Zbranch-protection=pac-ret -Zfixed-x18 \
-Cforce-frame-pointers=yes \
-Zsanitizer=kernel-address \
-o repro.o --emit=obj minimal_repro.rs
Check:
llvm-objdump --mattr=+v8.3a -d repro.o
Actual disassembly output
Full disassembly attached as c.
use_global — CORRECT (stp x29, x30, full frame record):
0000000000000010 <use_global>:
10: 3f 23 03 d5 paciasp
14: fd 7b bf a9 stp x29, x30, [sp, #-16]! ← CORRECT
18: fd 03 00 91 mov x29, sp
1c: 08 00 00 90 adrp x8, 0x0
20: 08 01 00 91 add x8, x8, #0
24: 00 7d 5f c8 ldxr x0, [x8]
28: 09 04 00 91 add x9, x0, #1
2c: 09 7d 0a c8 stxr w10, x9, [x8]
30: aa ff ff 35 cbnz w10, 0x24
34: fd 7b c1 a8 ldp x29, x30, [sp], #16
38: bf 23 03 d5 autiasp
3c: c0 03 5f d6 ret
asan.module_ctor (str x30, no frame record):
0000000000000000 <asan.module_ctor>:
0: 3f 23 03 d5 paciasp
4: fe 0f 1f f8 str x30, [sp, #-16]! ← Frame pointed omitted
8: 00 00 00 90 adrp x0, 0x0
c: 00 00 00 91 add x0, x0, #0
10: 21 00 80 52 mov w1, #1
14: 00 00 00 94 bl __asan_register_globals
18: fe 07 41 f8 ldr x30, [sp], #16
1c: bf 23 03 d5 autiasp
20: c0 03 5f d6 ret
asan.module_dtor
0000000000000000 <asan.module_dtor>:
0: 3f 23 03 d5 paciasp
4: fe 0f 1f f8 str x30, [sp, #-16]! ← Frame pointed omitted
8: 00 00 00 90 adrp x0, 0x0
c: 00 00 00 91 add x0, x0, #0
10: 21 00 80 52 mov w1, #1
14: 00 00 00 94 bl __asan_unregister_globals
18: fe 07 41 f8 ldr x30, [sp], #16
1c: bf 23 03 d5 autiasp
20: c0 03 5f d6 ret
disasm.s: disasm.txt
repro.o: repro_obj.txt
LLVM IR: the attribute mismatch
Comamnd used
rustc --edition=2021 --crate-type=lib --target=aarch64-unknown-none \
-Cpanic=abort -Copt-level=2 -Crelocation-model=static -Cembed-bitcode=n \
-Clto=n -Ccodegen-units=1 -Csymbol-mangling-version=v0 -Zfunction-sections=n \
-Ctarget-feature=-neon -Cforce-unwind-tables=y -Zuse-sync-unwind=n \
-Zbranch-protection=pac-ret -Zfixed-x18 -Cforce-frame-pointers=yes \
-Zsanitizer=kernel-address --emit=llvm-ir -o /tmp/asan-fp-repro/repro.ll \
./minimal_repro.rs
Full IR attached as repro.ll.
User functions use attribute groups #0 and #1, which have "frame-pointer"="all" and uwtable. The synthesised asan.module_ctor / asan.module_dtor use attribute group #2 which has only nounwind:
; asan.module_ctor — uses #2:
define internal void @asan.module_ctor() #2 {
call void @__asan_register_globals(i64 ptrtoint (ptr @0 to i64), i64 1)
ret void
}
; asan.module_dtor — uses #2:
define internal void @asan.module_dtor() #2 {
call void @__asan_unregister_globals(i64 ptrtoint (ptr @0 to i64), i64 1)
ret void
}
; User function attributes — CORRECT (has frame-pointer + uwtable):
attributes #0 = { nofree norecurse noredzone noreturn nosync nounwind
sanitize_address memory(none) uwtable "frame-pointer"="all"
"probe-stack"="inline-asm" "target-cpu"="generic"
"target-features"="+v8a,+strict-align,+neon,+fp-armv8,-neon,-fp-armv8,+reserve-x18" }
attributes #1 = { mustprogress nofree norecurse noredzone nounwind
sanitize_address willreturn
memory(readwrite, argmem: none, inaccessiblemem: none) uwtable
"frame-pointer"="all" "probe-stack"="inline-asm"
"target-cpu"="generic"
"target-features"="+v8a,+strict-align,+neon,+fp-armv8,-neon,-fp-armv8,+reserve-x18" }
; asan.module_ctor/dtor attributes — BUG (no frame-pointer, no uwtable):
attributes #2 = { nounwind }
repro.ll: repro_ll.txt
Since asan.module_ctor and asan.module_dtor lack the uwtable attribute (their attribute group 2 = { nounwind } has neither uwtable nor "frame-pointer"="all"), Backend does not emit any .eh_frame FDE (Frame Description Entry) for these functions. This means no Call Frame Information (CFI) directives are generated at all — including, critically, no DW_CFA_AARCH64_negate_ra_state.
Dwarf generated for repro.o (asan.module_ctor/asan.module_dtor)
00000014 0000001c 00000018 FDE cie=00000000 pc=00000000...00000010
Format: DWARF32
DW_CFA_advance_loc: 4 to 0x4
DW_CFA_AARCH64_negate_ra_state:
DW_CFA_advance_loc: 4 to 0x8
DW_CFA_def_cfa_offset: +16
DW_CFA_advance_loc: 4 to 0xc
DW_CFA_def_cfa: W29 +16
DW_CFA_offset: W30 -8
DW_CFA_offset: W29 -16
DW_CFA_nop:
DW_CFA_nop:
0x0: CFA=WSP
0x4: CFA=WSP: reg34=1
0x8: CFA=WSP+16: reg34=1
0xc: CFA=W29+16: W29=[CFA-16], W30=[CFA-8], reg34=1
dwarf_dump.txt
How can we force asan_ctor to use a frame pointer and generate proper unwind tables so that we get DW_CFA_AARCH64_negate_ra_state?
@vitalybuka Could you take look at this?
This issue actually breaks arm64 shadow call patching and PAC aware unwinding in Linux kernel.
When targeting aarch64-unknown-none with -Zsanitizer=kernel-address, -Cforce-frame-pointers=yes, and -Cforce-unwind-tables=y, all user functions correctly receive "frame-pointer"="all" + uwtable attributes and emit stp x29, x30 prologues.
However, asan.module_ctor and asan.module_dtor synthesised by LLVM's missing both "frame-pointer" and uwtable — and emit str x30 (LR-only, no frame record).
Reproducing Issue
minimal_repro.rs
Build
rustc --edition=2021 --crate-type=lib --target=aarch64-unknown-none \ -Cpanic=abort -Copt-level=2 -Crelocation-model=static \ -Cembed-bitcode=n -Clto=n -Ccodegen-units=1 \ -Csymbol-mangling-version=v0 -Zfunction-sections=n \ -Ctarget-feature="-neon" \ -Cforce-unwind-tables=y -Zuse-sync-unwind=n \ -Zbranch-protection=pac-ret -Zfixed-x18 \ -Cforce-frame-pointers=yes \ -Zsanitizer=kernel-address \ -o repro.o --emit=obj minimal_repro.rsCheck:
Actual disassembly output
Full disassembly attached as
c.use_global— CORRECT (stp x29, x30, full frame record):asan.module_ctor(str x30, no frame record):asan.module_dtordisasm.s: disasm.txt
repro.o: repro_obj.txt
LLVM IR: the attribute mismatch
Comamnd used
Full IR attached as
repro.ll.User functions use attribute groups
#0and#1, which have"frame-pointer"="all"anduwtable. The synthesisedasan.module_ctor/asan.module_dtoruse attribute group#2which has onlynounwind:repro.ll: repro_ll.txt
Since asan.module_ctor and asan.module_dtor lack the uwtable attribute (their attribute group 2 = { nounwind } has neither uwtable nor "frame-pointer"="all"), Backend does not emit any .eh_frame FDE (Frame Description Entry) for these functions. This means no Call Frame Information (CFI) directives are generated at all — including, critically, no DW_CFA_AARCH64_negate_ra_state.
Dwarf generated for repro.o (asan.module_ctor/asan.module_dtor)
dwarf_dump.txt
How can we force asan_ctor to use a frame pointer and generate proper unwind tables so that we get DW_CFA_AARCH64_negate_ra_state?
@vitalybuka Could you take look at this?
This issue actually breaks arm64 shadow call patching and PAC aware unwinding in Linux kernel.