Skip to content

Commit 03da7c5

Browse files
Merge pull request #13 from pythonbpf/ringbuf
Add ringbuf support
2 parents 5c8b132 + da9df2e commit 03da7c5

File tree

8 files changed

+244
-52
lines changed

8 files changed

+244
-52
lines changed

pythonbpf/debuginfo/debug_info_generator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ def get_basic_type(self, name: str, size: int, encoding: int) -> Any:
2121
)
2222
return self._type_cache[key]
2323

24+
def get_int32_type(self) -> Any:
25+
"""Get debug info for signed 32-bit integer"""
26+
return self.get_basic_type("int", 32, dc.DW_ATE_signed)
27+
2428
def get_uint32_type(self) -> Any:
2529
"""Get debug info for unsigned 32-bit integer"""
2630
return self.get_basic_type("unsigned int", 32, dc.DW_ATE_unsigned)

pythonbpf/maps/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .maps import HashMap, PerfEventArray
1+
from .maps import HashMap, PerfEventArray, RingBuf
22
from .maps_pass import maps_proc
33

4-
__all__ = ["HashMap", "PerfEventArray", "maps_proc"]
4+
__all__ = ["HashMap", "PerfEventArray", "maps_proc", "RingBuf"]

pythonbpf/maps/maps.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# This file provides type and function hints only and does not actually give any functionality.
12
class HashMap:
23
def __init__(self, key, value, max_entries):
34
self.key = key
@@ -33,3 +34,18 @@ def __init__(self, key_size, value_size):
3334

3435
def output(self, data):
3536
pass # Placeholder for output method
37+
38+
39+
class RingBuf:
40+
def __init__(self, max_entries):
41+
self.max_entries = max_entries
42+
43+
def reserve(self, size: int, flags=0):
44+
if size > self.max_entries:
45+
raise ValueError("size cannot be greater than set maximum entries")
46+
return 0
47+
48+
def submit(self, data, flags=0):
49+
pass
50+
51+
# add discard, output and also give names to flags and stuff

pythonbpf/maps/maps_pass.py

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import ast
2+
from logging import Logger
23
from llvmlite import ir
34
from enum import Enum
45
from .maps_utils import MapProcessorRegistry
56
from ..debuginfo import DebugInfoGenerator
67
import logging
78

8-
logger = logging.getLogger(__name__)
9+
logger: Logger = logging.getLogger(__name__)
910

1011

1112
def maps_proc(tree, module, chunks):
1213
"""Process all functions decorated with @map to find BPF maps"""
1314
map_sym_tab = {}
1415
for func_node in chunks:
1516
if is_map(func_node):
16-
print(f"Found BPF map: {func_node.name}")
17+
logger.info(f"Found BPF map: {func_node.name}")
1718
map_sym_tab[func_node.name] = process_bpf_map(func_node, module)
1819
return map_sym_tab
1920

@@ -26,8 +27,41 @@ def is_map(func_node):
2627

2728

2829
class BPFMapType(Enum):
30+
UNSPEC = 0
2931
HASH = 1
32+
ARRAY = 2
33+
PROG_ARRAY = 3
3034
PERF_EVENT_ARRAY = 4
35+
PERCPU_HASH = 5
36+
PERCPU_ARRAY = 6
37+
STACK_TRACE = 7
38+
CGROUP_ARRAY = 8
39+
LRU_HASH = 9
40+
LRU_PERCPU_HASH = 10
41+
LPM_TRIE = 11
42+
ARRAY_OF_MAPS = 12
43+
HASH_OF_MAPS = 13
44+
DEVMAP = 14
45+
SOCKMAP = 15
46+
CPUMAP = 16
47+
XSKMAP = 17
48+
SOCKHASH = 18
49+
CGROUP_STORAGE_DEPRECATED = 19
50+
CGROUP_STORAGE = 19
51+
REUSEPORT_SOCKARRAY = 20
52+
PERCPU_CGROUP_STORAGE_DEPRECATED = 21
53+
PERCPU_CGROUP_STORAGE = 21
54+
QUEUE = 22
55+
STACK = 23
56+
SK_STORAGE = 24
57+
DEVMAP_HASH = 25
58+
STRUCT_OPS = 26
59+
RINGBUF = 27
60+
INODE_STORAGE = 28
61+
TASK_STORAGE = 29
62+
BLOOM_FILTER = 30
63+
USER_RINGBUF = 31
64+
CGRP_STORAGE = 32
3165

3266

3367
def create_bpf_map(module, map_name, map_params):
@@ -51,7 +85,7 @@ def create_bpf_map(module, map_name, map_params):
5185

5286

5387
def create_map_debug_info(module, map_global, map_name, map_params):
54-
"""Generate debug information metadata for BPF map"""
88+
"""Generate debug information metadata for BPF maps HASH and PERF_EVENT_ARRAY"""
5589
generator = DebugInfoGenerator(module)
5690

5791
uint_type = generator.get_uint32_type()
@@ -112,6 +146,60 @@ def create_map_debug_info(module, map_global, map_name, map_params):
112146
return global_var
113147

114148

149+
def create_ringbuf_debug_info(module, map_global, map_name, map_params):
150+
"""Generate debug information metadata for BPF RINGBUF map"""
151+
generator = DebugInfoGenerator(module)
152+
153+
int_type = generator.get_int32_type()
154+
155+
type_array = generator.create_array_type(
156+
int_type, map_params.get("type", BPFMapType.RINGBUF).value
157+
)
158+
type_ptr = generator.create_pointer_type(type_array, 64)
159+
type_member = generator.create_struct_member("type", type_ptr, 0)
160+
161+
max_entries_array = generator.create_array_type(int_type, map_params["max_entries"])
162+
max_entries_ptr = generator.create_pointer_type(max_entries_array, 64)
163+
max_entries_member = generator.create_struct_member(
164+
"max_entries", max_entries_ptr, 64
165+
)
166+
167+
elements_arr = [type_member, max_entries_member]
168+
169+
struct_type = generator.create_struct_type(elements_arr, 128, is_distinct=True)
170+
171+
global_var = generator.create_global_var_debug_info(
172+
map_name, struct_type, is_local=False
173+
)
174+
map_global.set_metadata("dbg", global_var)
175+
return global_var
176+
177+
178+
@MapProcessorRegistry.register("RingBuf")
179+
def process_ringbuf_map(map_name, rval, module):
180+
"""Process a BPF_RINGBUF map declaration"""
181+
logger.info(f"Processing Ringbuf: {map_name}")
182+
map_params = {"type": BPFMapType.RINGBUF}
183+
184+
# Parse max_entries if present
185+
if len(rval.args) >= 1 and isinstance(rval.args[0], ast.Constant):
186+
const_val = rval.args[0].value
187+
if isinstance(const_val, int):
188+
map_params["max_entries"] = const_val
189+
190+
for keyword in rval.keywords:
191+
if keyword.arg == "max_entries" and isinstance(keyword.value, ast.Constant):
192+
const_val = keyword.value.value
193+
if isinstance(const_val, int):
194+
map_params["max_entries"] = const_val
195+
196+
logger.info(f"Ringbuf map parameters: {map_params}")
197+
198+
map_global = create_bpf_map(module, map_name, map_params)
199+
create_ringbuf_debug_info(module, map_global, map_name, map_params)
200+
return map_global
201+
202+
115203
@MapProcessorRegistry.register("HashMap")
116204
def process_hash_map(map_name, rval, module):
117205
"""Process a BPF_HASH map declaration"""

tests/c-form/ex8.bpf.c

Lines changed: 0 additions & 47 deletions
This file was deleted.

tests/c-form/ringbuf.bpf.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2+
#include <linux/bpf.h>
3+
#include <bpf/bpf_helpers.h>
4+
#include <bpf/bpf_tracing.h>
5+
#include <linux/types.h>
6+
7+
// Define the structure to be sent via ringbuf
8+
struct event {
9+
__u32 pid;
10+
__u32 uid;
11+
__u64 timestamp;
12+
char comm[16]; // Process name
13+
};
14+
15+
// Define the ringbuffer map
16+
struct {
17+
__uint(type, BPF_MAP_TYPE_RINGBUF);
18+
__uint(max_entries, 256 * 1024); // 256 KB
19+
} events SEC(".maps");
20+
21+
// Tracepoint for execve system calls
22+
SEC("tracepoint/syscalls/sys_enter_execve")
23+
int trace_execve(void *ctx)
24+
{
25+
struct event *e;
26+
__u64 pid_tgid;
27+
__u64 uid_gid;
28+
29+
// Reserve space in the ringbuffer
30+
e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
31+
if (!e)
32+
return 0;
33+
34+
// Fill the struct with data
35+
pid_tgid = bpf_get_current_pid_tgid();
36+
e->pid = pid_tgid >> 32;
37+
38+
uid_gid = bpf_get_current_uid_gid();
39+
e->uid = uid_gid & 0xFFFFFFFF;
40+
41+
e->timestamp = bpf_ktime_get_ns();
42+
43+
bpf_get_current_comm(&e->comm, sizeof(e->comm));
44+
45+
// Submit the event to ringbuffer
46+
bpf_ringbuf_submit(e, 0);
47+
48+
return 0;
49+
}
50+
51+
char LICENSE[] SEC("license") = "GPL";
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from pythonbpf import bpf, map, struct, section, bpfglobal, compile
2+
from pythonbpf.helpers import ktime, pid
3+
from pythonbpf.maps import PerfEventArray
4+
5+
from ctypes import c_void_p, c_int32, c_uint64
6+
7+
8+
# PLACEHOLDER EXAMPLE. THIS SHOULD TECHNICALLY STILL FAIL TESTS
9+
@bpf
10+
@struct
11+
class data_t:
12+
pid: c_uint64
13+
ts: c_uint64
14+
comm: str(16)
15+
16+
17+
@bpf
18+
@map
19+
def events() -> PerfEventArray:
20+
return PerfEventArray(key_size=c_int32, value_size=c_int32)
21+
22+
23+
@bpf
24+
@section("tracepoint/syscalls/sys_enter_clone")
25+
def hello(ctx: c_void_p) -> c_int32:
26+
dataobj = data_t()
27+
ts = ktime()
28+
strobj = "hellohellohello"
29+
dataobj.pid = pid()
30+
dataobj.ts = ktime()
31+
# dataobj.comm = strobj
32+
print(
33+
f"clone called at {dataobj.ts} by pid {dataobj.pid}, comm {strobj} at time {ts}"
34+
)
35+
events.output(dataobj)
36+
return c_int32(0)
37+
38+
39+
@bpf
40+
@bpfglobal
41+
def LICENSE() -> str:
42+
return "GPL"
43+
44+
45+
compile()

tests/failing_tests/ringbuf.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from pythonbpf import bpf, BPF, map, bpfglobal, section, compile, compile_to_ir
2+
from pythonbpf.maps import RingBuf, HashMap
3+
from ctypes import c_int32, c_void_p
4+
5+
6+
# Define a map
7+
@bpf
8+
@map
9+
def mymap() -> RingBuf:
10+
return RingBuf(max_entries=(1024))
11+
12+
13+
@bpf
14+
@map
15+
def mymap2() -> HashMap:
16+
return HashMap(key=c_int32, value=c_int32, max_entries=1024)
17+
18+
19+
@bpf
20+
@section("tracepoint/syscalls/sys_enter_clone")
21+
def random_section(ctx: c_void_p) -> c_int32:
22+
print("Hello")
23+
return c_int32(0)
24+
25+
26+
@bpf
27+
@bpfglobal
28+
def LICENSE() -> str:
29+
return "GPL"
30+
31+
32+
compile_to_ir("ringbuf.py", "ringbuf.ll")
33+
compile()
34+
b = BPF()
35+
b.load_and_attach()

0 commit comments

Comments
 (0)