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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.