8.1 KiB
Compilation
PythonBPF provides several functions and classes for compiling Python code into BPF bytecode and loading it into the kernel.
Overview
The compilation process transforms Python code into executable BPF programs:
- Python AST → LLVM IR generation (using llvmlite)
- LLVM IR → BPF bytecode (using llc)
- BPF Object → Kernel loading (using libbpf)
Compilation Functions
compile_to_ir()
Compile Python source to LLVM Intermediate Representation.
Signature
def compile_to_ir(filename: str, output: str, loglevel=logging.WARNING)
Parameters
filename- Path to the Python source file to compileoutput- Path where the LLVM IR file (.ll) should be writtenloglevel- Logging level (default:logging.WARNING)
Usage
from pythonbpf import compile_to_ir
import logging
# Compile to LLVM IR
compile_to_ir(
filename="my_bpf_program.py",
output="my_bpf_program.ll",
loglevel=logging.DEBUG
)
Output
This function generates an .ll file containing LLVM IR, which is human-readable assembly-like code. This is useful for:
- Debugging compilation issues
- Understanding code generation
compile()
Compile Python source to BPF object file.
Signature
def compile(filename: str = None, output: str = None, loglevel=logging.WARNING)
Parameters
filename- Path to the Python source file (default: calling file)output- Path for the output object file (default: same name with.oextension)loglevel- Logging level (default:logging.WARNING)
Usage
from pythonbpf import compile
import logging
# Compile current file
compile()
# Compile specific file
compile(filename="my_program.py", output="my_program.o")
# Compile with debug logging
compile(loglevel=logging.DEBUG)
Output
This function generates a .o file containing BPF bytecode that can be:
- Loaded into the kernel
- Inspected with
bpftool - Verified with the BPF verifier
- Distributed as a compiled binary
BPF Class
The BPF class provides a high-level interface to compile, load, and attach BPF programs.
Signature
class BPF:
def __init__(self, filename: str = None, loglevel=logging.WARNING)
def load(self)
def attach_all(self)
def load_and_attach(self)
Parameters
filename- Path to Python source file (default: calling file)loglevel- Logging level (default:logging.WARNING)
Methods
init()
Create a BPF object and compile the source.
from pythonbpf import BPF
# Compile current file
b = BPF()
# Compile specific file
b = BPF(filename="my_program.py")
load()
Load the compiled BPF program into the kernel.
b = BPF()
b.load()
This method:
- Loads the BPF object file into the kernel
- Creates maps
- Verifies the BPF program
- Returns a
BpfObjectinstance
attach_all()
Attach all BPF programs to their specified hooks.
b = BPF()
b.load()
b.attach_all()
This method:
- Attaches tracepoints
- Attaches kprobes/kretprobes
- Attaches XDP programs
- Enables all hooks
load_and_attach()
Convenience method that loads and attaches in one call.
b = BPF()
b.load_and_attach()
Equivalent to:
b = BPF()
b.load()
b.attach_all()
Complete Example
Here's a complete example showing the compilation workflow:
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 trace_exec(ctx: c_void_p) -> c_int64:
print("Process started")
return 0
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
if __name__ == "__main__":
# Method 1: Simple compilation and loading
b = BPF()
b.load_and_attach()
trace_pipe()
# Method 2: Step-by-step
# b = BPF()
# b.load()
# b.attach_all()
# trace_pipe()
# Method 3: Manual compilation
# from pythonbpf import compile
# compile(filename="my_program.py", output="my_program.o")
# # Then load with pylibbpf directly
Compilation Pipeline Details
AST Parsing
The Python ast module parses your source code:
import ast
tree = ast.parse(source_code, filename)
The AST is then walked to find:
- Functions decorated with
@bpf - Classes decorated with
@struct - Map definitions with
@map - Global variables with
@bpfglobal
IR Generation
PythonBPF uses llvmlite to generate LLVM IR:
from llvmlite import ir
# Create module
module = ir.Module(name='bpf_module')
module.triple = 'bpf'
# Generate IR for each BPF function
# ...
Key aspects of IR generation:
- Type conversion (Python types → LLVM types)
- Function definitions
- Map declarations
- Global variable initialization
- Debug information
BPF Compilation
The LLVM IR is compiled to BPF bytecode using llc:
llc -march=bpf -filetype=obj input.ll -o output.o
Kernel Loading
The compiled object is loaded using pylibbpf:
from pylibbpf import BpfObject
obj = BpfObject(path="program.o")
obj.load()
Debugging Compilation
Logging
Enable debug logging to see compilation details:
import logging
from pythonbpf import BPF
b = BPF(loglevel=logging.DEBUG)
This will show:
- AST parsing details
- IR generation steps
- Compilation commands
- Loading status
Inspecting LLVM IR
Generate and inspect the IR file:
from pythonbpf import compile_to_ir
compile_to_ir("program.py", "program.ll")
Then examine program.ll to understand the generated code.
Using bpftool
Inspect compiled objects with bpftool:
# Show program info
bpftool prog show
# Dump program instructions
bpftool prog dump xlated id <ID>
# Dump program JIT code
bpftool prog dump jited id <ID>
# Show maps
bpftool map show
# Dump map contents
bpftool map dump id <ID>
Verifier Errors
If the kernel verifier rejects your program:
- Check
dmesgfor detailed error messages:sudo dmesg | tail -50
Compilation Options
Optimization Levels
While PythonBPF doesn't expose optimization flags directly, you can:
-
Manually compile IR with specific flags:
llc -march=bpf -O2 -filetype=obj program.ll -o program.o -
Modify the compilation pipeline in your code
Debug Information
PythonBPF automatically generates debug information (DWARF) for:
- Function names
- Variable names
- Type information
This helps with:
- Stack traces
- Debugging with
bpftool - Source-level debugging
Working with Compiled Objects
Loading Pre-compiled Objects
You can load previously compiled objects:
from pylibbpf import BpfObject
# Load object file
obj = BpfObject(path="my_program.o")
obj.load()
# Attach programs
# (specific attachment depends on program type)
Distribution
Distribute compiled BPF objects:
-
Compile once:
from pythonbpf import compile compile(filename="program.py", output="program.o") -
Ship
program.ofile -
Load on target systems:
from pylibbpf import BpfObject obj = BpfObject(path="program.o") obj.load()
Version Compatibility
BPF objects are generally compatible across kernel versions, but:
- Some features require specific kernel versions
- Helper functions may not be available on older kernels
- BTF (BPF Type Format) requirements vary
Troubleshooting
Compilation Fails
If compilation fails:
- Check Python syntax
- Verify all decorators are correct
- Ensure type hints are present
- Check for unsupported Python features
Loading Fails
If loading fails:
- Check
dmesgfor verifier errors - Verify LICENSE is set correctly
- Ensure helper functions are valid
- Check map definitions
Programs Don't Attach
If attachment fails:
- Verify section names are correct
- Check that hooks exist on your kernel
- Ensure you have sufficient permissions
- Verify kernel version supports the feature
Next Steps
- Learn about {doc}
helpersfor available BPF helper functions - Explore {doc}
mapsfor data storage - See {doc}
decoratorsfor compilation markers