KernelScript: A Unified Language for Full‑Stack eBPF Development

KernelScript tackles the growing complexity of eBPF projects by unifying kernel‑side programs, userspace loaders, and kernel modules into a single codebase, using annotations to let the compiler generate the necessary glue code, thereby reducing boilerplate and improving team productivity.

Linux Kernel Journey
Linux Kernel Journey
Linux Kernel Journey
KernelScript: A Unified Language for Full‑Stack eBPF Development

1. The Problem Is Not Just the Verifier

eBPF has expanded from a niche kernel feature to scenarios such as XDP for high‑performance packet processing, TC for traffic control, probes and tracepoints for observability, struct_ops for custom TCP congestion control, and kfunc for deeper kernel integration. Yet development still follows a fragmented model: eBPF code lives in one file, userspace loaders in another, and maps, BTF types, attach logic, and build scripts are scattered across many places. Teams spend most of their time handling boilerplate, lifecycle management, and coordination rather than solving networking, security, or observability problems.

Even a simple XDP filter requires a .bpf.c source, a userspace loader, map initialization, attach code, a Makefile, and run instructions. Adding tail calls introduces a PROG_ARRAY map, index management, and initialization order; dynptr adds reserve/write/submit/cleanup steps; struct_ops and kfunc demand understanding of kernel structures, callback tables, and registration lifecycles. The author observes that developers often ask, “Why must I write so much repetitive, error‑prone glue code for a straightforward logic?”

2. Local Optimizations Cannot Fix the Fragmentation

Various tools wrap C helpers, provide friendlier userspace APIs, simplify map handling, or reduce parts of libbpf. While valuable, they only alleviate pain locally and do not address the fundamental split between kernel and userspace code, nor the need to keep multiple build artifacts in sync. Without language‑level support, developers must still decide which code runs in the kernel, which runs in userspace, and how to order loading and attachment.

3. One Repository Should Not Be Split Into Three Projects

KernelScript’s goal is to keep user‑space code, eBPF programs, and kernel modules together. It introduces annotations such as @xdp, @tc, @probe, @tracepoint, @struct_ops, and @kfunc that explicitly tell the compiler the target location of each function. A single repository can therefore contain a packet‑filter XDP program, a tracepoint that monitors scheduler switches, and the userspace loader that attaches them, all clearly distinguished by the annotations.

For example, a project may simultaneously:

Block abnormal traffic at the NIC entry with an XDP program.

Count frequent scheduler switches via a tracepoint.

Load both programs from userspace and print results.

These three concerns are part of one business goal, and the compiler now checks and generates the appropriate artifacts instead of relying on developers to remember which function becomes eBPF code, which stays in userspace, and which becomes a kernel module.

4. Complex Mechanisms Belong to the Compiler

Mechanisms that are mechanically fixed but verbose—such as tail calls, dynptr, struct_ops, and kfunc—are generated automatically. For tail calls, the compiler creates the BPF_MAP_TYPE_PROG_ARRAY, assigns indices, and writes the initialization code. For dynptr, it emits the reserve/write/submit sequence. For struct_ops and kfunc, it produces the BTF registration and module wrapping code. The developer simply writes normal code logic; the compiler handles the repetitive details.

5. The Importance of Type Safety

Relying solely on the verifier is insufficient. Errors in context types, probe signatures, attach order, or shared state definitions become costly near the kernel boundary. KernelScript leverages BTF to move these checks to compile time. Examples include:

Mis‑typed exit‑code in a process‑exit probe leads to wrong output.

Incorrect context type for sched_switch makes PID and priority extraction unreliable.

Treating a performance‑counter probe as a regular probe breaks the program model.

Early detection of such mistakes reduces debugging effort.

6. Sustainable Delivery Over Expert‑Only Craftsmanship

Because eBPF development currently requires deep expertise in the verifier, loader details, map lifecycles, and kernel extensions, most team members cannot modify or extend code safely. KernelScript aims to lower this barrier by consolidating the codebase, making review easier, and allowing anyone to understand high‑level intent (e.g., “filter packets”, “monitor task switches”, “evaluate a new congestion control algorithm”) without being blocked by glue code.

7. Future Directions: Continuing to Collapse the Development Model

Planned enhancements include:

Support for more SEC(...) forms such as perf_event, allowing a single annotation like @perf_event fn on_branch_miss(ctx: *bpf_perf_event_data) to generate both kernel and userspace glue.

Direct attach capabilities that eliminate repetitive attach boilerplate for common targets like @xdp on eth0 or @tracepoint on sched/sched_switch.

Hot‑upgrade without interrupting the running BPF program, handling map reuse, lifecycle transitions, and version roll‑over.

All of these continue the core goal: reduce manual assembly and let the language, compiler, and toolchain handle coordination.

Quick Start

To try KernelScript, clone the repository and build a minimal XDP example that simply passes packets:

# Debian/Ubuntu dependencies
sudo apt update
sudo apt install libbpf-dev libelf-dev zlib1g-dev opam bpftool

# Get source
git clone https://github.com/multikernel/kernelscript.git
cd kernelscript

# Install deps and build
opam init
opam install . --deps-only --with-test
eval $(opam env)
dune build
dune install

# Initialise an XDP project
kernelscript init xdp hello_world

# Compile to generate userspace and eBPF code
kernelscript compile hello_world/hello_world.ks

# Build and run
cd hello_world
make
sudo ./hello_world

After the build succeeds, the program is attached to the specified interface, demonstrating the end‑to‑end flow from a single KernelScript source file to a running eBPF program.

Repository: https://github.com/multikernel/kernelscript

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

eBPFLinux kerneltracingXDPCompiler designKernelScript
Linux Kernel Journey
Written by

Linux Kernel Journey

Linux Kernel Journey

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.