freenode
Kernel & Low-Level

BPF exception unwinds gain cleanup pads for Rust Drop

Yonghong Song’s bpf-next series makes bpf_throw() run compiler-emitted landing pads so resource-owning frames can release locks and values on the way out.

A 21-part series on the BPF kernel list teaches the verifier and runtime to honor exception cleanup landing pads when bpf_throw() unwinds the call stack. The change is the kernel half of support already landing in LLVM 23, and it is aimed squarely at letting Rust BPF programs use throw as a panic path without leaking resources.

Until now, bpf_throw() walked to the exception boundary and discarded every frame in between. Anything a frame still owned (an RCU read lock, a preemption-disabled section, a referenced kernel pointer) was never released, so the verifier simply forbade throws from such frames. Rust’s Drop glue is exactly that release work; without a place to run it, panic-via-throw was a non-starter.

The compiler side emits a flat .bpf_cleanup table of (begin, end, landing_pad) byte-offset triples for each invoke region that may unwind. At program load the kernel ingests that table, the verifier treats a covered call as able to transfer to its pad, and bpf_throw() runs matching pads as it walks rather than silently dropping frames. Pads restore the frame’s callee-saved state, run on the walker’s stack so they cannot disturb the frame being cleaned, and terminate through a bpf_unwind_resume kfunc (the kernel name for the usual _Unwind_Resume terminator).

libbpf grows parsing and load paths for the section, including light skeletons and the linker’s acceptance of the 32-bit relocations the table uses. JITs for x86-64 and arm64 force a known callee-saved spill shape so the walker can rebuild a frame, and they lower pad entry and resume in architecture-specific ways. Programs that carry a cleanup table currently lose private-stack mode, which can push deep bpf-to-bpf chains over the combined stack limit.

C has no native unwind, so selftests hand-write the same call-site brackets, pads, and records a frontend would emit, covering RCU and preemption cases that previously failed to load. With the series, those programs verify and run: the pad unlocks or re-enables before the frame is discarded, and resource-leak checks move to the end of the unwind walk instead of firing at the throw.

The practical outcome is that BPF exceptions become compatible with scoped ownership, which is the missing piece for idiomatic Rust panic handling inside the verifier’s resource rules.