Decorators
Decorators are the primary way to mark Python code for BPF compilation. PythonBPF provides five core decorators that control how your code is transformed into eBPF bytecode.
@bpf
The @bpf decorator marks functions or classes for BPF compilation.
Usage
from pythonbpf import bpf
@bpf
def my_function(ctx):
# This function will be compiled to BPF bytecode
pass
Description
Any function or class decorated with @bpf will be processed by the PythonBPF compiler and transformed into LLVM IR, then compiled to BPF bytecode. This is the fundamental decorator that enables BPF compilation.
Rules
Must be used on top-level functions or classes
The function must have proper type hints
Return types must be BPF-compatible
Only BPF-compatible operations are allowed inside
Example
from pythonbpf import bpf, section
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def trace_exec(ctx: c_void_p) -> c_int64:
print("Process started")
return c_int64(0)
@section
The @section(name) decorator specifies which kernel hook to attach the BPF program to.
Usage
from pythonbpf import bpf, section
@bpf
@section("tracepoint/syscalls/sys_enter_open")
def trace_open(ctx):
pass
Section Types
Tracepoints
Tracepoints are stable kernel hooks defined in /sys/kernel/tracing/events/:
# System call tracepoints
@section("tracepoint/syscalls/sys_enter_execve")
@section("tracepoint/syscalls/sys_enter_clone")
@section("tracepoint/syscalls/sys_enter_open")
@section("tracepoint/syscalls/sys_exit_read")
# Scheduler tracepoints
@section("tracepoint/sched/sched_process_fork")
@section("tracepoint/sched/sched_process_exit")
@section("tracepoint/sched/sched_switch")
# Block I/O tracepoints
@section("tracepoint/block/block_rq_insert")
@section("tracepoint/block/block_rq_complete")
Kprobes
Kprobes allow attaching to any kernel function:
@section("kprobe/do_sys_open")
def trace_sys_open(ctx):
pass
@section("kprobe/__x64_sys_write")
def trace_write(ctx):
pass
Kretprobes
Kretprobes trigger when a kernel function returns:
@section("kretprobe/do_sys_open")
def trace_open_return(ctx):
pass
XDP (eXpress Data Path)
For network packet processing at the earliest point:
from pythonbpf.helper import XDP_PASS
from ctypes import c_void_p, c_int64
@section("xdp")
def xdp_prog(ctx: c_void_p) -> c_int64:
# XDP_PASS, XDP_DROP, XDP_ABORTED constants available from pythonbpf.helper
return XDP_PASS
Finding Tracepoints
To find available tracepoints on your system:
# List all tracepoints
ls /sys/kernel/tracing/events/
# List syscall tracepoints
ls /sys/kernel/tracing/events/syscalls/
# View tracepoint format
cat /sys/kernel/tracing/events/syscalls/sys_enter_open/format
@map
The @map decorator marks a function as a BPF map definition.
Usage
from pythonbpf import bpf, map
from pythonbpf.maps import HashMap
from ctypes import c_uint32, c_uint64
@bpf
@map
def my_map() -> HashMap:
return HashMap(key=c_uint32, value=c_uint64, max_entries=1024)
Description
Maps are BPF data structures used to:
Store state between BPF program invocations
Communicate data between BPF programs
Share data with userspace
The function must return a map type (HashMap, PerfEventArray, RingBuffer) and the return type must be annotated.
Example
from pythonbpf import bpf, map, section
from pythonbpf.maps import HashMap
from pythonbpf.helper import pid
from ctypes import c_void_p, c_int64, c_uint32, c_uint64
@bpf
@map
def process_count() -> HashMap:
return HashMap(key=c_uint32, value=c_uint64, max_entries=4096)
@bpf
@section("tracepoint/syscalls/sys_enter_clone")
def count_clones(ctx: c_void_p) -> c_int64:
process_id = pid()
count = process_count.lookup(process_id)
if count:
process_count.update(process_id, count + 1)
else:
process_count.update(process_id, c_uint64(1))
return 0
See BPF Maps for more details on available map types.
@struct
The @struct decorator marks a class as a BPF struct definition.
Usage
from pythonbpf import bpf, struct
from ctypes import c_uint64, c_uint32
@bpf
@struct
class Event:
timestamp: c_uint64
pid: c_uint32
cpu: c_uint32
Description
Structs allow you to define custom data types for use in BPF programs. They can be used:
As map keys and values
For perf event output
In ring buffer submissions
As local variables
Field Types
Supported field types include:
Integer types:
c_int8,c_int16,c_int32,c_int64,c_uint8,c_uint16,c_uint32,c_uint64Pointers:
c_void_p,c_char_pFixed strings:
str(N)where N is the size (e.g.,str(16))Nested structs: Other
@structdecorated classes
Example
from pythonbpf import bpf, struct, map, section
from pythonbpf.maps import RingBuffer
from pythonbpf.helper import pid, ktime
from ctypes import c_void_p, c_int64, c_uint64, c_uint32
@bpf
@struct
class ProcessEvent:
timestamp: c_uint64
pid: c_uint32
comm: str(16)
@bpf
@map
def events() -> RingBuffer:
return RingBuffer(max_entries=4096)
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def track_processes(ctx: c_void_p) -> c_int64:
event = ProcessEvent()
event.timestamp = ktime()
event.pid = pid()
comm(event.comm) # Fills event.comm with process name
events.output(event)
return 0
See BPF Structs for more details on working with structs.
@bpfglobal
The @bpfglobal decorator marks a function as a BPF global variable definition.
Usage
from pythonbpf import bpf, bpfglobal
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
Description
BPF global variables are values that:
Are initialized when the program loads
Can be read by all BPF functions
Must be constant (cannot be modified at runtime in current implementation)
Common Global Variables
LICENSE (Required)
Every BPF program must declare a license:
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
Valid licenses include:
"GPL"- GNU General Public License"GPL v2"- GPL version 2"Dual BSD/GPL"- Dual licensed"Dual MIT/GPL"- Dual licensed
Warning
Many BPF features require a GPL-compatible license. Using a non-GPL license may prevent your program from loading or accessing certain kernel features.
Custom Global Variables
You can define other global variables:
@bpf
@bpfglobal
def DEBUG_MODE() -> int:
return 1
@bpf
@bpfglobal
def MAX_EVENTS() -> int:
return 1000
These can be referenced in your BPF functions, though modifying them at runtime is currently not supported.
Combining Decorators
Decorators are often used together. The order matters:
Correct Order
@bpf # Always first
@section("...") # Section before other decorators
def my_function():
pass
@bpf # Always first
@map # Map/struct/bpfglobal after @bpf
def my_map():
pass
@bpf # Always first
@struct # Map/struct/bpfglobal after @bpf
class MyStruct:
pass
@bpf # Always first
@bpfglobal # Map/struct/bpfglobal after @bpf
def LICENSE():
return "GPL"
Examples by Use Case
Simple Tracepoint
@bpf
@section("tracepoint/syscalls/sys_enter_open")
def trace_open(ctx: c_void_p) -> c_int64:
return c_int64(0)
Map Definition
@bpf
@map
def counters() -> HashMap:
return HashMap(key=c_uint32, value=c_uint64, max_entries=256)
Struct Definition
@bpf
@struct
class Event:
timestamp: c_uint64
value: c_uint32
Global Variable
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
Best Practices
Always use @bpf first - It must be the outermost decorator
Provide type hints - Required for proper code generation
Test incrementally - Verify each component works before combining
Common Errors
Missing @bpf Decorator
# Wrong - missing @bpf
@section("tracepoint/syscalls/sys_enter_open")
def my_func(ctx):
pass
# Correct
@bpf
@section("tracepoint/syscalls/sys_enter_open")
def my_func(ctx):
pass
Wrong Decorator Order
# Wrong - @section before @bpf
@section("tracepoint/syscalls/sys_enter_open")
@bpf
def my_func(ctx):
pass
# Correct
@bpf
@section("tracepoint/syscalls/sys_enter_open")
def my_func(ctx):
pass
Missing Type Hints
# Wrong - no type hints
@bpf
def my_func(ctx):
pass
# Correct
@bpf
def my_func(ctx: c_void_p) -> c_int64:
pass
Next Steps
Learn about BPF Maps for data storage and communication
Explore BPF Structs for defining custom data types
Understand Compilation to see how code is transformed
Check out Helper Functions and Utilities for available BPF helper functions