mirror of
https://github.com/varun-r-mallya/Python-BPF.git
synced 2026-02-07 13:40:59 +00:00
449 lines
9.0 KiB
Markdown
449 lines
9.0 KiB
Markdown
# 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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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/`:
|
|
|
|
```python
|
|
# 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:
|
|
|
|
```python
|
|
@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:
|
|
|
|
```python
|
|
@section("kretprobe/do_sys_open")
|
|
def trace_open_return(ctx):
|
|
pass
|
|
```
|
|
|
|
#### XDP (eXpress Data Path)
|
|
|
|
For network packet processing at the earliest point:
|
|
|
|
```python
|
|
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:
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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 {doc}`maps` for more details on available map types.
|
|
|
|
## @struct
|
|
|
|
The `@struct` decorator marks a class as a BPF struct definition.
|
|
|
|
### Usage
|
|
|
|
```python
|
|
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_uint64`
|
|
* **Pointers**: `c_void_p`, `c_char_p`
|
|
* **Fixed strings**: `str(N)` where N is the size (e.g., `str(16)`)
|
|
* **Nested structs**: Other `@struct` decorated classes
|
|
|
|
### Example
|
|
|
|
```python
|
|
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 {doc}`structs` for more details on working with structs.
|
|
|
|
## @bpfglobal
|
|
|
|
The `@bpfglobal` decorator marks a function as a BPF global variable definition.
|
|
|
|
### Usage
|
|
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
@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:
|
|
|
|
```python
|
|
@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
|
|
|
|
```python
|
|
@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
|
|
|
|
```python
|
|
@bpf
|
|
@section("tracepoint/syscalls/sys_enter_open")
|
|
def trace_open(ctx: c_void_p) -> c_int64:
|
|
return c_int64(0)
|
|
```
|
|
|
|
#### Map Definition
|
|
|
|
```python
|
|
@bpf
|
|
@map
|
|
def counters() -> HashMap:
|
|
return HashMap(key=c_uint32, value=c_uint64, max_entries=256)
|
|
```
|
|
|
|
#### Struct Definition
|
|
|
|
```python
|
|
@bpf
|
|
@struct
|
|
class Event:
|
|
timestamp: c_uint64
|
|
value: c_uint32
|
|
```
|
|
|
|
#### Global Variable
|
|
|
|
```python
|
|
@bpf
|
|
@bpfglobal
|
|
def LICENSE() -> str:
|
|
return "GPL"
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Always use @bpf first** - It must be the outermost decorator
|
|
2. **Provide type hints** - Required for proper code generation
|
|
3. **Test incrementally** - Verify each component works before combining
|
|
|
|
## Common Errors
|
|
|
|
### Missing @bpf Decorator
|
|
|
|
```python
|
|
# 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
|
|
|
|
```python
|
|
# 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
|
|
|
|
```python
|
|
# 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 {doc}`maps` for data storage and communication
|
|
* Explore {doc}`structs` for defining custom data types
|
|
* Understand {doc}`compilation` to see how code is transformed
|
|
* Check out {doc}`helpers` for available BPF helper functions
|