From 84ad58b77569c490496b30ae72e5ce6d303e74bf Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Wed, 1 Oct 2025 21:14:20 +0530 Subject: [PATCH 1/4] Add ringbuf type hinting. --- pythonbpf/maps/__init__.py | 4 +-- pythonbpf/maps/maps.py | 16 +++++++++ pythonbpf/maps/maps_pass.py | 2 +- tests/c-form/ex8.bpf.c | 47 -------------------------- tests/c-form/ringbuf.bpf.c | 30 ++++++++++++++++ tests/failing_tests/perf_buffer_map.py | 45 ++++++++++++++++++++++++ tests/failing_tests/ringbuf.py | 25 ++++++++++++++ 7 files changed, 119 insertions(+), 50 deletions(-) delete mode 100644 tests/c-form/ex8.bpf.c create mode 100644 tests/c-form/ringbuf.bpf.c create mode 100644 tests/failing_tests/perf_buffer_map.py create mode 100644 tests/failing_tests/ringbuf.py diff --git a/pythonbpf/maps/__init__.py b/pythonbpf/maps/__init__.py index 370bbf2..48fc9ff 100644 --- a/pythonbpf/maps/__init__.py +++ b/pythonbpf/maps/__init__.py @@ -1,4 +1,4 @@ -from .maps import HashMap, PerfEventArray +from .maps import HashMap, PerfEventArray, RingBuf from .maps_pass import maps_proc -__all__ = ["HashMap", "PerfEventArray", "maps_proc"] +__all__ = ["HashMap", "PerfEventArray", "maps_proc", "RingBuf"] diff --git a/pythonbpf/maps/maps.py b/pythonbpf/maps/maps.py index 24b421c..a2d7c21 100644 --- a/pythonbpf/maps/maps.py +++ b/pythonbpf/maps/maps.py @@ -1,3 +1,4 @@ +# This file provides type and function hints only and does not actually give any functionality. class HashMap: def __init__(self, key, value, max_entries): self.key = key @@ -33,3 +34,18 @@ def __init__(self, key_size, value_size): def output(self, data): pass # Placeholder for output method + + +class RingBuf: + def __init__(self, max_entries): + self.max_entries = max_entries + + def reserve(self, size: int, flags=0): + if size > self.max_entries: + raise ValueError("size cannot be greater than set maximum entries") + return 0 + + def submit(self, data, flags=0): + pass + + # add discard, output and also give names to flags and stuff diff --git a/pythonbpf/maps/maps_pass.py b/pythonbpf/maps/maps_pass.py index 2621617..51fd7c1 100644 --- a/pythonbpf/maps/maps_pass.py +++ b/pythonbpf/maps/maps_pass.py @@ -51,7 +51,7 @@ def create_bpf_map(module, map_name, map_params): def create_map_debug_info(module, map_global, map_name, map_params): - """Generate debug information metadata for BPF map""" + """Generate debug information metadata for BPF maps HASH and PERF_EVENT_ARRAY""" generator = DebugInfoGenerator(module) uint_type = generator.get_uint32_type() diff --git a/tests/c-form/ex8.bpf.c b/tests/c-form/ex8.bpf.c deleted file mode 100644 index b1861fd..0000000 --- a/tests/c-form/ex8.bpf.c +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include -#include -#include -#include -#define __TARGET_ARCH_aarch64 -#define u64 unsigned long long - -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, 10240); - __type(key, struct request *); - __type(value, u64); -} start SEC(".maps"); - -SEC("kprobe/blk_start_request") -int BPF_KPROBE(trace_start_req, struct request *req) -{ - u64 ts = bpf_ktime_get_ns(); - bpf_map_update_elem(&start, &req, &ts, BPF_ANY); - return 0; -} - -SEC("kprobe/blk_mq_start_request") -int BPF_KPROBE(trace_start_mq, struct request *req) -{ - u64 ts = bpf_ktime_get_ns(); - bpf_map_update_elem(&start, &req, &ts, BPF_ANY); - return 0; -} - -SEC("kprobe/blk_account_io_completion") -int BPF_KPROBE(trace_completion, struct request *req) -{ - u64 *tsp, delta; - - tsp = bpf_map_lookup_elem(&start, &req); - if (tsp) { - delta = bpf_ktime_get_ns() - *tsp; - bpf_printk("%d %x %d\n", req->__data_len, - req->cmd_flags, delta / 1000); - bpf_map_delete_elem(&start, &req); - } - return 0; -} - -char LICENSE[] SEC("license") = "GPL"; diff --git a/tests/c-form/ringbuf.bpf.c b/tests/c-form/ringbuf.bpf.c new file mode 100644 index 0000000..648de2c --- /dev/null +++ b/tests/c-form/ringbuf.bpf.c @@ -0,0 +1,30 @@ +#include "vmlinux.h" +#include +#include +#include + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 1 << 24); // 16 MB +} rb SEC(".maps"); + +//struct msg { +// u32 pid; +// char comm[16]; +//}; + +//SEC("tracepoint/syscalls/sys_enter_execve") +//int handle_execve(struct trace_event_raw_sys_enter *ctx) +//{ +// struct msg *m; +// m = bpf_ringbuf_reserve(&rb, sizeof(*m), 0); +// if (!m) +// return 0; +// +// m->pid = bpf_get_current_pid_tgid() >> 32; +// bpf_get_current_comm(&m->comm, sizeof(m->comm)); +// bpf_ringbuf_submit(m, 0); +// return 0; +//} + +//char LICENSE[] SEC("license") = "GPL"; diff --git a/tests/failing_tests/perf_buffer_map.py b/tests/failing_tests/perf_buffer_map.py new file mode 100644 index 0000000..1ec3874 --- /dev/null +++ b/tests/failing_tests/perf_buffer_map.py @@ -0,0 +1,45 @@ +from pythonbpf import bpf, map, struct, section, bpfglobal, compile +from pythonbpf.helpers import ktime, pid +from pythonbpf.maps import PerfEventArray + +from ctypes import c_void_p, c_int32, c_uint64 + + +# PLACEHOLDER EXAMPLE. THIS SHOULD TECHNICALLY STILL FAIL TESTS +@bpf +@struct +class data_t: + pid: c_uint64 + ts: c_uint64 + comm: str(16) + + +@bpf +@map +def events() -> PerfEventArray: + return PerfEventArray(key_size=c_int32, value_size=c_int32) + + +@bpf +@section("tracepoint/syscalls/sys_enter_clone") +def hello(ctx: c_void_p) -> c_int32: + dataobj = data_t() + ts = ktime() + strobj = "hellohellohello" + dataobj.pid = pid() + dataobj.ts = ktime() + # dataobj.comm = strobj + print( + f"clone called at {dataobj.ts} by pid {dataobj.pid}, comm {strobj} at time {ts}" + ) + events.output(dataobj) + return c_int32(0) + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() diff --git a/tests/failing_tests/ringbuf.py b/tests/failing_tests/ringbuf.py new file mode 100644 index 0000000..a6c60c8 --- /dev/null +++ b/tests/failing_tests/ringbuf.py @@ -0,0 +1,25 @@ +from pythonbpf import bpf, map, bpfglobal, section +from pythonbpf.maps import RingBuf +from ctypes import c_int32, c_void_p + + +# Define a map +@bpf +@map +def mymap() -> RingBuf: + return RingBuf(max_entries=(1 << 24)) + + +@bpf +@section("tracepoint/syscalls/sys_enter_clone") +def testing(ctx: c_void_p) -> c_int32: + return c_int32(0) + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 668343532f8a123a243ddec85f662fd9f752593a Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Wed, 1 Oct 2025 21:22:44 +0530 Subject: [PATCH 2/4] add map types for completion --- pythonbpf/maps/maps_pass.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pythonbpf/maps/maps_pass.py b/pythonbpf/maps/maps_pass.py index 51fd7c1..3a07c38 100644 --- a/pythonbpf/maps/maps_pass.py +++ b/pythonbpf/maps/maps_pass.py @@ -26,8 +26,41 @@ def is_map(func_node): class BPFMapType(Enum): + UNSPEC = 0 HASH = 1 + ARRAY = 2 + PROG_ARRAY = 3 PERF_EVENT_ARRAY = 4 + PERCPU_HASH = 5 + PERCPU_ARRAY = 6 + STACK_TRACE = 7 + CGROUP_ARRAY = 8 + LRU_HASH = 9 + LRU_PERCPU_HASH = 10 + LPM_TRIE = 11 + ARRAY_OF_MAPS = 12 + HASH_OF_MAPS = 13 + DEVMAP = 14 + SOCKMAP = 15 + CPUMAP = 16 + XSKMAP = 17 + SOCKHASH = 18 + CGROUP_STORAGE_DEPRECATED = 19 + CGROUP_STORAGE = 19 + REUSEPORT_SOCKARRAY = 20 + PERCPU_CGROUP_STORAGE_DEPRECATED = 21 + PERCPU_CGROUP_STORAGE = 21 + QUEUE = 22 + STACK = 23 + SK_STORAGE = 24 + DEVMAP_HASH = 25 + STRUCT_OPS = 26 + RINGBUF = 27 + INODE_STORAGE = 28 + TASK_STORAGE = 29 + BLOOM_FILTER = 30 + USER_RINGBUF = 31 + CGRP_STORAGE = 32 def create_bpf_map(module, map_name, map_params): From 8ceb1d1ac334a803e016ee6e7ffee170b34b186d Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Wed, 1 Oct 2025 21:39:16 +0530 Subject: [PATCH 3/4] add int32 type --- pythonbpf/debuginfo/debug_info_generator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pythonbpf/debuginfo/debug_info_generator.py b/pythonbpf/debuginfo/debug_info_generator.py index a4aeaec..da0e86a 100644 --- a/pythonbpf/debuginfo/debug_info_generator.py +++ b/pythonbpf/debuginfo/debug_info_generator.py @@ -21,6 +21,10 @@ def get_basic_type(self, name: str, size: int, encoding: int) -> Any: ) return self._type_cache[key] + def get_int32_type(self) -> Any: + """Get debug info for signed 32-bit integer""" + return self.get_basic_type("int", 32, dc.DW_ATE_signed) + def get_uint32_type(self) -> Any: """Get debug info for unsigned 32-bit integer""" return self.get_basic_type("unsigned int", 32, dc.DW_ATE_unsigned) From da9df2e6bf6146cca66fa1882b61b77ebe356ed3 Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Wed, 1 Oct 2025 23:51:18 +0530 Subject: [PATCH 4/4] add ringbuf map type --- pythonbpf/maps/maps_pass.py | 59 +++++++++++++++++++++++++++- tests/c-form/ringbuf.bpf.c | 71 ++++++++++++++++++++++------------ tests/failing_tests/ringbuf.py | 18 +++++++-- 3 files changed, 117 insertions(+), 31 deletions(-) diff --git a/pythonbpf/maps/maps_pass.py b/pythonbpf/maps/maps_pass.py index 3a07c38..26f2aee 100644 --- a/pythonbpf/maps/maps_pass.py +++ b/pythonbpf/maps/maps_pass.py @@ -1,11 +1,12 @@ import ast +from logging import Logger from llvmlite import ir from enum import Enum from .maps_utils import MapProcessorRegistry from ..debuginfo import DebugInfoGenerator import logging -logger = logging.getLogger(__name__) +logger: Logger = logging.getLogger(__name__) def maps_proc(tree, module, chunks): @@ -13,7 +14,7 @@ def maps_proc(tree, module, chunks): map_sym_tab = {} for func_node in chunks: if is_map(func_node): - print(f"Found BPF map: {func_node.name}") + logger.info(f"Found BPF map: {func_node.name}") map_sym_tab[func_node.name] = process_bpf_map(func_node, module) return map_sym_tab @@ -145,6 +146,60 @@ def create_map_debug_info(module, map_global, map_name, map_params): return global_var +def create_ringbuf_debug_info(module, map_global, map_name, map_params): + """Generate debug information metadata for BPF RINGBUF map""" + generator = DebugInfoGenerator(module) + + int_type = generator.get_int32_type() + + type_array = generator.create_array_type( + int_type, map_params.get("type", BPFMapType.RINGBUF).value + ) + type_ptr = generator.create_pointer_type(type_array, 64) + type_member = generator.create_struct_member("type", type_ptr, 0) + + max_entries_array = generator.create_array_type(int_type, map_params["max_entries"]) + max_entries_ptr = generator.create_pointer_type(max_entries_array, 64) + max_entries_member = generator.create_struct_member( + "max_entries", max_entries_ptr, 64 + ) + + elements_arr = [type_member, max_entries_member] + + struct_type = generator.create_struct_type(elements_arr, 128, is_distinct=True) + + global_var = generator.create_global_var_debug_info( + map_name, struct_type, is_local=False + ) + map_global.set_metadata("dbg", global_var) + return global_var + + +@MapProcessorRegistry.register("RingBuf") +def process_ringbuf_map(map_name, rval, module): + """Process a BPF_RINGBUF map declaration""" + logger.info(f"Processing Ringbuf: {map_name}") + map_params = {"type": BPFMapType.RINGBUF} + + # Parse max_entries if present + if len(rval.args) >= 1 and isinstance(rval.args[0], ast.Constant): + const_val = rval.args[0].value + if isinstance(const_val, int): + map_params["max_entries"] = const_val + + for keyword in rval.keywords: + if keyword.arg == "max_entries" and isinstance(keyword.value, ast.Constant): + const_val = keyword.value.value + if isinstance(const_val, int): + map_params["max_entries"] = const_val + + logger.info(f"Ringbuf map parameters: {map_params}") + + map_global = create_bpf_map(module, map_name, map_params) + create_ringbuf_debug_info(module, map_global, map_name, map_params) + return map_global + + @MapProcessorRegistry.register("HashMap") def process_hash_map(map_name, rval, module): """Process a BPF_HASH map declaration""" diff --git a/tests/c-form/ringbuf.bpf.c b/tests/c-form/ringbuf.bpf.c index 648de2c..f0abb91 100644 --- a/tests/c-form/ringbuf.bpf.c +++ b/tests/c-form/ringbuf.bpf.c @@ -1,30 +1,51 @@ -#include "vmlinux.h" +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +#include #include #include -#include +#include +// Define the structure to be sent via ringbuf +struct event { + __u32 pid; + __u32 uid; + __u64 timestamp; + char comm[16]; // Process name +}; + +// Define the ringbuffer map struct { __uint(type, BPF_MAP_TYPE_RINGBUF); - __uint(max_entries, 1 << 24); // 16 MB -} rb SEC(".maps"); - -//struct msg { -// u32 pid; -// char comm[16]; -//}; - -//SEC("tracepoint/syscalls/sys_enter_execve") -//int handle_execve(struct trace_event_raw_sys_enter *ctx) -//{ -// struct msg *m; -// m = bpf_ringbuf_reserve(&rb, sizeof(*m), 0); -// if (!m) -// return 0; -// -// m->pid = bpf_get_current_pid_tgid() >> 32; -// bpf_get_current_comm(&m->comm, sizeof(m->comm)); -// bpf_ringbuf_submit(m, 0); -// return 0; -//} - -//char LICENSE[] SEC("license") = "GPL"; + __uint(max_entries, 256 * 1024); // 256 KB +} events SEC(".maps"); + +// Tracepoint for execve system calls +SEC("tracepoint/syscalls/sys_enter_execve") +int trace_execve(void *ctx) +{ + struct event *e; + __u64 pid_tgid; + __u64 uid_gid; + + // Reserve space in the ringbuffer + e = bpf_ringbuf_reserve(&events, sizeof(*e), 0); + if (!e) + return 0; + + // Fill the struct with data + pid_tgid = bpf_get_current_pid_tgid(); + e->pid = pid_tgid >> 32; + + uid_gid = bpf_get_current_uid_gid(); + e->uid = uid_gid & 0xFFFFFFFF; + + e->timestamp = bpf_ktime_get_ns(); + + bpf_get_current_comm(&e->comm, sizeof(e->comm)); + + // Submit the event to ringbuffer + bpf_ringbuf_submit(e, 0); + + return 0; +} + +char LICENSE[] SEC("license") = "GPL"; diff --git a/tests/failing_tests/ringbuf.py b/tests/failing_tests/ringbuf.py index a6c60c8..0566d85 100644 --- a/tests/failing_tests/ringbuf.py +++ b/tests/failing_tests/ringbuf.py @@ -1,5 +1,5 @@ -from pythonbpf import bpf, map, bpfglobal, section -from pythonbpf.maps import RingBuf +from pythonbpf import bpf, BPF, map, bpfglobal, section, compile, compile_to_ir +from pythonbpf.maps import RingBuf, HashMap from ctypes import c_int32, c_void_p @@ -7,12 +7,19 @@ @bpf @map def mymap() -> RingBuf: - return RingBuf(max_entries=(1 << 24)) + return RingBuf(max_entries=(1024)) + + +@bpf +@map +def mymap2() -> HashMap: + return HashMap(key=c_int32, value=c_int32, max_entries=1024) @bpf @section("tracepoint/syscalls/sys_enter_clone") -def testing(ctx: c_void_p) -> c_int32: +def random_section(ctx: c_void_p) -> c_int32: + print("Hello") return c_int32(0) @@ -22,4 +29,7 @@ def LICENSE() -> str: return "GPL" +compile_to_ir("ringbuf.py", "ringbuf.ll") compile() +b = BPF() +b.load_and_attach()