From: Yonghong Song <yonghong.song@linux.dev>
To: Alexei Starovoitov <alexei.starovoitov@gmail.com>, bpf@vger.kernel.org
Cc: Andrii Nakryiko <andrii@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Eduard Zingerman <eddyz87@gmail.com>,
kernel-team@fb.com
Subject: Re: [PATCH bpf-next v10 00/24] bpf: Run exception cleanup landing pads when bpf_unwind() unwinds
Date: Tue, 13 Oct 2026 23:35:59 -0700 [thread overview]
Message-ID: <439c15c4-a386-4360-948f-7745860f618d@linux.dev> (raw)
In-Reply-To: <514a64b7-9d3e-473d-b85b-b25aef2be151@linux.dev>
On 10/11/26 10:30 AM, Yonghong Song wrote:
>
>
> On 10/10/26 5:15 PM, Alexei Starovoitov wrote:
>> # CARRIED OVER from the unsent v9 draft (nothing was sent on v9, v10
>> has only bot fixes). Delete this line, or the file if you dropped it
>> on purpose.
>> On Thu, Oct 08, 2026 at 09:14 PM Yonghong Song
>> <yonghong.song@linux.dev> wrote:
>>> - A subprog an unwind passes through must have an exit.
>>> - Until the Rust toolchain supports BPF exception handling, the
>>> selftests
>>> write their tables by hand in inline asm.
>> rustc can do it already.
>> For example,
>> take RUSTC_BPF from selftests/bpf/Makefile.buildvars, replace
>> panic=abort with panic=unwind, build core, then --emit=llvm-bc and
>> clang -c like Makefile.skel does for progs/*.rs.
>>
>> #[inline(never)]
>> fn fail() -> ! { unsafe { bpf_unwind() }; loop {} }
>>
>> #[inline(never)]
>> fn check(i: u32) -> u32 { if i > 7 { fail() } i }
>>
>> pub extern "C-unwind" fn prog(ctx: *const u32) -> u32 {
>> let _g = RcuReadGuard::new();
>> check(unsafe { *ctx })
>> }
>>
>> The object has .bpf_cleanup with the right record and this code:
>>
>> fail:
>> call bpf_unwind
>> L0: goto L0
>> check:
>> if w1 > 0x7 goto L0
>> w0 = w1
>> exit
>> L0: call fail
>> prog:
>> r6 = r1
>> call bpf_rcu_read_lock
>> w6 = *(u32 *)(r6 + 0x0)
>> w1 = w6
>> call check
>> call bpf_rcu_read_unlock
>> w0 = w6
>> exit
>> r6 = r0
>> call bpf_rcu_read_unlock
>> r1 = r6
>> call _Unwind_Resume
>>
>> They won't pass the verifier,
>> because
>> _Unwind_Resume() and fail() don't return, so LLVM emits nothing after
>> the call. check_subprogs() refuses prog() and check() with
>> "last insn is not an exit or jmp".
>> rust-bpf hack adds 'exit' and replaces call with 'call
>> bpf_unwind_resume'.
>
> The following is what I have:
>
> $ cat exc_review.rs
> #![no_std]
> #![no_main]
> #![feature(lang_items)]
> #![allow(internal_features)]
>
> unsafe extern "C-unwind" {
> fn bpf_unwind();
> }
>
> unsafe extern "C" {
> fn bpf_rcu_read_lock();
> fn bpf_rcu_read_unlock();
> }
>
> struct RcuReadGuard;
>
> impl RcuReadGuard {
> #[inline(always)]
> fn new() -> Self {
> unsafe { bpf_rcu_read_lock() };
> RcuReadGuard
> }
> }
>
> impl Drop for RcuReadGuard {
> #[inline(always)]
> fn drop(&mut self) {
> unsafe { bpf_rcu_read_unlock() };
> }
> }
>
> #[inline(never)]
> #[unsafe(no_mangle)]
> fn fail() -> ! {
> unsafe { bpf_unwind() };
> loop {}
> }
>
> #[inline(never)]
> #[unsafe(no_mangle)]
> fn check(i: u32) -> u32 {
> if i > 7 {
> fail()
> }
> i
> }
>
> #[unsafe(no_mangle)]
> #[unsafe(link_section = "syscall")]
> pub extern "C-unwind" fn prog(ctx: *const u32) -> u32 {
> let _g = RcuReadGuard::new();
> check(unsafe { *ctx })
> }
>
> #[panic_handler]
> fn panic(_info: &core::panic::PanicInfo) -> ! {
> loop {}
> }
>
> #[lang = "eh_personality"]
> extern "C" fn rust_eh_personality() {}
>
> #[used]
> #[unsafe(link_section = "license")]
> static LICENSE: [u8; 4] = *b"GPL\0";
>
> $ cat run.sh
> R=~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc
> CL=/home/yhs/work/llvm-project/llvm/build.23/Release/bin
>
> $R -Z unstable-options --print target-spec-json --target
> bpfel-unknown-none | \
> sed 's/"obj-is-bitcode": true/"obj-is-bitcode": false/' >
> bpfel-elf.json
> T=$PWD/bpfel-elf.json; D=$PWD/core-elf; mkdir -p $D
> SRC=$($R --print sysroot)/lib/rustlib/src/rust/library/core/src/lib.rs
> $R -Z unstable-options -Z always-encode-mir -O -C panic=unwind
> --crate-type rlib \
> --target $T -A warnings --edition 2024 --crate-name core
> --emit=metadata --out-dir $D $SRC
> echo '#![feature(compiler_builtins)] #![compiler_builtins] #![no_std]'
> | \
> $R -Z unstable-options -O -C panic=unwind --crate-type rlib
> --target $T -L $D \
> -A warnings --crate-name compiler_builtins --emit=metadata
> --out-dir $D -
>
> $R -Z unstable-options -Z function-sections=no -O -C panic=unwind
> --crate-type rlib \
> --target $T -L $D --edition 2021 -C debuginfo=2 -C target-cpu=v3 \
> --emit=asm -s exc_review.s exc_review.rs
>
> and below exc_review.s:
>
> check:
> if w1 > 7 goto .LBB1_2
> w0 = w1
> exit
> .LBB1_2:
> call fail
> fail:
> call bpf_unwind
> .LBB2_1:
> goto .LBB2_1
> prog:
> r6 = r1
> call bpf_rcu_read_lock
> w6 = *(u32 *)(r6 + 0)
> w1 = w6
> call check
> call bpf_rcu_read_unlock
> w0 = w6
> exit
> r6 = r0
> call bpf_rcu_read_unlock
> r1 = r6
> call _Unwind_Resume
>
> So it pretty similar to the above.
>
> However, we have a big issue for BTF.
>
> $ bpftool btf dump file exc_review.bpf.o
> [1] PTR '(anon)' type_id=2
> [2] STRUCT 'PanicInfo' size=24 vlen=4
> 'message' type_id=3 bits_offset=0
> 'location' type_id=4 bits_offset=64
> 'can_unwind' type_id=5 bits_offset=128
> 'force_no_backtrace' type_id=5 bits_offset=136
> [3] PTR '(anon)' type_id=23
> [4] PTR '(anon)' type_id=24
> [5] INT 'bool' size=1 bits_offset=0 nr_bits=8 encoding=BOOL
> [6] FUNC_PROTO '(anon)' ret_type_id=0 vlen=1
> '_info' type_id=1
> [7] FUNC 'panic' type_id=6 linkage=global
> [8] INT 'u32' size=4 bits_offset=0 nr_bits=32 encoding=(none)
> [9] FUNC_PROTO '(anon)' ret_type_id=8 vlen=1
> 'i' type_id=8
> [10] FUNC 'check' type_id=9 linkage=global
> [11] FUNC_PROTO '(anon)' ret_type_id=0 vlen=0
> [12] FUNC 'fail' type_id=11 linkage=global
> [13] PTR '(anon)' type_id=8
> [14] FUNC_PROTO '(anon)' ret_type_id=8 vlen=1
> 'ctx' type_id=13
> [15] FUNC 'prog' type_id=14 linkage=global
> [16] FUNC_PROTO '(anon)' ret_type_id=0 vlen=0
> [17] FUNC 'rust_eh_personality' type_id=16 linkage=global
> [18] INT 'u8' size=1 bits_offset=0 nr_bits=8 encoding=(none)
> [19] ARRAY '(anon)' type_id=18 index_type_id=20 nr_elems=4
> [20] INT '__ARRAY_SIZE_TYPE__' size=4 bits_offset=0 nr_bits=32
> encoding=(none)
> [21] VAR '_RNvCs63bSbDYK0Bo_10exc_review7LICENSE' type_id=19,
> linkage=global
> [22] DATASEC 'license' size=0 vlen=1
> type_id=21 offset=0 size=4 (VAR
> '_RNvCs63bSbDYK0Bo_10exc_review7LICENSE')
> [23] FWD 'Arguments' fwd_kind=struct
> [24] FWD 'Location' fwd_kind=struct
>
> There is no BTF for bpf_unwind, bpf_rcu_read_lock, _Unwind_Resume, etc.
>
> In llvm, we have two commits in clang frontend to allow generate
> extern functions
> BTF. See below:
> commit e3d8ee35e4ad: reland "[DebugInfo] Support to emit debugInfo
> for extern variables"
> commit a2a3ca8d9796: BPF: emit debuginfo for Function of DeclRefExpr
> if requested
>
> I think the best way is for rustc supports the above two commits.
I hacked rust compiler (https://github.com/rust-lang/rust) and the rust compiler
is able to support to emit btf for extern variables.
The following is a portion of rust example code. Some of them could be simplified
with macro, e.g., for link_section '.ksyms'...
...
unsafe extern "C-unwind" {
#[link_section = ".ksyms"]
fn bpf_unwind() -> !;
}
unsafe extern "C" {
#[link_section = ".ksyms"]
fn bpf_rcu_read_lock();
#[link_section = ".ksyms"]
fn bpf_rcu_read_unlock();
#[link_section = ".ksyms"]
fn bpf_preempt_disable();
#[link_section = ".ksyms"]
fn bpf_preempt_enable();
#[link_section = ".ksyms"]
fn bpf_local_irq_save(flags: *mut u64);
#[link_section = ".ksyms"]
fn bpf_local_irq_restore(flags: *mut u64);
}
// LLVM ends each cleanup landing pad with a call to _Unwind_Resume, and libbpf maps that
// extern onto the bpf_unwind_resume kfunc, so it needs BTF in .ksyms like the rest. The
// call reuses this declaration when the module already has one, but rustc only declares
// an extern that is used, and nothing in Rust calls it; the static keeps it referenced.
unsafe extern "C" {
#[link_section = ".ksyms"]
fn _Unwind_Resume(exception: *mut c_void);
}
#[used]
static UNWIND_RESUME_ANCHOR: unsafe extern "C" fn(*mut c_void) = _Unwind_Resume;
// One bit per landing-pad Drop. Must match prog_tests/rust_exceptions.c in the kernel tree,
// which runs this program and reads PADS_RAN back out of .bss.
const RAN_FOO3_PREEMPT: u64 = 0x1;
const RAN_FOO2_RCU: u64 = 0x2;
const RAN_FOO2_TRACKER: u64 = 0x4;
const RAN_FOO3_IRQ: u64 = 0x8;
#[no_mangle]
pub static mut PADS_RAN: u64 = 0;
// Volatile, because nothing in the program reads PADS_RAN back: plain stores would be dead.
#[inline(always)]
fn pad_ran(bit: u64) {
unsafe {
let p = &raw mut PADS_RAN;
write_volatile(p, *p | bit);
}
}
// -- RAII guards --
struct PreemptGuard;
impl PreemptGuard {
#[inline(always)]
fn disable() -> PreemptGuard {
unsafe { bpf_preempt_disable() };
PreemptGuard
}
}
impl Drop for PreemptGuard {
#[inline(always)]
fn drop(&mut self) {
unsafe { bpf_preempt_enable() };
pad_ran(RAN_FOO3_PREEMPT);
}
}
// The verifier tracks the saved flags by their stack slot, so the owning frame
// builds the guard in place rather than receiving it from a constructor.
struct IrqGuard {
flags: u64,
}
impl IrqGuard {
#[inline(always)]
fn save(&mut self) {
unsafe { bpf_local_irq_save(&mut self.flags) };
}
}
impl Drop for IrqGuard {
#[inline(always)]
fn drop(&mut self) {
unsafe { bpf_local_irq_restore(&mut self.flags) };
pad_ran(RAN_FOO3_IRQ);
}
}
...
// -- level 2: two invoke regions sharing one landing pad --
#[inline(never)]
#[no_mangle]
pub extern "C-unwind" fn foo2(fail3: bool, fail2: bool, val: u64) -> u64 {
let _tracker = Tracker;
let _rcu = RcuGuard::lock();
let r = foo3(fail3, val);
if fail2 {
unsafe { bpf_unwind() };
}
black_box(r)
}
// -- level 1: no resources, so no .bpf_cleanup entry --
#[inline(never)]
#[no_mangle]
pub extern "C-unwind" fn foo1(fail3: bool, fail2: bool, val: u64) -> u64 {
foo2(fail3, fail2, val)
}
#[unsafe(link_section = "syscall")]
#[no_mangle]
pub extern "C-unwind" fn entry(_ctx: *mut u8) -> i32 {
foo1(black_box(true), black_box(false), black_box(101)) as i32
}
// The verifier only lets a GPL-compatible program call kfuncs.
#[unsafe(link_section = "license")]
#[no_mangle]
static _LICENSE: [u8; 4] = [b'G', b'P', b'L', 0];
// -- unwinding runtime bits --
// Referenced by every function with a landing pad; never called on BPF, where
// the kernel dispatches to the pads itself.
#[lang = "eh_personality"]
extern "C" fn rust_eh_personality() {}
#[lang = "panic_in_cleanup"]
fn panic_in_cleanup() -> ! {
loop {}
}
The compilation is all from rustc.
The following is the objdump dump:
Disassembly of section .text:
0000000000000000 <foo1>:
; foo2(fail3, fail2, val)
0: 85 10 00 00 ff ff ff ff call -0x1
0000000000000000: R_BPF_64_32 foo2
; }
1: 95 00 00 00 00 00 00 00 exit
0000000000000010 <foo2>:
; pub extern "C-unwind" fn foo2(fail3: bool, fail2: bool, val: u64) -> u64 {
2: bf 37 00 00 00 00 00 00 r7 = r3
3: bf 26 00 00 00 00 00 00 r6 = r2
4: bf 18 00 00 00 00 00 00 r8 = r1
; unsafe { bpf_rcu_read_lock() };
5: 85 10 00 00 ff ff ff ff call -0x1
0000000000000028: R_BPF_64_32 bpf_rcu_read_lock
; let r = foo3(fail3, val);
6: bf 81 00 00 00 00 00 00 r1 = r8
7: bf 72 00 00 00 00 00 00 r2 = r7
8: 85 10 00 00 ff ff ff ff call -0x1
0000000000000040: R_BPF_64_32 foo3
; if fail2 {
9: 15 06 02 00 00 00 00 00 if r6 == 0x0 goto +0x2 <L0>
; unsafe { bpf_unwind() };
10: 85 10 00 00 ff ff ff ff call -0x1
0000000000000050: R_BPF_64_32 bpf_unwind
11: 85 10 00 00 ff ff ff ff call -0x1
0000000000000058: R_BPF_64_32 __bpf_trap
<L0>:
; unsafe { black_box(dummy) }
12: 7b 0a f8 ff 00 00 00 00 *(u64 *)(r10 - 0x8) = r0
13: bf a1 00 00 00 00 00 00 r1 = r10
14: 07 01 00 00 f8 ff ff ff r1 += -0x8
; unsafe { bpf_rcu_read_unlock() };
15: 85 10 00 00 ff ff ff ff call -0x1
0000000000000078: R_BPF_64_32 bpf_rcu_read_unlock
; write_volatile(p, *p | bit);
16: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll
0000000000000080: R_BPF_64_64 PADS_RAN
18: 79 12 00 00 00 00 00 00 r2 = *(u64 *)(r1 + 0x0)
19: bf 23 00 00 00 00 00 00 r3 = r2
20: 47 03 00 00 02 00 00 00 r3 |= 0x2
; unsafe { volatile_store(dst, src) };
21: 7b 31 00 00 00 00 00 00 *(u64 *)(r1 + 0x0) = r3
; write_volatile(p, *p | bit);
22: 47 02 00 00 06 00 00 00 r2 |= 0x6
; unsafe { volatile_store(dst, src) };
23: 7b 21 00 00 00 00 00 00 *(u64 *)(r1 + 0x0) = r2
; }
24: 79 a0 f8 ff 00 00 00 00 r0 = *(u64 *)(r10 - 0x8)
25: 95 00 00 00 00 00 00 00 exit
26: bf 06 00 00 00 00 00 00 r6 = r0
; unsafe { bpf_rcu_read_unlock() };
27: 85 10 00 00 ff ff ff ff call -0x1
00000000000000d8: R_BPF_64_32 bpf_rcu_read_unlock
; write_volatile(p, *p | bit);
28: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll
00000000000000e0: R_BPF_64_64 PADS_RAN
30: 79 12 00 00 00 00 00 00 r2 = *(u64 *)(r1 + 0x0)
31: bf 23 00 00 00 00 00 00 r3 = r2
32: 47 03 00 00 02 00 00 00 r3 |= 0x2
; unsafe { volatile_store(dst, src) };
33: 7b 31 00 00 00 00 00 00 *(u64 *)(r1 + 0x0) = r3
; write_volatile(p, *p | bit);
34: 47 02 00 00 06 00 00 00 r2 |= 0x6
; unsafe { volatile_store(dst, src) };
35: 7b 21 00 00 00 00 00 00 *(u64 *)(r1 + 0x0) = r2
36: bf 61 00 00 00 00 00 00 r1 = r6
37: 85 10 00 00 ff ff ff ff call -0x1
0000000000000128: R_BPF_64_32 _Unwind_Resume
0000000000000130 <foo3>:
; pub extern "C-unwind" fn foo3(fail: bool, val: u64) -> u64 {
38: bf 26 00 00 00 00 00 00 r6 = r2
39: bf 17 00 00 00 00 00 00 r7 = r1
; unsafe { bpf_preempt_disable() };
40: 85 10 00 00 ff ff ff ff call -0x1
0000000000000140: R_BPF_64_32 bpf_preempt_disable
...
; unsafe { volatile_store(dst, src) };
78: 7b 17 00 00 00 00 00 00 *(u64 *)(r7 + 0x0) = r1
79: bf 61 00 00 00 00 00 00 r1 = r6
80: 85 10 00 00 ff ff ff ff call -0x1
0000000000000280: R_BPF_64_32 _Unwind_Resume
0000000000000288 <rust_eh_personality>:
; extern "C" fn rust_eh_personality() {}
81: 95 00 00 00 00 00 00 00 exit
Disassembly of section syscall:
0000000000000000 <entry>:
; unsafe { black_box(dummy) }
0: b7 01 00 00 01 00 00 00 r1 = 0x1
1: 73 1a f6 ff 00 00 00 00 *(u8 *)(r10 - 0xa) = w1
2: bf a1 00 00 00 00 00 00 r1 = r10
3: 07 01 00 00 f6 ff ff ff r1 += -0xa
4: b7 01 00 00 00 00 00 00 r1 = 0x0
5: 73 1a f7 ff 00 00 00 00 *(u8 *)(r10 - 0x9) = w1
6: bf a1 00 00 00 00 00 00 r1 = r10
7: 07 01 00 00 f7 ff ff ff r1 += -0x9
8: b7 01 00 00 65 00 00 00 r1 = 0x65
9: 7b 1a f8 ff 00 00 00 00 *(u64 *)(r10 - 0x8) = r1
10: bf a1 00 00 00 00 00 00 r1 = r10
11: 07 01 00 00 f8 ff ff ff r1 += -0x8
; foo1(black_box(true), black_box(false), black_box(101)) as i32
12: 79 a3 f8 ff 00 00 00 00 r3 = *(u64 *)(r10 - 0x8)
13: 71 a2 f7 ff 00 00 00 00 w2 = *(u8 *)(r10 - 0x9)
14: 71 a1 f6 ff 00 00 00 00 w1 = *(u8 *)(r10 - 0xa)
15: 85 10 00 00 ff ff ff ff call -0x1
0000000000000078: R_BPF_64_32 foo1
; }
16: 95 00 00 00 00 00 00 00 exit
$ bpftool btf
dump file exc_cleanup.o
[1] PTR '(anon)' type_id=2
[2] INT 'u8' size=1 bits_offset=0 nr_bits=8 encoding=(none)
[3] FUNC_PROTO '(anon)' ret_type_id=4 vlen=1
'_ctx' type_id=1
[4] INT 'i32' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
[5] FUNC 'entry' type_id=3 linkage=global
[6] INT 'bool' size=1 bits_offset=0 nr_bits=8 encoding=BOOL
[7] INT 'u64' size=8 bits_offset=0 nr_bits=64 encoding=(none)
[8] FUNC_PROTO '(anon)' ret_type_id=7 vlen=3
'fail3' type_id=6
'fail2' type_id=6
'val' type_id=7
[9] FUNC 'foo1' type_id=8 linkage=global
[10] FUNC_PROTO '(anon)' ret_type_id=7 vlen=3
'fail3' type_id=6
'fail2' type_id=6
'val' type_id=7
[11] FUNC 'foo2' type_id=10 linkage=global
[12] FUNC_PROTO '(anon)' ret_type_id=0 vlen=0
[13] FUNC 'bpf_rcu_read_lock' type_id=12 linkage=extern
[14] FUNC_PROTO '(anon)' ret_type_id=0 vlen=0
[15] FUNC 'bpf_unwind' type_id=14 linkage=extern
[16] FUNC_PROTO '(anon)' ret_type_id=0 vlen=0
[17] FUNC '__bpf_trap' type_id=16 linkage=extern
[18] FUNC_PROTO '(anon)' ret_type_id=0 vlen=0
[19] FUNC 'bpf_rcu_read_unlock' type_id=18 linkage=extern
[20] FUNC_PROTO '(anon)' ret_type_id=0 vlen=1
'(anon)' type_id=21
[21] PTR '(anon)' type_id=0
[22] FUNC '_Unwind_Resume' type_id=20 linkage=extern
[23] FUNC_PROTO '(anon)' ret_type_id=7 vlen=2
'fail' type_id=6
'val' type_id=7
[24] FUNC 'foo3' type_id=23 linkage=global
[25] FUNC_PROTO '(anon)' ret_type_id=0 vlen=0
[26] FUNC 'bpf_preempt_disable' type_id=25 linkage=extern
[27] FUNC_PROTO '(anon)' ret_type_id=0 vlen=1
'(anon)' type_id=28
[28] PTR '(anon)' type_id=7
[29] FUNC 'bpf_local_irq_save' type_id=27 linkage=extern
[30] FUNC_PROTO '(anon)' ret_type_id=0 vlen=1
'(anon)' type_id=28
[31] FUNC 'bpf_local_irq_restore' type_id=30 linkage=extern
[32] FUNC_PROTO '(anon)' ret_type_id=0 vlen=0
[33] FUNC 'bpf_preempt_enable' type_id=32 linkage=extern
[34] FUNC_PROTO '(anon)' ret_type_id=0 vlen=0
[35] FUNC 'rust_eh_personality' type_id=34 linkage=global
[36] ARRAY '(anon)' type_id=2 index_type_id=37 nr_elems=4
[37] INT '__ARRAY_SIZE_TYPE__' size=4 bits_offset=0 nr_bits=32 encoding=(none)
[38] VAR '_LICENSE' type_id=36, linkage=global
[39] PTR '(anon)' type_id=20
[40] VAR '_RNvCsl8ViJ65pEud_19bpf_exc_cleanup_btf20UNWIND_RESUME_ANCHOR' type_id=39, linkage=global
[41] VAR 'PADS_RAN' type_id=7, linkage=global
[42] DATASEC '.bss' size=0 vlen=1
type_id=41 offset=0 size=8 (VAR 'PADS_RAN')
[43] DATASEC '.data.rel.ro._RNvCsl8ViJ65pEud_19bpf_exc_cleanup_btf20UNWIND_RESUME_ANCHOR' size=0 vlen=1
type_id=40 offset=0 size=8 (VAR '_RNvCsl8ViJ65pEud_19bpf_exc_cleanup_btf20UNWIND_RESUME_ANCHOR')
[44] DATASEC '.ksyms' size=0 vlen=9
type_id=13 offset=0 size=0 (FUNC 'bpf_rcu_read_lock')
type_id=15 offset=0 size=0 (FUNC 'bpf_unwind')
type_id=17 offset=0 size=0 (FUNC '__bpf_trap')
type_id=19 offset=0 size=0 (FUNC 'bpf_rcu_read_unlock')
type_id=22 offset=0 size=0 (FUNC '_Unwind_Resume')
type_id=26 offset=0 size=0 (FUNC 'bpf_preempt_disable')
type_id=29 offset=0 size=0 (FUNC 'bpf_local_irq_save')
type_id=31 offset=0 size=0 (FUNC 'bpf_local_irq_restore')
type_id=33 offset=0 size=0 (FUNC 'bpf_preempt_enable')
[45] DATASEC 'license' size=0 vlen=1
type_id=38 offset=0 size=4 (VAR '_LICENSE')
And I tried the following user space check:
+#define RUST_OBJ_FILE "progs_rust/exc_cleanup.o"
+
+/* exc_cleanup.rs: one bit per Drop, innermost frame first. */
+#define RUST_RAN_FOO3_PREEMPT 0x1
+#define RUST_RAN_FOO2_RCU 0x2
+#define RUST_RAN_FOO2_TRACKER 0x4
+#define RUST_RAN_FOO3_IRQ 0x8
+
+#define RUST_PADS_RAN \
+ (RUST_RAN_FOO3_PREEMPT | RUST_RAN_FOO2_RCU | RUST_RAN_FOO2_TRACKER | \
+ RUST_RAN_FOO3_IRQ)
+
+void test_rust_exceptions(void)
+{
+ char log[8192] = {};
+ LIBBPF_OPTS(bpf_object_open_opts, opts,
+ .kernel_log_buf = log,
+ .kernel_log_size = sizeof(log));
+ struct bpf_program *prog;
+ struct bpf_object *obj;
+ struct bpf_map *bss;
+ __u64 pads_ran, ctx = 0;
+ __u32 key = 0;
+ int err;
+
+ LIBBPF_OPTS(bpf_test_run_opts, topts,
+ .ctx_in = &ctx,
+ .ctx_size_in = sizeof(ctx),
+ );
+
+ if (access(RUST_OBJ_FILE, R_OK)) {
+ printf("%s:SKIP: no %s, see the comment in %s\n", __func__,
+ RUST_OBJ_FILE, __FILE__);
+ test__skip();
+ return;
+ }
+
+ obj = bpf_object__open_file(RUST_OBJ_FILE, &opts);
+ if (!ASSERT_OK_PTR(obj, "open"))
+ return;
+
+ err = bpf_object__load(obj);
+ if (err) {
+ if (err == -EOPNOTSUPP &&
+ strstr(log, "exception cleanup needs a JIT that can dispatch landing pads"))
+ test__skip();
+ else if (!ASSERT_OK(err, "load"))
+ fprintf(stderr, "%s", log);
+ goto out;
+ }
+
+ prog = bpf_object__find_program_by_name(obj, "entry");
+ if (!ASSERT_OK_PTR(prog, "find entry"))
+ goto out;
+
+ /* PADS_RAN is the whole of the object's .bss. */
+ bss = bpf_object__find_map_by_name(obj, ".bss");
+ if (!ASSERT_OK_PTR(bss, "find .bss"))
+ goto out;
+
+ err = bpf_prog_test_run_opts(bpf_program__fd(prog), &topts);
+ if (!ASSERT_OK(err, "run"))
+ goto out;
+
+ /* The unwind leaves entry too, and an unwound program returns 0. */
+ ASSERT_EQ(topts.retval, 0, "retval");
+
+ err = bpf_map__lookup_elem(bss, &key, sizeof(key), &pads_ran,
+ sizeof(pads_ran), 0);
+ if (!ASSERT_OK(err, "lookup PADS_RAN"))
+ goto out;
+ ASSERT_EQ(pads_ran, RUST_PADS_RAN, "pads_ran");
+out:
+ bpf_object__close(obj);
+}
And it works properly.
>
>> We need to get rid of rust-bpf IR rewrite,
>> so let's try to do so now?
Yes, currently I hacked into rustc compiler with AI help. Will need to
understand better for this.
>
> Yes, the best way is for rustc to support generating proper debuginfo
> for extern variables.
>
> Alternatively, we use use __kfunc_btf_anchor() thing to force btf
> generation where
> __kfunc_btf_anchor() has been used in various bpf selftests.
> Use clang to generate a .bc files (with __kfunc_btf_anchor()) and then
> merge (through llvm-link)
> this .bc file and another .bc file generated from rustc and then
> finally clang to
> generate .s or .o file. But this seems still a hack. See
> exceptions_cleanup.c, exceptions_cleanup_fail.c and
> exceptions_cleanup_shapes.c
>
>
>> 'call _Unwind_Resume' can be replaced by LLVM, by libbpf, or by the
>> verifier
>> with an exit. I feel that the verifier is right place to do it,
>> but open to suggestions.
I agree, using bpf_unwind_resume() in kernel is a better choice. In libbpf,
we just simply change _Unwind_Resume names to bpf_unwind_resume() to satisfy log's.
>
> Yes, _Unwind_Resume can be replaced with bpf_unwind_resume() kfunc.
> I think we should do libbpf or verifier since llvm has used
> _Unwind_Resume
> for x86_64, arm64, riscv, powerPC, loongArch, etc.
>
> Currently my patch already used bpf_unwind_resume() in the kernel. In
> libbpf,
> a little bit code to rename _Unwind_Resume to bpf_unwind_rename for
> some logs.
>
>>
>> but since rustc doesn't emit debug info for extern fn, so there is no
>> BTF
>> for _Unwind_Resume, so libbpf may be a better place?
>> Not sure, let's discuss.
Yes, I hacked in rustc and it seems working. I will continue to explore.
>
> The best option is for rustc to support generating extern func debug
> info.
> But if that is not possible, the libbpf will need to generate btf and
> amend
> them to the prog btf before loading.
>
>>
>> pw-bot: cr
>
>
prev parent reply other threads:[~2026-10-14 6:36 UTC|newest]
Thread overview: 49+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-09 4:14 [PATCH bpf-next v10 00/24] bpf: Run exception cleanup landing pads when bpf_unwind() unwinds Yonghong Song
2026-10-09 4:14 ` [PATCH bpf-next v10 01/24] bpf: Pack bpf_insn_aux_data flags into bit fields Yonghong Song
2026-10-09 4:14 ` [PATCH bpf-next v10 02/24] bpf: Accept the compiler's exception cleanup table at program load Yonghong Song
2026-10-09 4:15 ` [PATCH bpf-next v10 03/24] bpf: Add the bpf_unwind() and bpf_unwind_resume() kfuncs Yonghong Song
2026-10-09 4:15 ` [PATCH bpf-next v10 04/24] bpf: Keep a call site's landing pad in insn_aux_data, add lookups Yonghong Song
2026-10-09 4:28 ` sashiko-bot
2026-10-09 15:10 ` Yonghong Song
2026-10-09 4:15 ` [PATCH bpf-next v10 05/24] bpf: Mark covered call sites and check a program can take a table Yonghong Song
2026-10-09 4:34 ` sashiko-bot
2026-10-09 15:13 ` Yonghong Song
2026-10-09 4:15 ` [PATCH bpf-next v10 06/24] bpf: Make exception landing pads reachable in the CFG Yonghong Song
2026-10-09 4:15 ` [PATCH bpf-next v10 07/24] bpf: Verify an unwind through landing pads and epilogues Yonghong Song
2026-10-09 4:40 ` sashiko-bot
2026-10-09 15:24 ` Yonghong Song
2026-10-09 5:13 ` bot+bpf-ci
2026-10-09 15:25 ` Yonghong Song
2026-10-09 4:15 ` [PATCH bpf-next v10 08/24] bpf: Refuse a callback that can unwind Yonghong Song
2026-10-09 4:15 ` [PATCH bpf-next v10 09/24] bpf: Refuse a landing pad that does not resume Yonghong Song
2026-10-10 15:08 ` Alexei Starovoitov
2026-10-11 18:26 ` Yonghong Song
2026-10-09 4:15 ` [PATCH bpf-next v10 10/24] bpf: Do not use a private stack for a program that can unwind Yonghong Song
2026-10-09 4:45 ` sashiko-bot
2026-10-09 15:38 ` Yonghong Song
2026-10-09 4:15 ` [PATCH bpf-next v10 11/24] bpf: Prepare JITed programs for dispatching cleanup pads Yonghong Song
2026-10-09 4:35 ` sashiko-bot
2026-10-09 16:12 ` Yonghong Song
2026-10-09 5:13 ` bot+bpf-ci
2026-10-09 16:39 ` Yonghong Song
2026-10-09 4:15 ` [PATCH bpf-next v10 12/24] bpf: Dispatch cleanup pads by rewriting return addresses Yonghong Song
2026-10-09 4:15 ` [PATCH bpf-next v10 13/24] bpf: Refuse a trampoline that calls a subprog that can unwind Yonghong Song
2026-10-09 4:15 ` [PATCH bpf-next v10 14/24] bpf, x86: Dispatch exception cleanup pads at run time Yonghong Song
2026-10-09 4:32 ` sashiko-bot
2026-10-09 16:51 ` Yonghong Song
2026-10-09 4:16 ` [PATCH bpf-next v10 15/24] bpf, arm64: " Yonghong Song
2026-10-09 4:30 ` sashiko-bot
2026-10-09 16:55 ` Yonghong Song
2026-10-09 4:16 ` [PATCH bpf-next v10 16/24] libbpf: Resolve the compiler's _Unwind_Resume to the kernel's kfunc Yonghong Song
2026-10-09 4:16 ` [PATCH bpf-next v10 17/24] libbpf: Add cleanup_info to bpf_prog_load_opts Yonghong Song
2026-10-09 4:26 ` sashiko-bot
2026-10-09 4:16 ` [PATCH bpf-next v10 18/24] libbpf: Collect .bpf_cleanup records and pass them to the kernel Yonghong Song
2026-10-09 4:16 ` [PATCH bpf-next v10 19/24] libbpf: Carry the exception cleanup table through the light skeleton Yonghong Song
2026-10-09 4:16 ` [PATCH bpf-next v10 20/24] libbpf: Let the static linker carry .bpf_cleanup relocations Yonghong Song
2026-10-09 4:16 ` [PATCH bpf-next v10 21/24] selftests/bpf: Add end-to-end and negative .bpf_cleanup exception tests Yonghong Song
2026-10-09 4:16 ` [PATCH bpf-next v10 22/24] selftests/bpf: Add __set_global() and __ret_global() test tags Yonghong Song
2026-10-09 4:16 ` [PATCH bpf-next v10 23/24] selftests/bpf: Cover more accepted .bpf_cleanup exception shapes Yonghong Song
2026-10-09 4:16 ` [PATCH bpf-next v10 24/24] selftests/bpf: Load an exception cleanup program from a light skeleton Yonghong Song
2026-10-10 15:15 ` [PATCH bpf-next v10 00/24] bpf: Run exception cleanup landing pads when bpf_unwind() unwinds Alexei Starovoitov
2026-10-11 17:30 ` Yonghong Song
2026-10-14 6:35 ` Yonghong Song [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=439c15c4-a386-4360-948f-7745860f618d@linux.dev \
--to=yonghong.song@linux.dev \
--cc=alexei.starovoitov@gmail.com \
--cc=andrii@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=kernel-team@fb.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox