6.2 KiB
Quick Start
This guide will walk you through creating your first BPF program with PythonBPF.
Your First BPF Program
Let's create a simple "Hello World" program that prints a message every time a process is executed on your system.
Step 1: Create the Program
Create a new file called hello_world.py:
from pythonbpf import bpf, section, bpfglobal, BPF, trace_pipe
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
print("Hello, World!")
return 0
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
b = BPF()
b.load()
b.attach_all()
trace_pipe()
Step 2: Run the Program
Run the program with sudo (required for BPF operations):
sudo python3 hello_world.py
Step 3: See it in Action
Open another terminal and run any command:
ls
echo "test"
date
You should see "Hello, World!" printed in the first terminal for each command executed!
Press Ctrl+C to stop the program.
Understanding the Code
Let's break down what each part does:
Imports
from pythonbpf import bpf, section, bpfglobal, BPF, trace_pipe
from ctypes import c_void_p, c_int64
bpf- Decorator to mark functions for BPF compilationsection- Decorator to specify which kernel event to attach tobpfglobal- Decorator for BPF global variablesBPF- Class to compile, load, and attach BPF programstrace_pipe- Utility to read kernel trace output (similar to BCC)c_void_p,c_int64- C types for function signatures
The BPF Function
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
print("Hello, World!")
return 0
@bpf- Marks this function to be compiled to BPF bytecode@section("tracepoint/syscalls/sys_enter_execve")- Attaches to the execve syscall tracepoint (called when processes start)ctx: c_void_p- Context parameter (required for all BPF functions)print()- the PythonBPF API forbpf_printkhelper functionreturn 0- BPF functions must return an integer
License Declaration
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
- The Linux kernel requires BPF programs to declare a license
- Most kernel features require GPL-compatible licenses
- This is defined as a BPF global variable
Compilation and Execution
b = BPF()
b.load()
b.attach_all()
trace_pipe()
BPF()- Creates a BPF object and compiles the current fileb.load()- Loads the compiled BPF program into the kernelb.attach_all()- Attaches all BPF programs to their specified hookstrace_pipe()- Reads and displays output from the kernel trace buffer
Alternatively, you can also use the compile() function to compile the BPF code to an object file:
from pythonbpf import compile
This object file can then be loaded using any other userspace library in any language.
Next Example: Tracking Process IDs
Let's make a more interesting program that tracks which processes are being created:
from pythonbpf import bpf, section, bpfglobal, BPF, trace_pipe
from pythonbpf.helper import pid
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def track_exec(ctx: c_void_p) -> c_int64:
process_id = pid()
print(f"Process with PID: {process_id} is starting")
return 0
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
b = BPF()
b.load()
b.attach_all()
trace_pipe()
This program uses BPF helper functions:
pid()- Gets the current process ID
Run it with sudo python3 track_exec.py and watch processes being created!
Common Patterns
Tracepoints
Tracepoints are predefined hooks in the kernel. Common ones include:
# System calls
@section("tracepoint/syscalls/sys_enter_execve")
@section("tracepoint/syscalls/sys_enter_clone")
@section("tracepoint/syscalls/sys_enter_open")
# Scheduler events
@section("tracepoint/sched/sched_process_fork")
@section("tracepoint/sched/sched_switch")
Kprobes
Kprobes allow you to attach to any kernel function:
@section("kprobe/do_sys_open")
def trace_open(ctx: c_void_p) -> c_int64:
print("File is being opened")
return 0
XDP (eXpress Data Path)
For network packet processing:
from pythonbpf.helper import XDP_PASS
@section("xdp")
def xdp_pass(ctx: c_void_p) -> c_int64:
return XDP_PASS
Best Practices
- Always include a LICENSE - Required by the kernel
- Use type hints - Required by PythonBPF to generate correct code
- Return the correct type - Match the expected return type for your program type
- Test incrementally - Start simple and add complexity gradually
- Check kernel logs - Use
dmesgto see BPF verifier messages if loading fails
Common Issues
Program Won't Load
If your BPF program fails to load:
- Check
dmesgfor verifier error messages - Ensure your LICENSE is GPL-compatible
- Verify you're using supported BPF features
- Make sure return types match function signatures
No Output
If you don't see output:
- Verify the tracepoint/kprobe is being triggered
- Check that you're running with sudo
- Ensure
/sys/kernel/tracing/trace_pipeis accessible
Compilation Errors
If compilation fails:
- Check that
llcis installed and in your PATH - Verify your Python syntax is correct
- Ensure all imported types are from
ctypes - In the worst case, compile object files manually using
compile_to_ir()andllcto get detailed errors
Verification Failure
If verification fails:
- Compile the object files using
compile()function instead of loading directly - Run
sudo check.sh check <bpf>.oto get detailed verification output
Next Steps
Now that you understand the basics, explore:
- {doc}
../user-guide/decorators- Learn about all available decorators - {doc}
../user-guide/maps- Use BPF maps for data storage and communication - {doc}
../user-guide/structs- Define custom data structures - {doc}
../user-guide/helpers- Discover all available BPF helper functions - Examples directory - See more complex examples