From 5cbd9a531e0190530a89204c6e35d44d648a9c0f Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 27 Oct 2025 01:08:56 +0530 Subject: [PATCH 01/68] Add bpf_get_prandom_u32 helper --- pythonbpf/helper/__init__.py | 3 ++- pythonbpf/helper/bpf_helper_handler.py | 23 +++++++++++++++++++++++ pythonbpf/helper/helpers.py | 5 +++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/pythonbpf/helper/__init__.py b/pythonbpf/helper/__init__.py index 2f9c347..5c38137 100644 --- a/pythonbpf/helper/__init__.py +++ b/pythonbpf/helper/__init__.py @@ -1,7 +1,7 @@ from .helper_registry import HelperHandlerRegistry from .helper_utils import reset_scratch_pool from .bpf_helper_handler import handle_helper_call, emit_probe_read_kernel_str_call -from .helpers import ktime, pid, deref, comm, probe_read_str, XDP_DROP, XDP_PASS +from .helpers import ktime, pid, deref, comm, probe_read_str, random, XDP_DROP, XDP_PASS # Register the helper handler with expr module @@ -65,6 +65,7 @@ __all__ = [ "deref", "comm", "probe_read_str", + "random", "XDP_DROP", "XDP_PASS", ] diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 7868677..fa0e1e1 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -25,6 +25,7 @@ class BPFHelperID(Enum): BPF_MAP_DELETE_ELEM = 3 BPF_KTIME_GET_NS = 5 BPF_PRINTK = 6 + BPF_GET_PRANDOM_U32 = 7 BPF_GET_CURRENT_PID_TGID = 14 BPF_GET_CURRENT_COMM = 16 BPF_PERF_EVENT_OUTPUT = 25 @@ -433,6 +434,28 @@ def bpf_probe_read_kernel_str_emitter( return result, ir.IntType(64) +@HelperHandlerRegistry.register("random") +def bpf_get_prandom_u32_emitter( + call, + map_ptr, + module, + builder, + func, + local_sym_tab=None, + struct_sym_tab=None, + map_sym_tab=None, +): + """ + Emit LLVM IR for bpf_get_prandom_u32 helper function call. + """ + helper_id = ir.Constant(ir.IntType(64), BPFHelperID.BPF_GET_PRANDOM_U32.value) + fn_type = ir.FunctionType(ir.IntType(32), [], var_arg=False) + fn_ptr_type = ir.PointerType(fn_type) + fn_ptr = builder.inttoptr(helper_id, fn_ptr_type) + result = builder.call(fn_ptr, [], tail=False) + return result, ir.IntType(32) + + def handle_helper_call( call, module, diff --git a/pythonbpf/helper/helpers.py b/pythonbpf/helper/helpers.py index cb1a8e1..f8f2bd6 100644 --- a/pythonbpf/helper/helpers.py +++ b/pythonbpf/helper/helpers.py @@ -27,6 +27,11 @@ def probe_read_str(dst, src): return ctypes.c_int64(0) +def random(): + """get a pseudorandom u32 number""" + return ctypes.c_int32(0) + + XDP_ABORTED = ctypes.c_int64(0) XDP_DROP = ctypes.c_int64(1) XDP_PASS = ctypes.c_int64(2) From 0006e26b08f7d1aa0fe399c09df8136d6630ced9 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 27 Oct 2025 01:09:27 +0530 Subject: [PATCH 02/68] Add passing test for bpf_get_prandom_u32 implementation --- tests/passing_tests/helpers/prandom.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/passing_tests/helpers/prandom.py diff --git a/tests/passing_tests/helpers/prandom.py b/tests/passing_tests/helpers/prandom.py new file mode 100644 index 0000000..396927b --- /dev/null +++ b/tests/passing_tests/helpers/prandom.py @@ -0,0 +1,25 @@ +from pythonbpf import bpf, bpfglobal, section, BPF, trace_pipe +from ctypes import c_void_p, c_int64 +from pythonbpf.helper import random + + +@bpf +@section("tracepoint/syscalls/sys_enter_clone") +def hello_world(ctx: c_void_p) -> c_int64: + r = random() + print(f"Hello, World!, {r}") + return 0 # type: ignore [return-value] + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +# Compile and load +b = BPF() +b.load() +b.attach_all() + +trace_pipe() From 5bf60d69b8541f3ed1669273afcfd3151c7d5ce1 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 1 Nov 2025 12:52:15 +0530 Subject: [PATCH 03/68] Add BPF_PROBE_READ to HelperIDs --- pythonbpf/helper/bpf_helper_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index fa0e1e1..196c9d9 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -23,6 +23,7 @@ class BPFHelperID(Enum): BPF_MAP_LOOKUP_ELEM = 1 BPF_MAP_UPDATE_ELEM = 2 BPF_MAP_DELETE_ELEM = 3 + BPF_PROBE_READ = 4 BPF_KTIME_GET_NS = 5 BPF_PRINTK = 6 BPF_GET_PRANDOM_U32 = 7 From 2257c175edc1e222cec8af5b642c36f1c3e66af8 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 1 Nov 2025 13:14:50 +0530 Subject: [PATCH 04/68] Implement BPF_PROBE_READ helper --- pythonbpf/helper/bpf_helper_handler.py | 59 ++++++++++++++++++++++++++ pythonbpf/helper/helper_utils.py | 20 +++++++++ 2 files changed, 79 insertions(+) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 196c9d9..362bb41 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -10,6 +10,7 @@ from .helper_utils import ( get_buffer_ptr_and_size, get_char_array_ptr_and_size, get_ptr_from_arg, + get_int_value_from_arg, ) from .printk_formatter import simple_string_print, handle_fstring_print @@ -457,6 +458,64 @@ def bpf_get_prandom_u32_emitter( return result, ir.IntType(32) +@HelperHandlerRegistry.register("probe_read") +def bpf_probe_read_emitter( + call, + map_ptr, + module, + builder, + func, + local_sym_tab=None, + struct_sym_tab=None, + map_sym_tab=None, +): + """ + Emit LLVM IR for bpf_probe_read helper function + """ + + if len(call.args) != 3: + logger.warn("Expected 3 args for probe_read helper") + return + dst_ptr, _ = get_ptr_from_arg( + call.args[0], func, module, builder, local_sym_tab, map_sym_tab, struct_sym_tab + ) + size_val = ( + get_int_value_from_arg( + call.args[1], + func, + module, + builder, + local_sym_tab, + map_sym_tab, + struct_sym_tab, + ) + & 0xFFFFFFFF + ) + src_ptr, _ = get_ptr_from_arg( + call.args[2], func, module, builder, local_sym_tab, map_sym_tab, struct_sym_tab + ) + fn_type = ir.FunctionType( + ir.IntType(64), + [ir.PointerType(), ir.IntType(32), ir.PointerType()], + var_arg=False, + ) + fn_ptr = builder.inttoptr( + ir.Constant(ir.IntType(64), BPFHelperID.BPF_PROBE_READ.value), + ir.PointerType(fn_type), + ) + result = builder.call( + fn_ptr, + [ + builder.bitcast(dst_ptr, ir.PointerType()), + ir.Constant(ir.IntType(32), size_val), + builder.bitcast(src_ptr, ir.PointerType()), + ], + tail=False, + ) + logger.info(f"Emitted bpf_probe_read (size={size_val})") + return result, ir.IntType(64) + + def handle_helper_call( call, module, diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index fdfd452..841698c 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -274,3 +274,23 @@ def get_ptr_from_arg( raise ValueError(f"Expected pointer type, got {val_type}") return val, val_type + + +def get_int_value_from_arg( + arg, func, module, builder, local_sym_tab, map_sym_tab, struct_sym_tab +): + """Evaluate argument and return integer value""" + + result = eval_expr( + func, module, builder, arg, local_sym_tab, map_sym_tab, struct_sym_tab + ) + + if not result: + raise ValueError("Failed to evaluate argument") + + val, val_type = result + + if not isinstance(val_type, ir.IntType): + raise ValueError(f"Expected integer type, got {val_type}") + + return val From ec2ea835e5a69742c09294d0d262b136b2bfefcf Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 1 Nov 2025 13:50:23 +0530 Subject: [PATCH 05/68] Fix imports and type issues for bpf_probe_read --- pythonbpf/helper/__init__.py | 13 +++++++++++- pythonbpf/helper/bpf_helper_handler.py | 29 ++++++++++++-------------- pythonbpf/helper/helpers.py | 5 +++++ 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/pythonbpf/helper/__init__.py b/pythonbpf/helper/__init__.py index 5c38137..1bfa7ac 100644 --- a/pythonbpf/helper/__init__.py +++ b/pythonbpf/helper/__init__.py @@ -1,7 +1,17 @@ from .helper_registry import HelperHandlerRegistry from .helper_utils import reset_scratch_pool from .bpf_helper_handler import handle_helper_call, emit_probe_read_kernel_str_call -from .helpers import ktime, pid, deref, comm, probe_read_str, random, XDP_DROP, XDP_PASS +from .helpers import ( + ktime, + pid, + deref, + comm, + probe_read_str, + random, + probe_read, + XDP_DROP, + XDP_PASS, +) # Register the helper handler with expr module @@ -66,6 +76,7 @@ __all__ = [ "comm", "probe_read_str", "random", + "probe_read", "XDP_DROP", "XDP_PASS", ] diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 362bb41..420c1c5 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -476,23 +476,20 @@ def bpf_probe_read_emitter( if len(call.args) != 3: logger.warn("Expected 3 args for probe_read helper") return - dst_ptr, _ = get_ptr_from_arg( - call.args[0], func, module, builder, local_sym_tab, map_sym_tab, struct_sym_tab + dst_ptr = get_or_create_ptr_from_arg( + func, module, call.args[0], builder, local_sym_tab, map_sym_tab, struct_sym_tab ) - size_val = ( - get_int_value_from_arg( - call.args[1], - func, - module, - builder, - local_sym_tab, - map_sym_tab, - struct_sym_tab, - ) - & 0xFFFFFFFF + size_val = get_int_value_from_arg( + call.args[1], + func, + module, + builder, + local_sym_tab, + map_sym_tab, + struct_sym_tab, ) - src_ptr, _ = get_ptr_from_arg( - call.args[2], func, module, builder, local_sym_tab, map_sym_tab, struct_sym_tab + src_ptr = get_or_create_ptr_from_arg( + func, module, call.args[2], builder, local_sym_tab, map_sym_tab, struct_sym_tab ) fn_type = ir.FunctionType( ir.IntType(64), @@ -507,7 +504,7 @@ def bpf_probe_read_emitter( fn_ptr, [ builder.bitcast(dst_ptr, ir.PointerType()), - ir.Constant(ir.IntType(32), size_val), + builder.trunc(size_val, ir.IntType(32)), builder.bitcast(src_ptr, ir.PointerType()), ], tail=False, diff --git a/pythonbpf/helper/helpers.py b/pythonbpf/helper/helpers.py index f8f2bd6..440d311 100644 --- a/pythonbpf/helper/helpers.py +++ b/pythonbpf/helper/helpers.py @@ -32,6 +32,11 @@ def random(): return ctypes.c_int32(0) +def probe_read(dst, size, src): + """Safely read data from kernel memory""" + return ctypes.c_int64(0) + + XDP_ABORTED = ctypes.c_int64(0) XDP_DROP = ctypes.c_int64(1) XDP_PASS = ctypes.c_int64(2) From 70a04f54d1cbf72e9aa3091885741f50d48a725e Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 1 Nov 2025 13:51:08 +0530 Subject: [PATCH 06/68] Add passing test for bpf_probe_read helper --- tests/passing_tests/helpers/bpf_probe_read.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/passing_tests/helpers/bpf_probe_read.py diff --git a/tests/passing_tests/helpers/bpf_probe_read.py b/tests/passing_tests/helpers/bpf_probe_read.py new file mode 100644 index 0000000..fcece4d --- /dev/null +++ b/tests/passing_tests/helpers/bpf_probe_read.py @@ -0,0 +1,29 @@ +from pythonbpf import bpf, section, bpfglobal, compile, struct +from ctypes import c_void_p, c_int64, c_uint64, c_uint32 +from pythonbpf.helper import probe_read + + +@bpf +@struct +class data_t: + pid: c_uint32 + value: c_uint64 + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def test_probe_read(ctx: c_void_p) -> c_int64: + """Test bpf_probe_read helper function""" + data = data_t() + probe_read(data.value, 8, ctx) + probe_read(data.pid, 4, ctx) + return 0 + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 82cac8f8ef291db619f7bd8c9ba7c9c9943f9cd9 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 1 Nov 2025 14:02:07 +0530 Subject: [PATCH 07/68] Add BPF_GET_SMP_PROCESSOR_ID to HelperIDs --- pythonbpf/helper/bpf_helper_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 420c1c5..e231db9 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -28,6 +28,7 @@ class BPFHelperID(Enum): BPF_KTIME_GET_NS = 5 BPF_PRINTK = 6 BPF_GET_PRANDOM_U32 = 7 + BPF_GET_SMP_PROCESSOR_ID = 8 BPF_GET_CURRENT_PID_TGID = 14 BPF_GET_CURRENT_COMM = 16 BPF_PERF_EVENT_OUTPUT = 25 From 19dedede53cb3fa6f5a5eb41d2f33d6e214dc79b Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 1 Nov 2025 14:05:50 +0530 Subject: [PATCH 08/68] Implement BPF_GET_SMP_PROCESSOR_ID helper --- pythonbpf/helper/bpf_helper_handler.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index e231db9..230d4a8 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -514,6 +514,29 @@ def bpf_probe_read_emitter( return result, ir.IntType(64) +@HelperHandlerRegistry.register("smp_processor_id") +def bpf_get_smp_processor_id_emitter( + call, + map_ptr, + module, + builder, + func, + local_sym_tab=None, + struct_sym_tab=None, + map_sym_tab=None, +): + """ + Emit LLVM IR for bpf_get_smp_processor_id helper function call. + """ + helper_id = ir.Constant(ir.IntType(64), BPFHelperID.BPF_GET_SMP_PROCESSOR_ID.value) + fn_type = ir.FunctionType(ir.IntType(32), [], var_arg=False) + fn_ptr_type = ir.PointerType(fn_type) + fn_ptr = builder.inttoptr(helper_id, fn_ptr_type) + result = builder.call(fn_ptr, [], tail=False) + logger.info("Emitted bpf_get_smp_processor_id call") + return result, ir.IntType(32) + + def handle_helper_call( call, module, From dabb8bf0df0779f433998eed2e993cd05391b31e Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 1 Nov 2025 14:07:47 +0530 Subject: [PATCH 09/68] Fix imports for BPF_GET_SMP_PROCESSOR_ID --- pythonbpf/helper/__init__.py | 2 ++ pythonbpf/helper/helpers.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/pythonbpf/helper/__init__.py b/pythonbpf/helper/__init__.py index 1bfa7ac..ee07d5a 100644 --- a/pythonbpf/helper/__init__.py +++ b/pythonbpf/helper/__init__.py @@ -9,6 +9,7 @@ from .helpers import ( probe_read_str, random, probe_read, + smp_processor_id, XDP_DROP, XDP_PASS, ) @@ -77,6 +78,7 @@ __all__ = [ "probe_read_str", "random", "probe_read", + "smp_processor_id", "XDP_DROP", "XDP_PASS", ] diff --git a/pythonbpf/helper/helpers.py b/pythonbpf/helper/helpers.py index 440d311..6bed131 100644 --- a/pythonbpf/helper/helpers.py +++ b/pythonbpf/helper/helpers.py @@ -37,6 +37,11 @@ def probe_read(dst, size, src): return ctypes.c_int64(0) +def smp_processor_id(): + """get the current CPU id""" + return ctypes.c_int32(0) + + XDP_ABORTED = ctypes.c_int64(0) XDP_DROP = ctypes.c_int64(1) XDP_PASS = ctypes.c_int64(2) From f9ee43e7ef41e5b75d3981420616426af6805643 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 1 Nov 2025 14:13:52 +0530 Subject: [PATCH 10/68] Add passing test smp_processor_id.py for helpers --- .../passing_tests/helpers/smp_processor_id.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/passing_tests/helpers/smp_processor_id.py diff --git a/tests/passing_tests/helpers/smp_processor_id.py b/tests/passing_tests/helpers/smp_processor_id.py new file mode 100644 index 0000000..8c17a75 --- /dev/null +++ b/tests/passing_tests/helpers/smp_processor_id.py @@ -0,0 +1,40 @@ +from pythonbpf import bpf, section, bpfglobal, compile, struct +from ctypes import c_void_p, c_int64, c_uint32, c_uint64 +from pythonbpf.helper import smp_processor_id, ktime + + +@bpf +@struct +class cpu_event_t: + cpu_id: c_uint32 + timestamp: c_uint64 + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def trace_with_cpu(ctx: c_void_p) -> c_int64: + """Test bpf_get_smp_processor_id helper function""" + + # Get the current CPU ID + cpu = smp_processor_id() + + # Print it + print(f"Running on CPU {cpu}") + + # Use it in a struct + event = cpu_event_t() + event.cpu_id = smp_processor_id() + event.timestamp = ktime() + + print(f"Event on CPU {event.cpu_id} at time {event.timestamp}") + + return 0 + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 204ec26154f4d17eb67d5bac9fecee7c4a7d6685 Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Sat, 1 Nov 2025 14:43:19 +0530 Subject: [PATCH 11/68] add i32 support and make it extensible --- pythonbpf/allocation_pass.py | 43 +++++++++++++++- pythonbpf/functions/function_debug_info.py | 1 - pythonbpf/type_deducer.py | 2 + .../vmlinux_parser/vmlinux_exports_handler.py | 50 +++++++++++++++---- tests/c-form/i32test.bpf.c | 15 ++++++ tests/failing_tests/vmlinux/args_test.py | 30 +++++++++++ tests/failing_tests/vmlinux/i32_test.py | 23 +++++++++ .../vmlinux/simple_struct_test.py | 2 +- 8 files changed, 153 insertions(+), 13 deletions(-) create mode 100644 tests/c-form/i32test.bpf.c create mode 100644 tests/failing_tests/vmlinux/args_test.py create mode 100644 tests/failing_tests/vmlinux/i32_test.py diff --git a/pythonbpf/allocation_pass.py b/pythonbpf/allocation_pass.py index b96a9cf..4955f3f 100644 --- a/pythonbpf/allocation_pass.py +++ b/pythonbpf/allocation_pass.py @@ -1,6 +1,6 @@ import ast import logging - +import ctypes from llvmlite import ir from .local_symbol import LocalSymbol from pythonbpf.helper import HelperHandlerRegistry @@ -249,7 +249,46 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_ ].var = base_ptr # This is repurposing of var to store the pointer of the base type local_sym_tab[struct_var].ir_type = field_ir - actual_ir_type = ir.IntType(64) + # Determine the actual IR type based on the field's type + actual_ir_type = None + + # Check if it's a ctypes primitive + if field.type.__module__ == ctypes.__name__: + try: + field_size_bytes = ctypes.sizeof(field.type) + field_size_bits = field_size_bytes * 8 + + if field_size_bits in [8, 16, 32, 64]: + actual_ir_type = ir.IntType(field_size_bits) + else: + logger.warning( + f"Unusual field size {field_size_bits} bits for {field_name}" + ) + actual_ir_type = ir.IntType(64) + except Exception as e: + logger.warning( + f"Could not determine size for ctypes field {field_name}: {e}" + ) + actual_ir_type = ir.IntType(64) + + # Check if it's a nested vmlinux struct or complex type + elif field.type.__module__ == "vmlinux": + # For pointers to structs, use pointer type (64-bit) + if field.ctype_complex_type is not None and issubclass( + field.ctype_complex_type, ctypes._Pointer + ): + actual_ir_type = ir.IntType(64) # Pointer is always 64-bit + # For embedded structs, this is more complex - might need different handling + else: + logger.warning( + f"Field {field_name} is a nested vmlinux struct, using i64 for now" + ) + actual_ir_type = ir.IntType(64) + else: + logger.warning( + f"Unknown field type module {field.type.__module__} for {field_name}" + ) + actual_ir_type = ir.IntType(64) # Allocate with the actual IR type, not the GlobalVariable var = _allocate_with_type(builder, var_name, actual_ir_type) diff --git a/pythonbpf/functions/function_debug_info.py b/pythonbpf/functions/function_debug_info.py index f924ebc..e22e9ad 100644 --- a/pythonbpf/functions/function_debug_info.py +++ b/pythonbpf/functions/function_debug_info.py @@ -59,7 +59,6 @@ def generate_function_debug_info( leading_argument_name, 1, pointer_to_context_debug_info ) retained_nodes = [context_local_variable] - print("function name", func_node.name) subprogram_debug_info = generator.create_subprogram( func_node.name, subroutine_type, retained_nodes ) diff --git a/pythonbpf/type_deducer.py b/pythonbpf/type_deducer.py index a6834a9..fd589ae 100644 --- a/pythonbpf/type_deducer.py +++ b/pythonbpf/type_deducer.py @@ -16,6 +16,8 @@ mapping = { "c_long": ir.IntType(64), "c_ulong": ir.IntType(64), "c_longlong": ir.IntType(64), + "c_uint": ir.IntType(32), + "c_int": ir.IntType(32), # Not so sure about this one "str": ir.PointerType(ir.IntType(8)), } diff --git a/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py b/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py index 62c0327..6ec5988 100644 --- a/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py +++ b/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py @@ -1,6 +1,6 @@ import logging from typing import Any - +import ctypes from llvmlite import ir from pythonbpf.local_symbol import LocalSymbol @@ -98,18 +98,16 @@ class VmlinuxHandler: python_type.__name__, field_name ) builder.function.args[0].type = ir.PointerType(ir.IntType(8)) - print(builder.function.args[0]) field_ptr = self.load_ctx_field( - builder, builder.function.args[0], globvar_ir + builder, builder.function.args[0], globvar_ir, field_data ) - print(field_ptr) # Return pointer to field and field type return field_ptr, field_data else: raise RuntimeError("Variable accessed not found in symbol table") @staticmethod - def load_ctx_field(builder, ctx_arg, offset_global): + def load_ctx_field(builder, ctx_arg, offset_global, field_data): """ Generate LLVM IR to load a field from BPF context using offset. @@ -117,7 +115,7 @@ class VmlinuxHandler: builder: llvmlite IRBuilder instance ctx_arg: The context pointer argument (ptr/i8*) offset_global: Global variable containing the field offset (i64) - + field_data: contains data about the field Returns: The loaded value (i64 register) """ @@ -164,9 +162,43 @@ class VmlinuxHandler: passthrough_fn, [ir.Constant(ir.IntType(32), 0), field_ptr], tail=True ) - # Bitcast to i64* (assuming field is 64-bit, adjust if needed) - i64_ptr_type = ir.PointerType(ir.IntType(64)) - typed_ptr = builder.bitcast(verified_ptr, i64_ptr_type) + # Determine the appropriate IR type based on field information + int_width = 64 # Default to 64-bit + + if field_data is not None: + # Try to determine the size from field metadata + if field_data.type.__module__ == ctypes.__name__: + try: + field_size_bytes = ctypes.sizeof(field_data.type) + field_size_bits = field_size_bytes * 8 + + if field_size_bits in [8, 16, 32, 64]: + int_width = field_size_bits + logger.info(f"Determined field size: {int_width} bits") + else: + logger.warning( + f"Unusual field size {field_size_bits} bits, using default 64" + ) + except Exception as e: + logger.warning( + f"Could not determine field size: {e}, using default 64" + ) + + elif field_data.type.__module__ == "vmlinux": + # For pointers to structs or complex vmlinux types + if field_data.ctype_complex_type is not None and issubclass( + field_data.ctype_complex_type, ctypes._Pointer + ): + int_width = 64 # Pointers are always 64-bit + logger.info("Field is a pointer type, using 64 bits") + # TODO: Add handling for other complex types (arrays, embedded structs, etc.) + else: + logger.warning("Complex vmlinux field type, using default 64 bits") + + # Bitcast to appropriate pointer type based on determined width + ptr_type = ir.PointerType(ir.IntType(int_width)) + + typed_ptr = builder.bitcast(verified_ptr, ptr_type) # Load and return the value value = builder.load(typed_ptr) diff --git a/tests/c-form/i32test.bpf.c b/tests/c-form/i32test.bpf.c new file mode 100644 index 0000000..71b479f --- /dev/null +++ b/tests/c-form/i32test.bpf.c @@ -0,0 +1,15 @@ +#include +#include + +SEC("xdp") +int print_xdp_data(struct xdp_md *ctx) +{ + // 'data' is a pointer to the start of packet data + void *data = (void *)(long)ctx->data; + + bpf_printk("ctx->data = %p\n", data); + + return XDP_PASS; +} + +char LICENSE[] SEC("license") = "GPL"; diff --git a/tests/failing_tests/vmlinux/args_test.py b/tests/failing_tests/vmlinux/args_test.py new file mode 100644 index 0000000..7acca25 --- /dev/null +++ b/tests/failing_tests/vmlinux/args_test.py @@ -0,0 +1,30 @@ +import logging + +from pythonbpf import bpf, section, bpfglobal, compile_to_ir +from pythonbpf import compile # noqa: F401 +from vmlinux import TASK_COMM_LEN # noqa: F401 +from vmlinux import struct_trace_event_raw_sys_enter # noqa: F401 +from ctypes import c_int64, c_int32, c_void_p # noqa: F401 + + +# from vmlinux import struct_uinput_device +# from vmlinux import struct_blk_integrity_iter + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: struct_trace_event_raw_sys_enter) -> c_int64: + b = ctx.args + c = b[0] + print(f"This is context args field {c}") + return c_int64(0) + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile_to_ir("args_test.py", "args_test.ll", loglevel=logging.INFO) +compile() diff --git a/tests/failing_tests/vmlinux/i32_test.py b/tests/failing_tests/vmlinux/i32_test.py new file mode 100644 index 0000000..86ce2b7 --- /dev/null +++ b/tests/failing_tests/vmlinux/i32_test.py @@ -0,0 +1,23 @@ +from ctypes import c_int64, c_int32 +from pythonbpf import bpf, section, bpfglobal, compile_to_ir, compile +from vmlinux import struct_xdp_md +from vmlinux import XDP_PASS + + +@bpf +@section("xdp") +def print_xdp_data(ctx: struct_xdp_md) -> c_int64: + data = ctx.data # 32-bit field: packet start pointer + something = c_int32(2 + data) + print(f"ctx->data = {something}") + return c_int64(XDP_PASS) + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile_to_ir("i32_test.py", "i32_test.ll") +compile() diff --git a/tests/passing_tests/vmlinux/simple_struct_test.py b/tests/passing_tests/vmlinux/simple_struct_test.py index 97ab54a..2f34ba4 100644 --- a/tests/passing_tests/vmlinux/simple_struct_test.py +++ b/tests/passing_tests/vmlinux/simple_struct_test.py @@ -44,4 +44,4 @@ def LICENSE() -> str: compile_to_ir("simple_struct_test.py", "simple_struct_test.ll", loglevel=logging.DEBUG) -# compile() +compile() From 3489f45b6317a11ded36f2ceb69ef5fe40f43d49 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 1 Nov 2025 18:57:07 +0530 Subject: [PATCH 12/68] Add failing XDP vmlinux tests --- .../failing_tests/vmlinux/i32_test_fail_1.py | 24 +++++++++++++++++++ .../failing_tests/vmlinux/i32_test_fail_2.py | 23 ++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 tests/failing_tests/vmlinux/i32_test_fail_1.py create mode 100644 tests/failing_tests/vmlinux/i32_test_fail_2.py diff --git a/tests/failing_tests/vmlinux/i32_test_fail_1.py b/tests/failing_tests/vmlinux/i32_test_fail_1.py new file mode 100644 index 0000000..3f6d3c1 --- /dev/null +++ b/tests/failing_tests/vmlinux/i32_test_fail_1.py @@ -0,0 +1,24 @@ +from ctypes import c_int64 +from pythonbpf import bpf, section, bpfglobal, compile +from vmlinux import struct_xdp_md +from vmlinux import XDP_PASS +import logging + + +@bpf +@section("xdp") +def print_xdp_data(ctx: struct_xdp_md) -> c_int64: + data = 0 + data = ctx.data # 32-bit field: packet start pointer + something = 2 + data + print(f"ctx->data = {something}") + return c_int64(XDP_PASS) + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile(logging.INFO) diff --git a/tests/failing_tests/vmlinux/i32_test_fail_2.py b/tests/failing_tests/vmlinux/i32_test_fail_2.py new file mode 100644 index 0000000..7a628f3 --- /dev/null +++ b/tests/failing_tests/vmlinux/i32_test_fail_2.py @@ -0,0 +1,23 @@ +from ctypes import c_int64 +from pythonbpf import bpf, section, bpfglobal, compile +from vmlinux import struct_xdp_md +from vmlinux import XDP_PASS +import logging + + +@bpf +@section("xdp") +def print_xdp_data(ctx: struct_xdp_md) -> c_int64: + data = c_int64(ctx.data) # 32-bit field: packet start pointer + something = 2 + data + print(f"ctx->data = {something}") + return c_int64(XDP_PASS) + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile(logging.INFO) From 8b28a927c3cb2c5ab0edcc11fe3486199e3a3bed Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 2 Nov 2025 03:27:27 +0530 Subject: [PATCH 13/68] Add helpful TODO to PID_TGID emitter --- pythonbpf/helper/bpf_helper_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 230d4a8..f755ce9 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -322,6 +322,7 @@ def bpf_get_current_pid_tgid_emitter( result = builder.call(fn_ptr, [], tail=False) # Extract the lower 32 bits (PID) using bitwise AND with 0xFFFFFFFF + # TODO: return both PID and TGID if we end up needing TGID somewhere mask = ir.Constant(ir.IntType(64), 0xFFFFFFFF) pid = builder.and_(result, mask) return pid, ir.IntType(64) From b7c1e92f0501cafa427e17c69ea814f10767e471 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 2 Nov 2025 03:29:02 +0530 Subject: [PATCH 14/68] Add BPF_GET_CURRENT_UID_GID to HelperIDs --- pythonbpf/helper/bpf_helper_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index f755ce9..c434f34 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -30,6 +30,7 @@ class BPFHelperID(Enum): BPF_GET_PRANDOM_U32 = 7 BPF_GET_SMP_PROCESSOR_ID = 8 BPF_GET_CURRENT_PID_TGID = 14 + BPF_GET_CURRENT_UID_GID = 15 BPF_GET_CURRENT_COMM = 16 BPF_PERF_EVENT_OUTPUT = 25 BPF_PROBE_READ_KERNEL_STR = 115 From 5b7769dd38671ab21f8d7fe485546821faf94b2f Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 2 Nov 2025 03:34:04 +0530 Subject: [PATCH 15/68] Implement bpf_get_current_uid_gid_emitter --- pythonbpf/helper/bpf_helper_handler.py | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index c434f34..762a0b2 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -539,6 +539,33 @@ def bpf_get_smp_processor_id_emitter( return result, ir.IntType(32) +@HelperHandlerRegistry.register("uid") +def bpf_get_current_uid_gid_emitter( + call, + map_ptr, + module, + builder, + func, + local_sym_tab=None, + struct_sym_tab=None, + map_sym_tab=None, +): + """ + Emit LLVM IR for bpf_get_current_uid_gid helper function call. + """ + helper_id = ir.Constant(ir.IntType(64), BPFHelperID.BPF_GET_CURRENT_UID_GID.value) + fn_type = ir.FunctionType(ir.IntType(64), [], var_arg=False) + fn_ptr_type = ir.PointerType(fn_type) + fn_ptr = builder.inttoptr(helper_id, fn_ptr_type) + result = builder.call(fn_ptr, [], tail=False) + + # Extract the lower 32 bits (UID) using bitwise AND with 0xFFFFFFFF + # TODO: return both UID and GID if we end up needing GID somewhere + mask = ir.Constant(ir.IntType(64), 0xFFFFFFFF) + pid = builder.and_(result, mask) + return pid, ir.IntType(64) + + def handle_helper_call( call, module, From 4884ed7577bdc04806bcf09449d23c947f8d035a Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 2 Nov 2025 03:35:41 +0530 Subject: [PATCH 16/68] Fix imports for bpf_get_current_uid_gid --- pythonbpf/helper/__init__.py | 2 ++ pythonbpf/helper/helpers.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/pythonbpf/helper/__init__.py b/pythonbpf/helper/__init__.py index ee07d5a..a243140 100644 --- a/pythonbpf/helper/__init__.py +++ b/pythonbpf/helper/__init__.py @@ -10,6 +10,7 @@ from .helpers import ( random, probe_read, smp_processor_id, + uid, XDP_DROP, XDP_PASS, ) @@ -79,6 +80,7 @@ __all__ = [ "random", "probe_read", "smp_processor_id", + "uid", "XDP_DROP", "XDP_PASS", ] diff --git a/pythonbpf/helper/helpers.py b/pythonbpf/helper/helpers.py index 6bed131..84ffd9a 100644 --- a/pythonbpf/helper/helpers.py +++ b/pythonbpf/helper/helpers.py @@ -42,6 +42,11 @@ def smp_processor_id(): return ctypes.c_int32(0) +def uid(): + """get current user id""" + return ctypes.c_int32(0) + + XDP_ABORTED = ctypes.c_int64(0) XDP_DROP = ctypes.c_int64(1) XDP_PASS = ctypes.c_int64(2) From 4efd3223cd47b62393c8549f371856569ae882ac Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 2 Nov 2025 03:47:26 +0530 Subject: [PATCH 17/68] Add passing uid_gid helper test --- tests/passing_tests/helpers/uid_gid.py | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/passing_tests/helpers/uid_gid.py diff --git a/tests/passing_tests/helpers/uid_gid.py b/tests/passing_tests/helpers/uid_gid.py new file mode 100644 index 0000000..e4e50b4 --- /dev/null +++ b/tests/passing_tests/helpers/uid_gid.py @@ -0,0 +1,31 @@ +from pythonbpf import bpf, section, bpfglobal, compile +from ctypes import c_void_p, c_int64 +from pythonbpf.helper import uid, pid + + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def filter_by_user(ctx: c_void_p) -> c_int64: + """Filter events by specific user ID""" + + current_uid = uid() + + # Only trace root user (UID 0) + if current_uid == 0: + process_id = pid() + print(f"Root process {process_id} executed") + + # Or trace specific user (e.g., UID 1000) + if current_uid == 1002: + print("User 1002 executed something") + + return 0 + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From c5de92b9d0ef5f2482ad0a3cf5d923e90b5c5623 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 2 Nov 2025 04:17:15 +0530 Subject: [PATCH 18/68] Add BPF_SKB_STORE_BYTES to HelperIDs --- pythonbpf/helper/bpf_helper_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 762a0b2..9a6304f 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -29,6 +29,7 @@ class BPFHelperID(Enum): BPF_PRINTK = 6 BPF_GET_PRANDOM_U32 = 7 BPF_GET_SMP_PROCESSOR_ID = 8 + BPF_SKB_STORE_BYTES = 9 BPF_GET_CURRENT_PID_TGID = 14 BPF_GET_CURRENT_UID_GID = 15 BPF_GET_CURRENT_COMM = 16 From f757a32a639ae01aa1a592ef924e7dc2a140c75e Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 2 Nov 2025 04:32:05 +0530 Subject: [PATCH 19/68] Implement bpf_skb_store_bytes_emitter --- pythonbpf/helper/bpf_helper_handler.py | 79 ++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 9a6304f..abf35ef 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -567,6 +567,85 @@ def bpf_get_current_uid_gid_emitter( return pid, ir.IntType(64) +@HelperHandlerRegistry.register("skb_store_bytes") +def bpf_skb_store_bytes_emitter( + call, + map_ptr, + module, + builder, + func, + local_sym_tab=None, + struct_sym_tab=None, + map_sym_tab=None, +): + """ + Emit LLVM IR for bpf_skb_store_bytes helper function call. + Expected call signature: skb_store_bytes(skb, offset, from, len, flags) + """ + + if len(call.args) not in (4, 5): + raise ValueError( + f"skb_store_bytes expects 4 or 5 args (skb, offset, from, len, flags), got {len(call.args)}" + ) + + skb_ptr = get_or_create_ptr_from_arg( + func, module, call.args[0], builder, local_sym_tab, map_sym_tab, struct_sym_tab + ) + offset_val = get_int_value_from_arg( + call.args[1], + func, + module, + builder, + local_sym_tab, + map_sym_tab, + struct_sym_tab, + ) + from_ptr = get_or_create_ptr_from_arg( + func, module, call.args[2], builder, local_sym_tab, map_sym_tab, struct_sym_tab + ) + len_val = get_int_value_from_arg( + call.args[3], + func, + module, + builder, + local_sym_tab, + map_sym_tab, + struct_sym_tab, + ) + if len(call.args) == 5: + flags_val = get_flags_val(call.args[4], builder, local_sym_tab) + else: + flags_val = ir.Constant(ir.IntType(64), 0) + fn_type = ir.FunctionType( + ir.IntType(64), + [ + ir.PointerType(), # skb + ir.IntType(32), # offset + ir.PointerType(), # from + ir.IntType(32), # len + ir.IntType(64), # flags + ], + var_arg=False, + ) + fn_ptr = builder.inttoptr( + ir.Constant(ir.IntType(64), BPFHelperID.BPF_SKB_STORE_BYTES.value), + ir.PointerType(fn_type), + ) + result = builder.call( + fn_ptr, + [ + builder.bitcast(skb_ptr, ir.PointerType()), + builder.trunc(offset_val, ir.IntType(32)), + builder.bitcast(from_ptr, ir.PointerType()), + builder.trunc(len_val, ir.IntType(32)), + flags_val, + ], + tail=False, + ) + logger.info("Emitted bpf_skb_store_bytes call") + return result, ir.IntType(64) + + def handle_helper_call( call, module, From 67c9d9b9326a94a9154dd76cf1ee2d2790f4c4e5 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 2 Nov 2025 04:33:45 +0530 Subject: [PATCH 20/68] Fix imports for bpf_skb_store_bytes --- pythonbpf/helper/__init__.py | 2 ++ pythonbpf/helper/helpers.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/pythonbpf/helper/__init__.py b/pythonbpf/helper/__init__.py index a243140..4c1d283 100644 --- a/pythonbpf/helper/__init__.py +++ b/pythonbpf/helper/__init__.py @@ -11,6 +11,7 @@ from .helpers import ( probe_read, smp_processor_id, uid, + skb_store_bytes, XDP_DROP, XDP_PASS, ) @@ -81,6 +82,7 @@ __all__ = [ "probe_read", "smp_processor_id", "uid", + "skb_store_bytes", "XDP_DROP", "XDP_PASS", ] diff --git a/pythonbpf/helper/helpers.py b/pythonbpf/helper/helpers.py index 84ffd9a..0e55a20 100644 --- a/pythonbpf/helper/helpers.py +++ b/pythonbpf/helper/helpers.py @@ -47,6 +47,11 @@ def uid(): return ctypes.c_int32(0) +def skb_store_bytes(skb, offset, from_buf, size, flags=0): + """store bytes into a socket buffer""" + return ctypes.c_int64(0) + + XDP_ABORTED = ctypes.c_int64(0) XDP_DROP = ctypes.c_int64(1) XDP_PASS = ctypes.c_int64(2) From 5e371787ebed70da1d7d37e282532e207f14ddfa Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 3 Nov 2025 21:11:16 +0530 Subject: [PATCH 21/68] Fix the number of args for skb_store_bytes by making the first arg implicit --- pythonbpf/helper/bpf_helper_handler.py | 23 +++++++++++------------ pythonbpf/helper/helpers.py | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index abf35ef..8421c45 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -583,16 +583,14 @@ def bpf_skb_store_bytes_emitter( Expected call signature: skb_store_bytes(skb, offset, from, len, flags) """ - if len(call.args) not in (4, 5): + if len(call.args) not in (3, 4): raise ValueError( - f"skb_store_bytes expects 4 or 5 args (skb, offset, from, len, flags), got {len(call.args)}" + f"skb_store_bytes expects 3 or 4 args (offset, from, len, flags), got {len(call.args)}" ) - skb_ptr = get_or_create_ptr_from_arg( - func, module, call.args[0], builder, local_sym_tab, map_sym_tab, struct_sym_tab - ) + skb_ptr = func.args[0] # First argument to the function is skb offset_val = get_int_value_from_arg( - call.args[1], + call.args[0], func, module, builder, @@ -601,10 +599,10 @@ def bpf_skb_store_bytes_emitter( struct_sym_tab, ) from_ptr = get_or_create_ptr_from_arg( - func, module, call.args[2], builder, local_sym_tab, map_sym_tab, struct_sym_tab + func, module, call.args[1], builder, local_sym_tab, map_sym_tab, struct_sym_tab ) len_val = get_int_value_from_arg( - call.args[3], + call.args[2], func, module, builder, @@ -612,10 +610,11 @@ def bpf_skb_store_bytes_emitter( map_sym_tab, struct_sym_tab, ) - if len(call.args) == 5: - flags_val = get_flags_val(call.args[4], builder, local_sym_tab) + if len(call.args) == 4: + flags_val = get_flags_val(call.args[3], builder, local_sym_tab) else: - flags_val = ir.Constant(ir.IntType(64), 0) + flags_val = 0 + flags = ir.Constant(ir.IntType(64), flags_val) fn_type = ir.FunctionType( ir.IntType(64), [ @@ -638,7 +637,7 @@ def bpf_skb_store_bytes_emitter( builder.trunc(offset_val, ir.IntType(32)), builder.bitcast(from_ptr, ir.PointerType()), builder.trunc(len_val, ir.IntType(32)), - flags_val, + flags, ], tail=False, ) diff --git a/pythonbpf/helper/helpers.py b/pythonbpf/helper/helpers.py index 0e55a20..302b526 100644 --- a/pythonbpf/helper/helpers.py +++ b/pythonbpf/helper/helpers.py @@ -47,7 +47,7 @@ def uid(): return ctypes.c_int32(0) -def skb_store_bytes(skb, offset, from_buf, size, flags=0): +def skb_store_bytes(offset, from_buf, size, flags=0): """store bytes into a socket buffer""" return ctypes.c_int64(0) From 33e18f6d6dcb1b2dd84229766b56214559c37019 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 3 Nov 2025 21:21:13 +0530 Subject: [PATCH 22/68] Introduce HelperSignature in HelperHandlerRegistry --- pythonbpf/helper/helper_registry.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/pythonbpf/helper/helper_registry.py b/pythonbpf/helper/helper_registry.py index 476e3b6..7fbb597 100644 --- a/pythonbpf/helper/helper_registry.py +++ b/pythonbpf/helper/helper_registry.py @@ -1,17 +1,31 @@ +from dataclasses import dataclass +from llvmlite import ir from typing import Callable +@dataclass +class HelperSignature: + """Signature of a BPF helper function""" + + arg_types: list[ir.Type] + return_type: ir.Type + func: Callable + + class HelperHandlerRegistry: """Registry for BPF helpers""" - _handlers: dict[str, Callable] = {} + _handlers: dict[str, HelperSignature] = {} @classmethod - def register(cls, helper_name): + def register(cls, helper_name, param_types=None, return_type=None): """Decorator to register a handler function for a helper""" def decorator(func): - cls._handlers[helper_name] = func + helper_sig = HelperSignature( + arg_types=param_types, return_type=return_type, func=func + ) + cls._handlers[helper_name] = helper_sig return func return decorator @@ -19,7 +33,8 @@ class HelperHandlerRegistry: @classmethod def get_handler(cls, helper_name): """Get the handler function for a helper""" - return cls._handlers.get(helper_name) + handler = cls._handlers.get(helper_name) + return handler.func if handler else None @classmethod def has_handler(cls, helper_name): From d8cddb9799ae7000692d372dbf0e766a57bd98c9 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 4 Nov 2025 05:19:22 +0530 Subject: [PATCH 23/68] Add signature extraction to HelperHandlerRegistry --- pythonbpf/helper/helper_registry.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pythonbpf/helper/helper_registry.py b/pythonbpf/helper/helper_registry.py index 7fbb597..dccb8c2 100644 --- a/pythonbpf/helper/helper_registry.py +++ b/pythonbpf/helper/helper_registry.py @@ -40,3 +40,22 @@ class HelperHandlerRegistry: def has_handler(cls, helper_name): """Check if a handler function is registered for a helper""" return helper_name in cls._handlers + + @classmethod + def get_signature(cls, helper_name): + """Get the signature of a helper function""" + return cls._handlers.get(helper_name) + + @classmethod + def get_param_type(cls, helper_name, index): + """Get the type of a parameter of a helper function by the index""" + signature = cls.get_signature(helper_name) + if signature and 0 <= index < len(signature.arg_types): + return signature.arg_types[index] + return None + + @classmethod + def get_return_type(cls, helper_name): + """Get the return type of a helper function""" + signature = cls.get_signature(helper_name) + return signature.return_type if signature else None From 752f564d3f2e2b1676aeca584854df3365649f7f Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 4 Nov 2025 05:40:22 +0530 Subject: [PATCH 24/68] Change count_temps_in_call to return hashmap of types --- pythonbpf/functions/functions_pass.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pythonbpf/functions/functions_pass.py b/pythonbpf/functions/functions_pass.py index e391092..47d83b5 100644 --- a/pythonbpf/functions/functions_pass.py +++ b/pythonbpf/functions/functions_pass.py @@ -33,7 +33,7 @@ logger = logging.getLogger(__name__) def count_temps_in_call(call_node, local_sym_tab): """Count the number of temporary variables needed for a function call.""" - count = 0 + count = {} is_helper = False # NOTE: We exclude print calls for now @@ -43,21 +43,26 @@ def count_temps_in_call(call_node, local_sym_tab): and call_node.func.id != "print" ): is_helper = True + func_name = call_node.func.id elif isinstance(call_node.func, ast.Attribute): if HelperHandlerRegistry.has_handler(call_node.func.attr): is_helper = True + func_name = call_node.func.attr if not is_helper: return 0 - for arg in call_node.args: + for arg_idx in range(len(call_node.args)): # NOTE: Count all non-name arguments # For struct fields, if it is being passed as an argument, # The struct object should already exist in the local_sym_tab - if not isinstance(arg, ast.Name) and not ( + arg = call_node.args[arg_idx] + if isinstance(arg, ast.Name) or ( isinstance(arg, ast.Attribute) and arg.value.id in local_sym_tab ): - count += 1 + continue + param_type = HelperHandlerRegistry.get_param_type(func_name, arg_idx) + count[param_type] = count.get(param_type, 0) + 1 return count From 123a92af1d624e6256e693976585b592727f0688 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 4 Nov 2025 06:20:39 +0530 Subject: [PATCH 25/68] Change allocation pass to generate typed temp variables --- pythonbpf/allocation_pass.py | 30 ++++++++++++++++++++------- pythonbpf/functions/functions_pass.py | 15 ++++++++++---- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/pythonbpf/allocation_pass.py b/pythonbpf/allocation_pass.py index 49c787f..ae748dd 100644 --- a/pythonbpf/allocation_pass.py +++ b/pythonbpf/allocation_pass.py @@ -199,17 +199,33 @@ def _allocate_for_binop(builder, var_name, local_sym_tab): logger.info(f"Pre-allocated {var_name} for binop result") +def _get_type_name(ir_type): + """Get a string representation of an IR type.""" + if isinstance(ir_type, ir.IntType): + return f"i{ir_type.width}" + elif isinstance(ir_type, ir.PointerType): + return "ptr" + elif isinstance(ir_type, ir.ArrayType): + return f"[{ir_type.count}x{_get_type_name(ir_type.element)}]" + else: + return str(ir_type).replace(" ", "") + + def allocate_temp_pool(builder, max_temps, local_sym_tab): """Allocate the temporary scratch space pool for helper arguments.""" - if max_temps == 0: + if not max_temps: + logger.info("No temp pool allocation needed") return - logger.info(f"Allocating temp pool of {max_temps} variables") - for i in range(max_temps): - temp_name = f"__helper_temp_{i}" - temp_var = builder.alloca(ir.IntType(64), name=temp_name) - temp_var.align = 8 - local_sym_tab[temp_name] = LocalSymbol(temp_var, ir.IntType(64)) + for tmp_type, cnt in max_temps.items(): + type_name = _get_type_name(tmp_type) + logger.info(f"Allocating temp pool of {cnt} variables of type {type_name}") + for i in range(cnt): + temp_name = f"__helper_temp_{type_name}_{i}" + temp_var = builder.alloca(tmp_type, name=temp_name) + temp_var.align = _get_alignment(tmp_type) + local_sym_tab[temp_name] = LocalSymbol(temp_var, tmp_type) + logger.debug(f"Allocated temp variable: {temp_name}") def _allocate_for_name(builder, var_name, rval, local_sym_tab): diff --git a/pythonbpf/functions/functions_pass.py b/pythonbpf/functions/functions_pass.py index 47d83b5..c652d3e 100644 --- a/pythonbpf/functions/functions_pass.py +++ b/pythonbpf/functions/functions_pass.py @@ -98,11 +98,15 @@ def handle_if_allocation( def allocate_mem( module, builder, body, func, ret_type, map_sym_tab, local_sym_tab, structs_sym_tab ): - max_temps_needed = 0 + max_temps_needed = {} + + def merge_type_counts(count_dict): + nonlocal max_temps_needed + for typ, cnt in count_dict.items(): + max_temps_needed[typ] = max(max_temps_needed.get(typ, 0), cnt) def update_max_temps_for_stmt(stmt): nonlocal max_temps_needed - temps_needed = 0 if isinstance(stmt, ast.If): for s in stmt.body: @@ -111,10 +115,13 @@ def allocate_mem( update_max_temps_for_stmt(s) return + stmt_temps = {} for node in ast.walk(stmt): if isinstance(node, ast.Call): - temps_needed += count_temps_in_call(node, local_sym_tab) - max_temps_needed = max(max_temps_needed, temps_needed) + call_temps = count_temps_in_call(node, local_sym_tab) + for typ, cnt in call_temps.items(): + stmt_temps[typ] = stmt_temps.get(typ, 0) + cnt + merge_type_counts(stmt_temps) for stmt in body: update_max_temps_for_stmt(stmt) From 963e2a81718ca65571e6077763118c9ceab6dae2 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 4 Nov 2025 14:16:44 +0530 Subject: [PATCH 26/68] Change ScratchPoolManager to use typed scratch space --- pythonbpf/functions/functions_pass.py | 2 +- pythonbpf/helper/helper_registry.py | 2 +- pythonbpf/helper/helper_utils.py | 31 +++++++++++++++++++++------ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/pythonbpf/functions/functions_pass.py b/pythonbpf/functions/functions_pass.py index c652d3e..33342b7 100644 --- a/pythonbpf/functions/functions_pass.py +++ b/pythonbpf/functions/functions_pass.py @@ -50,7 +50,7 @@ def count_temps_in_call(call_node, local_sym_tab): func_name = call_node.func.attr if not is_helper: - return 0 + return {} # No temps needed for arg_idx in range(len(call_node.args)): # NOTE: Count all non-name arguments diff --git a/pythonbpf/helper/helper_registry.py b/pythonbpf/helper/helper_registry.py index dccb8c2..0e09d70 100644 --- a/pythonbpf/helper/helper_registry.py +++ b/pythonbpf/helper/helper_registry.py @@ -50,7 +50,7 @@ class HelperHandlerRegistry: def get_param_type(cls, helper_name, index): """Get the type of a parameter of a helper function by the index""" signature = cls.get_signature(helper_name) - if signature and 0 <= index < len(signature.arg_types): + if signature and signature.arg_types and 0 <= index < len(signature.arg_types): return signature.arg_types[index] return None diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index 841698c..4ec901d 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -14,26 +14,43 @@ class ScratchPoolManager: """Manage the temporary helper variables in local_sym_tab""" def __init__(self): - self._counter = 0 + self._counters = {} @property def counter(self): - return self._counter + return sum(self._counter.values()) def reset(self): - self._counter = 0 + self._counters.clear() logger.debug("Scratch pool counter reset to 0") - def get_next_temp(self, local_sym_tab): - temp_name = f"__helper_temp_{self._counter}" - self._counter += 1 + def _get_type_name(self, ir_type): + if isinstance(ir_type, ir.PointerType): + return "ptr" + elif isinstance(ir_type, ir.IntType): + return f"i{ir_type.width}" + elif isinstance(ir_type, ir.ArrayType): + return f"[{ir_type.count}x{self._get_type_name(ir_type.element)}]" + else: + return str(ir_type).replace(" ", "") + + def get_next_temp(self, local_sym_tab, expected_type=None): + # Default to i64 if no expected type provided + type_name = self._get_type_name(expected_type) if expected_type else "i64" + if type_name not in self._counters: + self._counters[type_name] = 0 + + counter = self._counters[type_name] + temp_name = f"__helper_temp_{type_name}_{counter}" + self._counters[type_name] += 1 if temp_name not in local_sym_tab: raise ValueError( f"Scratch pool exhausted or inadequate: {temp_name}. " - f"Current counter: {self._counter}" + f"Type: {type_name} Counter: {counter}" ) + logger.debug(f"Using {temp_name} for type {type_name}") return local_sym_tab[temp_name].var, temp_name From 7d29790f00c0aad33b6cc39e8f6dd84829025658 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 4 Nov 2025 16:02:56 +0530 Subject: [PATCH 27/68] Make use of new get_next_temp in helpers --- pythonbpf/helper/helper_utils.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index 4ec901d..71665c8 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -77,24 +77,35 @@ def get_var_ptr_from_name(var_name, local_sym_tab): def create_int_constant_ptr(value, builder, local_sym_tab, int_width=64): """Create a pointer to an integer constant.""" - # Default to 64-bit integer - ptr, temp_name = _temp_pool_manager.get_next_temp(local_sym_tab) + int_type = ir.IntType(int_width) + ptr, temp_name = _temp_pool_manager.get_next_temp(local_sym_tab, int_type) logger.info(f"Using temp variable '{temp_name}' for int constant {value}") - const_val = ir.Constant(ir.IntType(int_width), value) + const_val = ir.Constant(int_type, value) builder.store(const_val, ptr) return ptr def get_or_create_ptr_from_arg( - func, module, arg, builder, local_sym_tab, map_sym_tab, struct_sym_tab=None + func, + module, + arg, + builder, + local_sym_tab, + map_sym_tab, + expected_type=None, + struct_sym_tab=None, ): """Extract or create pointer from the call arguments.""" if isinstance(arg, ast.Name): + # Stack space is already allocated ptr = get_var_ptr_from_name(arg.id, local_sym_tab) elif isinstance(arg, ast.Constant) and isinstance(arg.value, int): - ptr = create_int_constant_ptr(arg.value, builder, local_sym_tab) + if expected_type and isinstance(expected_type, ir.IntType): + int_width = expected_type.width + ptr = create_int_constant_ptr(arg.value, builder, local_sym_tab, int_width) else: + # NOTE: For any integer expression reaching this branch, it is probably a struct field or a binop # Evaluate the expression and store the result in a temp variable val = get_operand_value( func, module, arg, builder, local_sym_tab, map_sym_tab, struct_sym_tab @@ -102,11 +113,14 @@ def get_or_create_ptr_from_arg( if val is None: raise ValueError("Failed to evaluate expression for helper arg.") - # NOTE: We assume the result is an int64 for now - # if isinstance(arg, ast.Attribute): - # return val - ptr, temp_name = _temp_pool_manager.get_next_temp(local_sym_tab) + ptr, temp_name = _temp_pool_manager.get_next_temp(local_sym_tab, expected_type) logger.info(f"Using temp variable '{temp_name}' for expression result") + if ( + isinstance(val.type, ir.IntType) + and expected_type + and val.type.width > expected_type.width + ): + val = builder.trunc(val, expected_type) builder.store(val, ptr) return ptr From 3078d4224deb0d03b71079fa096e607e416c97f5 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 4 Nov 2025 16:09:11 +0530 Subject: [PATCH 28/68] Add typed scratch space support to the bpf_skb_store_bytes helper --- pythonbpf/helper/bpf_helper_handler.py | 33 +++++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 8421c45..21b73d5 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -567,7 +567,11 @@ def bpf_get_current_uid_gid_emitter( return pid, ir.IntType(64) -@HelperHandlerRegistry.register("skb_store_bytes") +@HelperHandlerRegistry.register( + "skb_store_bytes", + param_types=[ir.IntType(32), ir.PointerType(), ir.IntType(32), ir.IntType(64)], + return_type=ir.IntType(64), +) def bpf_skb_store_bytes_emitter( call, map_ptr, @@ -583,6 +587,14 @@ def bpf_skb_store_bytes_emitter( Expected call signature: skb_store_bytes(skb, offset, from, len, flags) """ + args_signature = [ + ir.PointerType(), # skb pointer + ir.IntType(32), # offset + ir.PointerType(), # from + ir.IntType(32), # len + ir.IntType(64), # flags + ] + if len(call.args) not in (3, 4): raise ValueError( f"skb_store_bytes expects 3 or 4 args (offset, from, len, flags), got {len(call.args)}" @@ -596,10 +608,18 @@ def bpf_skb_store_bytes_emitter( builder, local_sym_tab, map_sym_tab, + args_signature[1], struct_sym_tab, ) from_ptr = get_or_create_ptr_from_arg( - func, module, call.args[1], builder, local_sym_tab, map_sym_tab, struct_sym_tab + func, + module, + call.args[1], + builder, + local_sym_tab, + map_sym_tab, + args_signature[2], + struct_sym_tab, ) len_val = get_int_value_from_arg( call.args[2], @@ -608,6 +628,7 @@ def bpf_skb_store_bytes_emitter( builder, local_sym_tab, map_sym_tab, + args_signature[3], struct_sym_tab, ) if len(call.args) == 4: @@ -617,13 +638,7 @@ def bpf_skb_store_bytes_emitter( flags = ir.Constant(ir.IntType(64), flags_val) fn_type = ir.FunctionType( ir.IntType(64), - [ - ir.PointerType(), # skb - ir.IntType(32), # offset - ir.PointerType(), # from - ir.IntType(32), # len - ir.IntType(64), # flags - ], + args_signature, var_arg=False, ) fn_ptr = builder.inttoptr( From 338d4994d8bc9d077ae5cf73f79dae423ef94a10 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 5 Nov 2025 17:36:37 +0530 Subject: [PATCH 29/68] Fix count_temps_in_call to only look for Pointer args of a helper_sig --- pythonbpf/functions/functions_pass.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pythonbpf/functions/functions_pass.py b/pythonbpf/functions/functions_pass.py index 33342b7..c994368 100644 --- a/pythonbpf/functions/functions_pass.py +++ b/pythonbpf/functions/functions_pass.py @@ -62,7 +62,9 @@ def count_temps_in_call(call_node, local_sym_tab): ): continue param_type = HelperHandlerRegistry.get_param_type(func_name, arg_idx) - count[param_type] = count.get(param_type, 0) + 1 + if isinstance(param_type, ir.PointerType): + pointee_type = param_type.pointee + count[pointee_type] = count.get(pointee_type, 0) + 1 return count From 2685d0a0eed535abe3ff01bc7ca926813d17d7d7 Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Wed, 5 Nov 2025 17:38:38 +0530 Subject: [PATCH 30/68] add i32 support special case and find ctx repetition in multiple functions error. --- pythonbpf/allocation_pass.py | 13 +++++++++-- pythonbpf/assign_pass.py | 21 +++++++++++++----- .../vmlinux_parser/vmlinux_exports_handler.py | 22 +++++++++++++++---- tests/c-form/Makefile | 12 ++++++---- tests/c-form/i32test.bpf.c | 4 ++-- tests/failing_tests/vmlinux/i32_test.py | 10 +++++++-- 6 files changed, 63 insertions(+), 19 deletions(-) diff --git a/pythonbpf/allocation_pass.py b/pythonbpf/allocation_pass.py index 4955f3f..8c66a9d 100644 --- a/pythonbpf/allocation_pass.py +++ b/pythonbpf/allocation_pass.py @@ -81,7 +81,7 @@ def _allocate_for_call(builder, var_name, rval, local_sym_tab, structs_sym_tab): call_type = rval.func.id # C type constructors - if call_type in ("c_int32", "c_int64", "c_uint32", "c_uint64"): + if call_type in ("c_int32", "c_int64", "c_uint32", "c_uint64", "c_void_p"): ir_type = ctypes_to_ir(call_type) var = builder.alloca(ir_type, name=var_name) var.align = ir_type.width // 8 @@ -259,7 +259,16 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_ field_size_bits = field_size_bytes * 8 if field_size_bits in [8, 16, 32, 64]: - actual_ir_type = ir.IntType(field_size_bits) + # Special case: struct_xdp_md i32 fields should allocate as i64 + # because load_ctx_field will zero-extend them to i64 + if vmlinux_struct_name == "struct_xdp_md" and field_size_bits == 32: + actual_ir_type = ir.IntType(64) + logger.info( + f"Allocating {var_name} as i64 for i32 field from struct_xdp_md.{field_name} " + "(will be zero-extended during load)" + ) + else: + actual_ir_type = ir.IntType(field_size_bits) else: logger.warning( f"Unusual field size {field_size_bits} bits for {field_name}" diff --git a/pythonbpf/assign_pass.py b/pythonbpf/assign_pass.py index a1c2798..34f45a7 100644 --- a/pythonbpf/assign_pass.py +++ b/pythonbpf/assign_pass.py @@ -152,15 +152,26 @@ def handle_variable_assignment( if val_type != var_type: if isinstance(val_type, Field): logger.info("Handling assignment to struct field") + # Special handling for struct_xdp_md i32 fields that are zero-extended to i64 + # The load_ctx_field already extended them, so val is i64 but val_type.type shows c_uint + if (hasattr(val_type, 'type') and + val_type.type.__name__ == "c_uint" and + isinstance(var_type, ir.IntType) and + var_type.width == 64): + # This is the struct_xdp_md case - value is already i64 + builder.store(val, var_ptr) + logger.info(f"Assigned zero-extended struct_xdp_md i32 field to {var_name} (i64)") + return True # TODO: handling only ctype struct fields for now. Handle other stuff too later. - if var_type == ctypes_to_ir(val_type.type.__name__): + elif var_type == ctypes_to_ir(val_type.type.__name__): builder.store(val, var_ptr) logger.info(f"Assigned ctype struct field to {var_name}") return True - logger.error( - f"Failed to assign ctype struct field to {var_name}: {val_type} != {var_type}" - ) - return False + else: + logger.error( + f"Failed to assign ctype struct field to {var_name}: {val_type} != {var_type}" + ) + return False elif isinstance(val_type, ir.IntType) and isinstance(var_type, ir.IntType): # Allow implicit int widening if val_type.width < var_type.width: diff --git a/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py b/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py index 6ec5988..14fae81 100644 --- a/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py +++ b/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py @@ -94,12 +94,13 @@ class VmlinuxHandler: f"Attempting to access field {field_name} of possible vmlinux struct {struct_var_name}" ) python_type: type = var_info.metadata + struct_name = python_type.__name__ globvar_ir, field_data = self.get_field_type( - python_type.__name__, field_name + struct_name, field_name ) builder.function.args[0].type = ir.PointerType(ir.IntType(8)) field_ptr = self.load_ctx_field( - builder, builder.function.args[0], globvar_ir, field_data + builder, builder.function.args[0], globvar_ir, field_data, struct_name ) # Return pointer to field and field type return field_ptr, field_data @@ -107,7 +108,7 @@ class VmlinuxHandler: raise RuntimeError("Variable accessed not found in symbol table") @staticmethod - def load_ctx_field(builder, ctx_arg, offset_global, field_data): + def load_ctx_field(builder, ctx_arg, offset_global, field_data, struct_name=None): """ Generate LLVM IR to load a field from BPF context using offset. @@ -116,8 +117,9 @@ class VmlinuxHandler: ctx_arg: The context pointer argument (ptr/i8*) offset_global: Global variable containing the field offset (i64) field_data: contains data about the field + struct_name: Name of the struct being accessed (optional) Returns: - The loaded value (i64 register) + The loaded value (i64 register or appropriately sized) """ # Load the offset value @@ -164,6 +166,7 @@ class VmlinuxHandler: # Determine the appropriate IR type based on field information int_width = 64 # Default to 64-bit + needs_zext = False # Track if we need zero-extension for xdp_md if field_data is not None: # Try to determine the size from field metadata @@ -175,6 +178,12 @@ class VmlinuxHandler: if field_size_bits in [8, 16, 32, 64]: int_width = field_size_bits logger.info(f"Determined field size: {int_width} bits") + + # Special handling for struct_xdp_md i32 fields + # Load as i32 but extend to i64 before storing + if struct_name == "struct_xdp_md" and int_width == 32: + needs_zext = True + logger.info(f"struct_xdp_md i32 field detected, will zero-extend to i64") else: logger.warning( f"Unusual field size {field_size_bits} bits, using default 64" @@ -203,6 +212,11 @@ class VmlinuxHandler: # Load and return the value value = builder.load(typed_ptr) + # Zero-extend i32 to i64 for struct_xdp_md fields + if needs_zext: + value = builder.zext(value, ir.IntType(64)) + logger.info("Zero-extended i32 value to i64 for struct_xdp_md field") + return value def has_field(self, struct_name, field_name): diff --git a/tests/c-form/Makefile b/tests/c-form/Makefile index 64ff900..03f8ef2 100644 --- a/tests/c-form/Makefile +++ b/tests/c-form/Makefile @@ -1,19 +1,23 @@ BPF_CLANG := clang -CFLAGS := -O0 -emit-llvm -target bpf -c +CFLAGS := -emit-llvm -target bpf -c SRC := $(wildcard *.bpf.c) LL := $(SRC:.bpf.c=.bpf.ll) +LL2 := $(SRC:.bpf.c=.bpf.o2.ll) OBJ := $(SRC:.bpf.c=.bpf.o) .PHONY: all clean -all: $(LL) $(OBJ) +all: $(LL) $(OBJ) $(LL2) %.bpf.o: %.bpf.c $(BPF_CLANG) -O2 -g -target bpf -c $< -o $@ %.bpf.ll: %.bpf.c - $(BPF_CLANG) $(CFLAGS) -g -S $< -o $@ + $(BPF_CLANG) -O0 $(CFLAGS) -g -S $< -o $@ + +%.bpf.o2.ll: %.bpf.c + $(BPF_CLANG) -O2 $(CFLAGS) -g -S $< -o $@ clean: - rm -f $(LL) $(OBJ) + rm -f $(LL) $(OBJ) $(LL2) diff --git a/tests/c-form/i32test.bpf.c b/tests/c-form/i32test.bpf.c index 71b479f..8457bab 100644 --- a/tests/c-form/i32test.bpf.c +++ b/tests/c-form/i32test.bpf.c @@ -5,9 +5,9 @@ SEC("xdp") int print_xdp_data(struct xdp_md *ctx) { // 'data' is a pointer to the start of packet data - void *data = (void *)(long)ctx->data; + long data = (long)ctx->data; - bpf_printk("ctx->data = %p\n", data); + bpf_printk("ctx->data = %lld\n", data); return XDP_PASS; } diff --git a/tests/failing_tests/vmlinux/i32_test.py b/tests/failing_tests/vmlinux/i32_test.py index 86ce2b7..cff9f0a 100644 --- a/tests/failing_tests/vmlinux/i32_test.py +++ b/tests/failing_tests/vmlinux/i32_test.py @@ -1,4 +1,4 @@ -from ctypes import c_int64, c_int32 +from ctypes import c_int64, c_int32, c_void_p from pythonbpf import bpf, section, bpfglobal, compile_to_ir, compile from vmlinux import struct_xdp_md from vmlinux import XDP_PASS @@ -8,10 +8,16 @@ from vmlinux import XDP_PASS @section("xdp") def print_xdp_data(ctx: struct_xdp_md) -> c_int64: data = ctx.data # 32-bit field: packet start pointer - something = c_int32(2 + data) + something = c_void_p(data) print(f"ctx->data = {something}") return c_int64(XDP_PASS) +@bpf +@section("xdp") +def print_xdp_dat2a(ct2x: struct_xdp_md) -> c_int64: + data = ct2x.data # 32-bit field: packet start pointer + print(f"ct2x->data = {data}") + return c_int64(XDP_PASS) @bpf @bpfglobal From 44c6ceda275c4c63112d8b23f7bb6e63c8016557 Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Wed, 5 Nov 2025 17:44:29 +0530 Subject: [PATCH 31/68] fix context debug info repetition circular reference error --- pythonbpf/functions/function_debug_info.py | 11 +++++++++++ tests/failing_tests/vmlinux/i32_test.py | 13 ++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/pythonbpf/functions/function_debug_info.py b/pythonbpf/functions/function_debug_info.py index e22e9ad..985eb92 100644 --- a/pythonbpf/functions/function_debug_info.py +++ b/pythonbpf/functions/function_debug_info.py @@ -49,16 +49,27 @@ def generate_function_debug_info( "The first argument should always be a pointer to a struct or a void pointer" ) context_debug_info = VmlinuxHandlerRegistry.get_struct_debug_info(annotation.id) + + # Create pointer to context this must be created fresh for each function + # to avoid circular reference issues when the same struct is used in multiple functions pointer_to_context_debug_info = generator.create_pointer_type( context_debug_info, 64 ) + + # Create subroutine type - also fresh for each function subroutine_type = generator.create_subroutine_type( return_type, pointer_to_context_debug_info ) + + # Create local variable - fresh for each function with unique name context_local_variable = generator.create_local_variable_debug_info( leading_argument_name, 1, pointer_to_context_debug_info ) + retained_nodes = [context_local_variable] + logger.info(f"Generating debug info for function {func_node.name}") + + # Create subprogram with is_distinct=True to ensure each function gets unique debug info subprogram_debug_info = generator.create_subprogram( func_node.name, subroutine_type, retained_nodes ) diff --git a/tests/failing_tests/vmlinux/i32_test.py b/tests/failing_tests/vmlinux/i32_test.py index cff9f0a..737d9c5 100644 --- a/tests/failing_tests/vmlinux/i32_test.py +++ b/tests/failing_tests/vmlinux/i32_test.py @@ -3,6 +3,12 @@ from pythonbpf import bpf, section, bpfglobal, compile_to_ir, compile from vmlinux import struct_xdp_md from vmlinux import XDP_PASS +@bpf +@section("xdp") +def print_xdp_dat2a(ct2x: struct_xdp_md) -> c_int64: + data = ct2x.data # 32-bit field: packet start pointer + print(f"ct2x->data = {data}") + return c_int64(XDP_PASS) @bpf @section("xdp") @@ -12,13 +18,6 @@ def print_xdp_data(ctx: struct_xdp_md) -> c_int64: print(f"ctx->data = {something}") return c_int64(XDP_PASS) -@bpf -@section("xdp") -def print_xdp_dat2a(ct2x: struct_xdp_md) -> c_int64: - data = ct2x.data # 32-bit field: packet start pointer - print(f"ct2x->data = {data}") - return c_int64(XDP_PASS) - @bpf @bpfglobal def LICENSE() -> str: From b0d35693b93dff24775df5214da5428f3d279aa2 Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Wed, 5 Nov 2025 17:44:45 +0530 Subject: [PATCH 32/68] format chore --- pythonbpf/allocation_pass.py | 5 ++++- pythonbpf/assign_pass.py | 14 +++++++++----- .../vmlinux_parser/vmlinux_exports_handler.py | 8 ++++---- tests/failing_tests/vmlinux/i32_test.py | 5 ++++- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/pythonbpf/allocation_pass.py b/pythonbpf/allocation_pass.py index 8c66a9d..3b25b6f 100644 --- a/pythonbpf/allocation_pass.py +++ b/pythonbpf/allocation_pass.py @@ -261,7 +261,10 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_ if field_size_bits in [8, 16, 32, 64]: # Special case: struct_xdp_md i32 fields should allocate as i64 # because load_ctx_field will zero-extend them to i64 - if vmlinux_struct_name == "struct_xdp_md" and field_size_bits == 32: + if ( + vmlinux_struct_name == "struct_xdp_md" + and field_size_bits == 32 + ): actual_ir_type = ir.IntType(64) logger.info( f"Allocating {var_name} as i64 for i32 field from struct_xdp_md.{field_name} " diff --git a/pythonbpf/assign_pass.py b/pythonbpf/assign_pass.py index 34f45a7..0bd48c6 100644 --- a/pythonbpf/assign_pass.py +++ b/pythonbpf/assign_pass.py @@ -154,13 +154,17 @@ def handle_variable_assignment( logger.info("Handling assignment to struct field") # Special handling for struct_xdp_md i32 fields that are zero-extended to i64 # The load_ctx_field already extended them, so val is i64 but val_type.type shows c_uint - if (hasattr(val_type, 'type') and - val_type.type.__name__ == "c_uint" and - isinstance(var_type, ir.IntType) and - var_type.width == 64): + if ( + hasattr(val_type, "type") + and val_type.type.__name__ == "c_uint" + and isinstance(var_type, ir.IntType) + and var_type.width == 64 + ): # This is the struct_xdp_md case - value is already i64 builder.store(val, var_ptr) - logger.info(f"Assigned zero-extended struct_xdp_md i32 field to {var_name} (i64)") + logger.info( + f"Assigned zero-extended struct_xdp_md i32 field to {var_name} (i64)" + ) return True # TODO: handling only ctype struct fields for now. Handle other stuff too later. elif var_type == ctypes_to_ir(val_type.type.__name__): diff --git a/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py b/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py index 14fae81..30f3058 100644 --- a/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py +++ b/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py @@ -95,9 +95,7 @@ class VmlinuxHandler: ) python_type: type = var_info.metadata struct_name = python_type.__name__ - globvar_ir, field_data = self.get_field_type( - struct_name, field_name - ) + globvar_ir, field_data = self.get_field_type(struct_name, field_name) builder.function.args[0].type = ir.PointerType(ir.IntType(8)) field_ptr = self.load_ctx_field( builder, builder.function.args[0], globvar_ir, field_data, struct_name @@ -183,7 +181,9 @@ class VmlinuxHandler: # Load as i32 but extend to i64 before storing if struct_name == "struct_xdp_md" and int_width == 32: needs_zext = True - logger.info(f"struct_xdp_md i32 field detected, will zero-extend to i64") + logger.info( + "struct_xdp_md i32 field detected, will zero-extend to i64" + ) else: logger.warning( f"Unusual field size {field_size_bits} bits, using default 64" diff --git a/tests/failing_tests/vmlinux/i32_test.py b/tests/failing_tests/vmlinux/i32_test.py index 737d9c5..4ba0969 100644 --- a/tests/failing_tests/vmlinux/i32_test.py +++ b/tests/failing_tests/vmlinux/i32_test.py @@ -1,8 +1,9 @@ -from ctypes import c_int64, c_int32, c_void_p +from ctypes import c_int64, c_void_p from pythonbpf import bpf, section, bpfglobal, compile_to_ir, compile from vmlinux import struct_xdp_md from vmlinux import XDP_PASS + @bpf @section("xdp") def print_xdp_dat2a(ct2x: struct_xdp_md) -> c_int64: @@ -10,6 +11,7 @@ def print_xdp_dat2a(ct2x: struct_xdp_md) -> c_int64: print(f"ct2x->data = {data}") return c_int64(XDP_PASS) + @bpf @section("xdp") def print_xdp_data(ctx: struct_xdp_md) -> c_int64: @@ -18,6 +20,7 @@ def print_xdp_data(ctx: struct_xdp_md) -> c_int64: print(f"ctx->data = {something}") return c_int64(XDP_PASS) + @bpf @bpfglobal def LICENSE() -> str: From c5fdd3bce2f484d40c74b3175c2843e1fb072c44 Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Wed, 5 Nov 2025 17:48:26 +0530 Subject: [PATCH 33/68] move some tests to passing --- tests/{failing_tests => passing_tests}/vmlinux/i32_test.py | 0 tests/{failing_tests => passing_tests}/vmlinux/i32_test_fail_1.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/{failing_tests => passing_tests}/vmlinux/i32_test.py (100%) rename tests/{failing_tests => passing_tests}/vmlinux/i32_test_fail_1.py (100%) diff --git a/tests/failing_tests/vmlinux/i32_test.py b/tests/passing_tests/vmlinux/i32_test.py similarity index 100% rename from tests/failing_tests/vmlinux/i32_test.py rename to tests/passing_tests/vmlinux/i32_test.py diff --git a/tests/failing_tests/vmlinux/i32_test_fail_1.py b/tests/passing_tests/vmlinux/i32_test_fail_1.py similarity index 100% rename from tests/failing_tests/vmlinux/i32_test_fail_1.py rename to tests/passing_tests/vmlinux/i32_test_fail_1.py From 2f4785b796afc511cb6a1dd3fbbaf985a9df024a Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Wed, 5 Nov 2025 18:01:41 +0530 Subject: [PATCH 34/68] add int type conversion for all vmlinux struct field int types. --- pythonbpf/expr/expr_pass.py | 44 ++++++++++++++++--- .../failing_tests/vmlinux/i32_test_fail_2.py | 3 +- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/pythonbpf/expr/expr_pass.py b/pythonbpf/expr/expr_pass.py index 1d10fcb..a9eab98 100644 --- a/pythonbpf/expr/expr_pass.py +++ b/pythonbpf/expr/expr_pass.py @@ -12,6 +12,7 @@ from .type_normalization import ( get_base_type_and_depth, deref_to_depth, ) +from pythonbpf.vmlinux_parser.assignment_info import Field from .vmlinux_registry import VmlinuxHandlerRegistry logger: Logger = logging.getLogger(__name__) @@ -279,16 +280,45 @@ def _handle_ctypes_call( call_type = expr.func.id expected_type = ctypes_to_ir(call_type) - if val[1] != expected_type: + # Extract the actual IR value and type + # val could be (value, ir_type) or (value, Field) + value, val_type = val + + # If val_type is a Field object (from vmlinux struct), get the actual IR type of the value + if isinstance(val_type, Field): + # The value is already the correct IR value (potentially zero-extended) + # Get the IR type from the value itself + actual_ir_type = value.type + logger.info( + f"Converting vmlinux field {val_type.name} (IR type: {actual_ir_type}) to {call_type}" + ) + else: + actual_ir_type = val_type + + if actual_ir_type != expected_type: # NOTE: We are only considering casting to and from int types for now - if isinstance(val[1], ir.IntType) and isinstance(expected_type, ir.IntType): - if val[1].width < expected_type.width: - val = (builder.sext(val[0], expected_type), expected_type) + if isinstance(actual_ir_type, ir.IntType) and isinstance( + expected_type, ir.IntType + ): + if actual_ir_type.width < expected_type.width: + value = builder.sext(value, expected_type) + logger.info( + f"Sign-extended from i{actual_ir_type.width} to i{expected_type.width}" + ) + elif actual_ir_type.width > expected_type.width: + value = builder.trunc(value, expected_type) + logger.info( + f"Truncated from i{actual_ir_type.width} to i{expected_type.width}" + ) else: - val = (builder.trunc(val[0], expected_type), expected_type) + # Same width, just use as-is (e.g., both i64) + pass else: - raise ValueError(f"Type mismatch: expected {expected_type}, got {val[1]}") - return val + raise ValueError( + f"Type mismatch: expected {expected_type}, got {actual_ir_type} (original type: {val_type})" + ) + + return value, expected_type def _handle_compare( diff --git a/tests/failing_tests/vmlinux/i32_test_fail_2.py b/tests/failing_tests/vmlinux/i32_test_fail_2.py index 7a628f3..4792fd6 100644 --- a/tests/failing_tests/vmlinux/i32_test_fail_2.py +++ b/tests/failing_tests/vmlinux/i32_test_fail_2.py @@ -1,5 +1,5 @@ from ctypes import c_int64 -from pythonbpf import bpf, section, bpfglobal, compile +from pythonbpf import bpf, section, bpfglobal, compile, compile_to_ir from vmlinux import struct_xdp_md from vmlinux import XDP_PASS import logging @@ -20,4 +20,5 @@ def LICENSE() -> str: return "GPL" +compile_to_ir("i32_test_fail_2.py", "i32_test_fail_2.ll") compile(logging.INFO) From 5ad33b011ed2a8810dc4547c5937ca2773376c4d Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Wed, 5 Nov 2025 18:02:28 +0530 Subject: [PATCH 35/68] move a test to passing --- tests/{failing_tests => passing_tests}/vmlinux/i32_test_fail_2.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{failing_tests => passing_tests}/vmlinux/i32_test_fail_2.py (100%) diff --git a/tests/failing_tests/vmlinux/i32_test_fail_2.py b/tests/passing_tests/vmlinux/i32_test_fail_2.py similarity index 100% rename from tests/failing_tests/vmlinux/i32_test_fail_2.py rename to tests/passing_tests/vmlinux/i32_test_fail_2.py From 3e6cea2b67c08f6bf6eeca9b20952daeaa5f55bb Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 5 Nov 2025 19:10:58 +0530 Subject: [PATCH 36/68] Move get_struct_char_array_ptr from helper/printk_formatter to helper/helper_utils, enable array to ptr conversion in skb_store_bytes --- pythonbpf/helper/helper_utils.py | 80 ++++++++++++++++++++++++++++ pythonbpf/helper/printk_formatter.py | 49 +---------------- 2 files changed, 82 insertions(+), 47 deletions(-) diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index 71665c8..125011b 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -85,6 +85,52 @@ def create_int_constant_ptr(value, builder, local_sym_tab, int_width=64): return ptr +def get_struct_char_array_ptr(expr, builder, local_sym_tab, struct_sym_tab): + """Get pointer to first element of char array in struct field, or None.""" + if not (isinstance(expr, ast.Attribute) and isinstance(expr.value, ast.Name)): + return None + + var_name = expr.value.id + field_name = expr.attr + + # Check if it's a valid struct field + if not ( + local_sym_tab + and var_name in local_sym_tab + and struct_sym_tab + and local_sym_tab[var_name].metadata in struct_sym_tab + ): + return None + + struct_type = local_sym_tab[var_name].metadata + struct_info = struct_sym_tab[struct_type] + + if field_name not in struct_info.fields: + return None + + field_type = struct_info.field_type(field_name) + + # Check if it's a char array + is_char_array = ( + isinstance(field_type, ir.ArrayType) + and isinstance(field_type.element, ir.IntType) + and field_type.element.width == 8 + ) + + if not is_char_array: + return None + + # Get field pointer and GEP to first element: [N x i8]* -> i8* + struct_ptr = local_sym_tab[var_name].var + field_ptr = struct_info.gep(builder, struct_ptr, field_name) + + return builder.gep( + field_ptr, + [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)], + inbounds=True, + ) + + def get_or_create_ptr_from_arg( func, module, @@ -97,6 +143,7 @@ def get_or_create_ptr_from_arg( ): """Extract or create pointer from the call arguments.""" + logger.info(f"Getting pointer from arg: {ast.dump(arg)}") if isinstance(arg, ast.Name): # Stack space is already allocated ptr = get_var_ptr_from_name(arg.id, local_sym_tab) @@ -104,6 +151,39 @@ def get_or_create_ptr_from_arg( if expected_type and isinstance(expected_type, ir.IntType): int_width = expected_type.width ptr = create_int_constant_ptr(arg.value, builder, local_sym_tab, int_width) + elif isinstance(arg, ast.Attribute): + # A struct field + struct_name = arg.value.id + field_name = arg.attr + + if not local_sym_tab or struct_name not in local_sym_tab: + raise ValueError(f"Struct '{struct_name}' not found") + + struct_type = local_sym_tab[struct_name].metadata + if not struct_sym_tab or struct_type not in struct_sym_tab: + raise ValueError(f"Struct type '{struct_type}' not found") + + struct_info = struct_sym_tab[struct_type] + if field_name not in struct_info.fields: + raise ValueError( + f"Field '{field_name}' not found in struct '{struct_name}'" + ) + + field_type = struct_info.field_type(field_name) + struct_ptr = local_sym_tab[struct_name].var + + # Special handling for char arrays + if ( + isinstance(field_type, ir.ArrayType) + and isinstance(field_type.element, ir.IntType) + and field_type.element.width == 8 + ): + ptr = get_struct_char_array_ptr(arg, builder, local_sym_tab, struct_sym_tab) + if not ptr: + raise ValueError("Failed to get char array pointer from struct field") + else: + ptr = struct_info.gep(builder, struct_ptr, field_name) + else: # NOTE: For any integer expression reaching this branch, it is probably a struct field or a binop # Evaluate the expression and store the result in a temp variable diff --git a/pythonbpf/helper/printk_formatter.py b/pythonbpf/helper/printk_formatter.py index a18f135..221be10 100644 --- a/pythonbpf/helper/printk_formatter.py +++ b/pythonbpf/helper/printk_formatter.py @@ -4,6 +4,7 @@ import logging from llvmlite import ir from pythonbpf.expr import eval_expr, get_base_type_and_depth, deref_to_depth from pythonbpf.expr.vmlinux_registry import VmlinuxHandlerRegistry +from pythonbpf.helper.helper_utils import get_struct_char_array_ptr logger = logging.getLogger(__name__) @@ -219,7 +220,7 @@ def _prepare_expr_args(expr, func, module, builder, local_sym_tab, struct_sym_ta """Evaluate and prepare an expression to use as an arg for bpf_printk.""" # Special case: struct field char array needs pointer to first element - char_array_ptr = _get_struct_char_array_ptr( + char_array_ptr = get_struct_char_array_ptr( expr, builder, local_sym_tab, struct_sym_tab ) if char_array_ptr: @@ -242,52 +243,6 @@ def _prepare_expr_args(expr, func, module, builder, local_sym_tab, struct_sym_ta return ir.Constant(ir.IntType(64), 0) -def _get_struct_char_array_ptr(expr, builder, local_sym_tab, struct_sym_tab): - """Get pointer to first element of char array in struct field, or None.""" - if not (isinstance(expr, ast.Attribute) and isinstance(expr.value, ast.Name)): - return None - - var_name = expr.value.id - field_name = expr.attr - - # Check if it's a valid struct field - if not ( - local_sym_tab - and var_name in local_sym_tab - and struct_sym_tab - and local_sym_tab[var_name].metadata in struct_sym_tab - ): - return None - - struct_type = local_sym_tab[var_name].metadata - struct_info = struct_sym_tab[struct_type] - - if field_name not in struct_info.fields: - return None - - field_type = struct_info.field_type(field_name) - - # Check if it's a char array - is_char_array = ( - isinstance(field_type, ir.ArrayType) - and isinstance(field_type.element, ir.IntType) - and field_type.element.width == 8 - ) - - if not is_char_array: - return None - - # Get field pointer and GEP to first element: [N x i8]* -> i8* - struct_ptr = local_sym_tab[var_name].var - field_ptr = struct_info.gep(builder, struct_ptr, field_name) - - return builder.gep( - field_ptr, - [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)], - inbounds=True, - ) - - def _handle_pointer_arg(val, func, builder): """Convert pointer type for bpf_printk.""" target, depth = get_base_type_and_depth(val.type) From 5b36726b7df39c550188b3b8ab8d1cd74bc610e9 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 5 Nov 2025 20:02:39 +0530 Subject: [PATCH 37/68] Make bpf_skb_store_bytes work --- pythonbpf/helper/bpf_helper_handler.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 21b73d5..4c75783 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -569,7 +569,12 @@ def bpf_get_current_uid_gid_emitter( @HelperHandlerRegistry.register( "skb_store_bytes", - param_types=[ir.IntType(32), ir.PointerType(), ir.IntType(32), ir.IntType(64)], + param_types=[ + ir.IntType(32), + ir.PointerType(ir.IntType(8)), + ir.IntType(32), + ir.IntType(64), + ], return_type=ir.IntType(64), ) def bpf_skb_store_bytes_emitter( @@ -608,7 +613,6 @@ def bpf_skb_store_bytes_emitter( builder, local_sym_tab, map_sym_tab, - args_signature[1], struct_sym_tab, ) from_ptr = get_or_create_ptr_from_arg( @@ -628,7 +632,6 @@ def bpf_skb_store_bytes_emitter( builder, local_sym_tab, map_sym_tab, - args_signature[3], struct_sym_tab, ) if len(call.args) == 4: From 2e37726922cd5171d62e638a59f9b77ddebe137b Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Thu, 6 Nov 2025 19:47:57 +0530 Subject: [PATCH 38/68] Add signature relection for all helper handlers except print --- pythonbpf/helper/bpf_helper_handler.py | 86 ++++++++++++++++++++++---- pythonbpf/helper/helper_utils.py | 2 +- 2 files changed, 74 insertions(+), 14 deletions(-) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 4c75783..5ac8764 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -37,7 +37,11 @@ class BPFHelperID(Enum): BPF_PROBE_READ_KERNEL_STR = 115 -@HelperHandlerRegistry.register("ktime") +@HelperHandlerRegistry.register( + "ktime", + param_types=[], + return_type=ir.IntType(64), +) def bpf_ktime_get_ns_emitter( call, map_ptr, @@ -60,7 +64,11 @@ def bpf_ktime_get_ns_emitter( return result, ir.IntType(64) -@HelperHandlerRegistry.register("lookup") +@HelperHandlerRegistry.register( + "lookup", + param_types=[ir.PointerType(ir.IntType(64))], + return_type=ir.PointerType(ir.IntType(64)), +) def bpf_map_lookup_elem_emitter( call, map_ptr, @@ -102,6 +110,7 @@ def bpf_map_lookup_elem_emitter( return result, ir.PointerType() +# NOTE: This has special handling so we won't reflect the signature here. @HelperHandlerRegistry.register("print") def bpf_printk_emitter( call, @@ -150,7 +159,15 @@ def bpf_printk_emitter( return True -@HelperHandlerRegistry.register("update") +@HelperHandlerRegistry.register( + "update", + param_types=[ + ir.PointerType(ir.IntType(64)), + ir.PointerType(ir.IntType(64)), + ir.IntType(64), + ], + return_type=ir.PointerType(ir.IntType(64)), +) def bpf_map_update_elem_emitter( call, map_ptr, @@ -205,7 +222,11 @@ def bpf_map_update_elem_emitter( return result, None -@HelperHandlerRegistry.register("delete") +@HelperHandlerRegistry.register( + "delete", + param_types=[ir.PointerType(ir.IntType(64))], + return_type=ir.PointerType(ir.IntType(64)), +) def bpf_map_delete_elem_emitter( call, map_ptr, @@ -245,7 +266,11 @@ def bpf_map_delete_elem_emitter( return result, None -@HelperHandlerRegistry.register("comm") +@HelperHandlerRegistry.register( + "comm", + param_types=[ir.PointerType(ir.IntType(8))], + return_type=ir.IntType(64), +) def bpf_get_current_comm_emitter( call, map_ptr, @@ -302,7 +327,11 @@ def bpf_get_current_comm_emitter( return result, None -@HelperHandlerRegistry.register("pid") +@HelperHandlerRegistry.register( + "pid", + param_types=[], + return_type=ir.IntType(64), +) def bpf_get_current_pid_tgid_emitter( call, map_ptr, @@ -330,7 +359,11 @@ def bpf_get_current_pid_tgid_emitter( return pid, ir.IntType(64) -@HelperHandlerRegistry.register("output") +@HelperHandlerRegistry.register( + "output", + param_types=[ir.PointerType(ir.IntType(8))], + return_type=ir.IntType(64), +) def bpf_perf_event_output_handler( call, map_ptr, @@ -405,7 +438,14 @@ def emit_probe_read_kernel_str_call(builder, dst_ptr, dst_size, src_ptr): return result -@HelperHandlerRegistry.register("probe_read_str") +@HelperHandlerRegistry.register( + "probe_read_str", + param_types=[ + ir.PointerType(ir.IntType(8)), + ir.PointerType(ir.IntType(8)), + ], + return_type=ir.IntType(64), +) def bpf_probe_read_kernel_str_emitter( call, map_ptr, @@ -440,7 +480,11 @@ def bpf_probe_read_kernel_str_emitter( return result, ir.IntType(64) -@HelperHandlerRegistry.register("random") +@HelperHandlerRegistry.register( + "random", + param_types=[], + return_type=ir.IntType(32), +) def bpf_get_prandom_u32_emitter( call, map_ptr, @@ -462,7 +506,15 @@ def bpf_get_prandom_u32_emitter( return result, ir.IntType(32) -@HelperHandlerRegistry.register("probe_read") +@HelperHandlerRegistry.register( + "probe_read", + param_types=[ + ir.PointerType(ir.IntType(8)), + ir.IntType(32), + ir.PointerType(ir.IntType(8)), + ], + return_type=ir.IntType(64), +) def bpf_probe_read_emitter( call, map_ptr, @@ -517,7 +569,11 @@ def bpf_probe_read_emitter( return result, ir.IntType(64) -@HelperHandlerRegistry.register("smp_processor_id") +@HelperHandlerRegistry.register( + "smp_processor_id", + param_types=[], + return_type=ir.IntType(32), +) def bpf_get_smp_processor_id_emitter( call, map_ptr, @@ -540,7 +596,11 @@ def bpf_get_smp_processor_id_emitter( return result, ir.IntType(32) -@HelperHandlerRegistry.register("uid") +@HelperHandlerRegistry.register( + "uid", + param_types=[], + return_type=ir.IntType(64), +) def bpf_get_current_uid_gid_emitter( call, map_ptr, @@ -622,8 +682,8 @@ def bpf_skb_store_bytes_emitter( builder, local_sym_tab, map_sym_tab, - args_signature[2], struct_sym_tab, + args_signature[2], ) len_val = get_int_value_from_arg( call.args[2], diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index 125011b..613e025 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -138,8 +138,8 @@ def get_or_create_ptr_from_arg( builder, local_sym_tab, map_sym_tab, - expected_type=None, struct_sym_tab=None, + expected_type=None, ): """Extract or create pointer from the call arguments.""" From 3ccd3f767e8b97d89cfc4579843d7a820b449eec Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Thu, 6 Nov 2025 19:59:04 +0530 Subject: [PATCH 39/68] Add expected types for pointer creation of args in probe_read handler --- pythonbpf/helper/bpf_helper_handler.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 5ac8764..cf08327 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -533,7 +533,14 @@ def bpf_probe_read_emitter( logger.warn("Expected 3 args for probe_read helper") return dst_ptr = get_or_create_ptr_from_arg( - func, module, call.args[0], builder, local_sym_tab, map_sym_tab, struct_sym_tab + func, + module, + call.args[0], + builder, + local_sym_tab, + map_sym_tab, + struct_sym_tab, + ir.IntType(8), ) size_val = get_int_value_from_arg( call.args[1], @@ -545,7 +552,14 @@ def bpf_probe_read_emitter( struct_sym_tab, ) src_ptr = get_or_create_ptr_from_arg( - func, module, call.args[2], builder, local_sym_tab, map_sym_tab, struct_sym_tab + func, + module, + call.args[2], + builder, + local_sym_tab, + map_sym_tab, + struct_sym_tab, + ir.IntType(8), ) fn_type = ir.FunctionType( ir.IntType(64), From 2f4a7d2f90cafc4b00d023ceea58af9f3fd1dd83 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 7 Nov 2025 18:54:59 +0530 Subject: [PATCH 40/68] Remove get_struct_char_array_ptr in favour of get_char_array_ptr_and_size, wrap it in get_or_crate_ptr_from_arg to use in bpf_helper_handler --- pythonbpf/helper/bpf_helper_handler.py | 3 +- pythonbpf/helper/helper_utils.py | 55 ++++---------------------- pythonbpf/helper/printk_formatter.py | 4 +- 3 files changed, 11 insertions(+), 51 deletions(-) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index cf08327..b3bf663 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -8,7 +8,6 @@ from .helper_utils import ( get_flags_val, get_data_ptr_and_size, get_buffer_ptr_and_size, - get_char_array_ptr_and_size, get_ptr_from_arg, get_int_value_from_arg, ) @@ -464,7 +463,7 @@ def bpf_probe_read_kernel_str_emitter( ) # Get destination buffer (char array -> i8*) - dst_ptr, dst_size = get_char_array_ptr_and_size( + dst_ptr, dst_size = get_or_create_ptr_from_arg( call.args[0], builder, local_sym_tab, struct_sym_tab ) diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index 613e025..4d7de22 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -85,52 +85,6 @@ def create_int_constant_ptr(value, builder, local_sym_tab, int_width=64): return ptr -def get_struct_char_array_ptr(expr, builder, local_sym_tab, struct_sym_tab): - """Get pointer to first element of char array in struct field, or None.""" - if not (isinstance(expr, ast.Attribute) and isinstance(expr.value, ast.Name)): - return None - - var_name = expr.value.id - field_name = expr.attr - - # Check if it's a valid struct field - if not ( - local_sym_tab - and var_name in local_sym_tab - and struct_sym_tab - and local_sym_tab[var_name].metadata in struct_sym_tab - ): - return None - - struct_type = local_sym_tab[var_name].metadata - struct_info = struct_sym_tab[struct_type] - - if field_name not in struct_info.fields: - return None - - field_type = struct_info.field_type(field_name) - - # Check if it's a char array - is_char_array = ( - isinstance(field_type, ir.ArrayType) - and isinstance(field_type.element, ir.IntType) - and field_type.element.width == 8 - ) - - if not is_char_array: - return None - - # Get field pointer and GEP to first element: [N x i8]* -> i8* - struct_ptr = local_sym_tab[var_name].var - field_ptr = struct_info.gep(builder, struct_ptr, field_name) - - return builder.gep( - field_ptr, - [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)], - inbounds=True, - ) - - def get_or_create_ptr_from_arg( func, module, @@ -148,6 +102,7 @@ def get_or_create_ptr_from_arg( # Stack space is already allocated ptr = get_var_ptr_from_name(arg.id, local_sym_tab) elif isinstance(arg, ast.Constant) and isinstance(arg.value, int): + int_width = 64 # Deafult to i64 if expected_type and isinstance(expected_type, ir.IntType): int_width = expected_type.width ptr = create_int_constant_ptr(arg.value, builder, local_sym_tab, int_width) @@ -178,7 +133,9 @@ def get_or_create_ptr_from_arg( and isinstance(field_type.element, ir.IntType) and field_type.element.width == 8 ): - ptr = get_struct_char_array_ptr(arg, builder, local_sym_tab, struct_sym_tab) + ptr, sz = get_char_array_ptr_and_size( + arg, builder, local_sym_tab, struct_sym_tab + ) if not ptr: raise ValueError("Failed to get char array pointer from struct field") else: @@ -203,6 +160,10 @@ def get_or_create_ptr_from_arg( val = builder.trunc(val, expected_type) builder.store(val, ptr) + # NOTE: For char arrays, also return size + if sz: + return ptr, sz + return ptr diff --git a/pythonbpf/helper/printk_formatter.py b/pythonbpf/helper/printk_formatter.py index 221be10..58990c0 100644 --- a/pythonbpf/helper/printk_formatter.py +++ b/pythonbpf/helper/printk_formatter.py @@ -4,7 +4,7 @@ import logging from llvmlite import ir from pythonbpf.expr import eval_expr, get_base_type_and_depth, deref_to_depth from pythonbpf.expr.vmlinux_registry import VmlinuxHandlerRegistry -from pythonbpf.helper.helper_utils import get_struct_char_array_ptr +from pythonbpf.helper.helper_utils import get_char_array_ptr_and_size logger = logging.getLogger(__name__) @@ -220,7 +220,7 @@ def _prepare_expr_args(expr, func, module, builder, local_sym_tab, struct_sym_ta """Evaluate and prepare an expression to use as an arg for bpf_printk.""" # Special case: struct field char array needs pointer to first element - char_array_ptr = get_struct_char_array_ptr( + char_array_ptr, _ = get_char_array_ptr_and_size( expr, builder, local_sym_tab, struct_sym_tab ) if char_array_ptr: From be629729740ea87d7fcb8e7cdaeaac2fe1583b53 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 7 Nov 2025 19:00:57 +0530 Subject: [PATCH 41/68] Fix ScratchPoolManager::counter --- pythonbpf/helper/helper_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index 4d7de22..d7019b8 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -18,7 +18,7 @@ class ScratchPoolManager: @property def counter(self): - return sum(self._counter.values()) + return sum(self._counters.values()) def reset(self): self._counters.clear() From b5a3494cc6f842542f88aa71a25af667ce9b6097 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 7 Nov 2025 19:01:40 +0530 Subject: [PATCH 42/68] Fix typo in get_or_create_ptr_from_arg --- pythonbpf/helper/helper_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index d7019b8..0a92149 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -102,7 +102,7 @@ def get_or_create_ptr_from_arg( # Stack space is already allocated ptr = get_var_ptr_from_name(arg.id, local_sym_tab) elif isinstance(arg, ast.Constant) and isinstance(arg.value, int): - int_width = 64 # Deafult to i64 + int_width = 64 # Default to i64 if expected_type and isinstance(expected_type, ir.IntType): int_width = expected_type.width ptr = create_int_constant_ptr(arg.value, builder, local_sym_tab, int_width) From 6c85b248ce751740eb2e9dcb4903f391cbf421af Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 7 Nov 2025 19:03:21 +0530 Subject: [PATCH 43/68] Init sz in get_or_create_ptr_from_arg --- pythonbpf/helper/helper_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index 0a92149..06d3cf1 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -98,6 +98,7 @@ def get_or_create_ptr_from_arg( """Extract or create pointer from the call arguments.""" logger.info(f"Getting pointer from arg: {ast.dump(arg)}") + sz = None if isinstance(arg, ast.Name): # Stack space is already allocated ptr = get_var_ptr_from_name(arg.id, local_sym_tab) From cf99b3bb9a859b4324d6dc583f0930380ab0f66d Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 7 Nov 2025 19:16:48 +0530 Subject: [PATCH 44/68] Fix call to get_or_create_ptr_from_arg for probe_read_str --- pythonbpf/helper/bpf_helper_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index b3bf663..acfedef 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -464,7 +464,7 @@ def bpf_probe_read_kernel_str_emitter( # Get destination buffer (char array -> i8*) dst_ptr, dst_size = get_or_create_ptr_from_arg( - call.args[0], builder, local_sym_tab, struct_sym_tab + func, module, call.args[0], builder, local_sym_tab, map_sym_tab, struct_sym_tab ) # Get source pointer (evaluate expression) From c883d95655a5f9b1a49ded3e35c7f45ca79c4a39 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 11 Nov 2025 17:43:20 +0530 Subject: [PATCH 45/68] Minor fix - check expr type before sending to char_array handler in printk_formatter --- pythonbpf/helper/printk_formatter.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pythonbpf/helper/printk_formatter.py b/pythonbpf/helper/printk_formatter.py index 58990c0..721213e 100644 --- a/pythonbpf/helper/printk_formatter.py +++ b/pythonbpf/helper/printk_formatter.py @@ -220,11 +220,12 @@ def _prepare_expr_args(expr, func, module, builder, local_sym_tab, struct_sym_ta """Evaluate and prepare an expression to use as an arg for bpf_printk.""" # Special case: struct field char array needs pointer to first element - char_array_ptr, _ = get_char_array_ptr_and_size( - expr, builder, local_sym_tab, struct_sym_tab - ) - if char_array_ptr: - return char_array_ptr + if isinstance(expr, ast.Attribute): + char_array_ptr, _ = get_char_array_ptr_and_size( + expr, builder, local_sym_tab, struct_sym_tab + ) + if char_array_ptr: + return char_array_ptr # Regular expression evaluation val, _ = eval_expr(func, module, builder, expr, local_sym_tab, None, struct_sym_tab) From 316c21c42865f4853c507da14083a4f29d073086 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 11 Nov 2025 21:00:42 +0530 Subject: [PATCH 46/68] Fix char_array to pointer/int detection fallback in helper_utils --- pythonbpf/helper/helper_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index 06d3cf1..d9cb7cd 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -287,7 +287,8 @@ def get_char_array_ptr_and_size(buf_arg, builder, local_sym_tab, struct_sym_tab) field_type = struct_info.field_type(field_name) if not _is_char_array(field_type): - raise ValueError("Expected char array field") + logger.info("Field is not a char array, falling back to int or ptr detection") + return None, 0 struct_ptr = local_sym_tab[var_name].var field_ptr = struct_info.gep(builder, struct_ptr, field_name) From df981be095b289ca36b66b57658636c7e71834dc Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 11 Nov 2025 21:08:06 +0530 Subject: [PATCH 47/68] Janitorial format --- BCC-Examples/sync_perf_output.py | 2 -- pythonbpf/helper/helper_utils.py | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BCC-Examples/sync_perf_output.py b/BCC-Examples/sync_perf_output.py index 4b91b21..f778fd5 100644 --- a/BCC-Examples/sync_perf_output.py +++ b/BCC-Examples/sync_perf_output.py @@ -68,8 +68,6 @@ def callback(cpu, event): perf = b["events"].open_perf_buffer(callback, struct_name="data_t") print("Starting to poll... (Ctrl+C to stop)") -print("Try running: fork() or clone() system calls to trigger events") - try: while True: b["events"].poll(1000) diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index d9cb7cd..aecb5e9 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -287,7 +287,9 @@ def get_char_array_ptr_and_size(buf_arg, builder, local_sym_tab, struct_sym_tab) field_type = struct_info.field_type(field_name) if not _is_char_array(field_type): - logger.info("Field is not a char array, falling back to int or ptr detection") + logger.info( + "Field is not a char array, falling back to int or ptr detection" + ) return None, 0 struct_ptr = local_sym_tab[var_name].var From cd52d0d91bcf16c2a365d086120a3ad30da59376 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 12 Nov 2025 01:07:12 +0530 Subject: [PATCH 48/68] Rename RingBuf map to RingBuffer --- pythonbpf/maps/__init__.py | 4 ++-- pythonbpf/maps/maps.py | 2 +- pythonbpf/maps/maps_pass.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pythonbpf/maps/__init__.py b/pythonbpf/maps/__init__.py index 48fc9ff..e0b2ada 100644 --- a/pythonbpf/maps/__init__.py +++ b/pythonbpf/maps/__init__.py @@ -1,4 +1,4 @@ -from .maps import HashMap, PerfEventArray, RingBuf +from .maps import HashMap, PerfEventArray, RingBuffer from .maps_pass import maps_proc -__all__ = ["HashMap", "PerfEventArray", "maps_proc", "RingBuf"] +__all__ = ["HashMap", "PerfEventArray", "maps_proc", "RingBuffer"] diff --git a/pythonbpf/maps/maps.py b/pythonbpf/maps/maps.py index a2d7c21..af07138 100644 --- a/pythonbpf/maps/maps.py +++ b/pythonbpf/maps/maps.py @@ -36,7 +36,7 @@ class PerfEventArray: pass # Placeholder for output method -class RingBuf: +class RingBuffer: def __init__(self, max_entries): self.max_entries = max_entries diff --git a/pythonbpf/maps/maps_pass.py b/pythonbpf/maps/maps_pass.py index 85837d7..f7e1b70 100644 --- a/pythonbpf/maps/maps_pass.py +++ b/pythonbpf/maps/maps_pass.py @@ -79,7 +79,7 @@ def _parse_map_params(rval, expected_args=None): return params -@MapProcessorRegistry.register("RingBuf") +@MapProcessorRegistry.register("RingBuffer") def process_ringbuf_map(map_name, rval, module): """Process a BPF_RINGBUF map declaration""" logger.info(f"Processing Ringbuf: {map_name}") From 5eaeb3e921706ced02e40bd706af9e6b3567e390 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 12 Nov 2025 01:14:25 +0530 Subject: [PATCH 49/68] Add max_entries constraints for RingBuffer --- pythonbpf/maps/maps_pass.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pythonbpf/maps/maps_pass.py b/pythonbpf/maps/maps_pass.py index f7e1b70..42d7a14 100644 --- a/pythonbpf/maps/maps_pass.py +++ b/pythonbpf/maps/maps_pass.py @@ -86,6 +86,17 @@ def process_ringbuf_map(map_name, rval, module): map_params = _parse_map_params(rval, expected_args=["max_entries"]) map_params["type"] = BPFMapType.RINGBUF + # NOTE: constraints borrowed from https://docs.ebpf.io/linux/map-type/BPF_MAP_TYPE_RINGBUF/ + max_entries = map_params.get("max_entries") + if ( + not isinstance(max_entries, int) + or max_entries < 4096 + or (max_entries & (max_entries - 1)) != 0 + ): + raise ValueError( + "Ringbuf max_entries must be a power of two greater than or equal to the page size (4096)" + ) + logger.info(f"Ringbuf map parameters: {map_params}") map_global = create_bpf_map(module, map_name, map_params) From 1d7a436c9f2ffed2c395be7a8c76f1586a9cd0c8 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 12 Nov 2025 01:30:15 +0530 Subject: [PATCH 50/68] Add linting function for RingBuf.discard --- pythonbpf/maps/maps.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pythonbpf/maps/maps.py b/pythonbpf/maps/maps.py index af07138..783f03f 100644 --- a/pythonbpf/maps/maps.py +++ b/pythonbpf/maps/maps.py @@ -48,4 +48,7 @@ class RingBuffer: def submit(self, data, flags=0): pass + def discard(self, data, flags=0): + pass + # add discard, output and also give names to flags and stuff From 7a56e5d0cdd52418f9a431bbc39854f26c7d02c1 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 12 Nov 2025 01:59:07 +0530 Subject: [PATCH 51/68] Initialize required helpers for ringbuffer --- pythonbpf/helper/bpf_helper_handler.py | 4 ++++ pythonbpf/maps/maps.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 17ff797..cd5d6b5 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -34,6 +34,10 @@ class BPFHelperID(Enum): BPF_GET_CURRENT_COMM = 16 BPF_PERF_EVENT_OUTPUT = 25 BPF_PROBE_READ_KERNEL_STR = 115 + BPF_RINGBUF_OUTPUT = 130 + BPF_RINGBUF_RESERVE = 131 + BPF_RINGBUF_SUBMIT = 132 + BPF_RINGBUF_DISCARD = 133 @HelperHandlerRegistry.register( diff --git a/pythonbpf/maps/maps.py b/pythonbpf/maps/maps.py index 783f03f..ac2d524 100644 --- a/pythonbpf/maps/maps.py +++ b/pythonbpf/maps/maps.py @@ -40,6 +40,9 @@ class RingBuffer: def __init__(self, max_entries): self.max_entries = max_entries + def output(self, data, flags=0): + pass + def reserve(self, size: int, flags=0): if size > self.max_entries: raise ValueError("size cannot be greater than set maximum entries") From 209df33c8f3319d457b28cb28a17952e50f0b102 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 12 Nov 2025 03:53:16 +0530 Subject: [PATCH 52/68] Add RingBuf submit and reserve helpers --- pythonbpf/helper/bpf_helper_handler.py | 110 +++++++++++++++++++++++++ pythonbpf/maps/maps.py | 2 +- 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index cd5d6b5..116d1a0 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -740,6 +740,116 @@ def bpf_skb_store_bytes_emitter( return result, ir.IntType(64) +@HelperHandlerRegistry.register( + "reserve", + param_types=[ir.IntType(64)], + return_type=ir.PointerType(ir.IntType(8)), +) +def bpf_ringbuf_reserve_emitter( + call, + map_ptr, + module, + builder, + func, + local_sym_tab=None, + struct_sym_tab=None, + map_sym_tab=None, +): + """ + Emit LLVM IR for bpf_ringbuf_reserve helper function call. + Expected call signature: ringbuf.reserve(size) + """ + + if len(call.args) != 1: + raise ValueError( + f"ringbuf.reserve expects exactly one argument (size), got {len(call.args)}" + ) + + size_val = get_int_value_from_arg( + call.args[0], + func, + module, + builder, + local_sym_tab, + map_sym_tab, + struct_sym_tab, + ) + + map_void_ptr = builder.bitcast(map_ptr, ir.PointerType()) + fn_type = ir.FunctionType( + ir.PointerType(ir.IntType(8)), + [ir.PointerType(), ir.IntType(64)], + var_arg=False, + ) + fn_ptr_type = ir.PointerType(fn_type) + + fn_addr = ir.Constant(ir.IntType(64), BPFHelperID.BPF_RINGBUF_RESERVE.value) + fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type) + + result = builder.call(fn_ptr, [map_void_ptr, size_val], tail=False) + + return result, ir.PointerType(ir.IntType(8)) + + +@HelperHandlerRegistry.register( + "submit", + param_types=[ir.PointerType(ir.IntType(8)), ir.IntType(64)], + return_type=ir.VoidType(), +) +def bpf_ringbuf_submit_emitter( + call, + map_ptr, + module, + builder, + func, + local_sym_tab=None, + struct_sym_tab=None, + map_sym_tab=None, +): + """ + Emit LLVM IR for bpf_ringbuf_submit helper function call. + Expected call signature: ringbuf.submit(data, flags=0) + """ + + if len(call.args) not in (1, 2): + raise ValueError( + f"ringbuf.submit expects 1 or 2 args (data, flags), got {len(call.args)}" + ) + + data_arg = call.args[0] + flags_arg = call.args[1] if len(call.args) == 2 else None + + data_ptr = get_or_create_ptr_from_arg( + func, + module, + data_arg, + builder, + local_sym_tab, + map_sym_tab, + struct_sym_tab, + ir.PointerType(ir.IntType(8)), + ) + + flags_const = get_flags_val(flags_arg, builder, local_sym_tab) + if not flags_arg: + flags_const = ir.Constant(ir.IntType(64), 0) + + map_void_ptr = builder.bitcast(map_ptr, ir.PointerType()) + fn_type = ir.FunctionType( + ir.VoidType(), + [ir.PointerType(), ir.PointerType(), ir.IntType(64)], + var_arg=False, + ) + fn_ptr_type = ir.PointerType(fn_type) + + fn_addr = ir.Constant(ir.IntType(64), BPFHelperID.BPF_RINGBUF_SUBMIT.value) + fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type) + + result = builder.call(fn_ptr, [map_void_ptr, data_ptr, flags_const], tail=False) + + return result, None + + def handle_helper_call( call, module, diff --git a/pythonbpf/maps/maps.py b/pythonbpf/maps/maps.py index ac2d524..583e957 100644 --- a/pythonbpf/maps/maps.py +++ b/pythonbpf/maps/maps.py @@ -43,7 +43,7 @@ class RingBuffer: def output(self, data, flags=0): pass - def reserve(self, size: int, flags=0): + def reserve(self, size: int): if size > self.max_entries: raise ValueError("size cannot be greater than set maximum entries") return 0 From cbddc0aa96fd215a59c75755d6cb291a55d8c3cd Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 12 Nov 2025 13:16:23 +0530 Subject: [PATCH 53/68] Introduce MapSymbol to propagate map type info in map_sym_tab --- pythonbpf/helper/bpf_helper_handler.py | 10 ++++++---- pythonbpf/maps/__init__.py | 3 ++- pythonbpf/maps/maps_pass.py | 10 +++++----- pythonbpf/maps/maps_utils.py | 11 +++++++++++ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 116d1a0..294985e 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -12,11 +12,9 @@ from .helper_utils import ( get_int_value_from_arg, ) from .printk_formatter import simple_string_print, handle_fstring_print - -from logging import Logger import logging -logger: Logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) class BPFHelperID(Enum): @@ -377,6 +375,10 @@ def bpf_perf_event_output_handler( struct_sym_tab=None, map_sym_tab=None, ): + """ + Emit LLVM IR for bpf_perf_event_output helper function call. + """ + if len(call.args) != 1: raise ValueError( f"Perf event output expects exactly one argument, got {len(call.args)}" @@ -904,6 +906,6 @@ def handle_helper_call( if not map_sym_tab or map_name not in map_sym_tab: raise ValueError(f"Map '{map_name}' not found in symbol table") - return invoke_helper(method_name, map_sym_tab[map_name]) + return invoke_helper(method_name, map_sym_tab[map_name].sym) return None diff --git a/pythonbpf/maps/__init__.py b/pythonbpf/maps/__init__.py index e0b2ada..eb2007d 100644 --- a/pythonbpf/maps/__init__.py +++ b/pythonbpf/maps/__init__.py @@ -1,4 +1,5 @@ from .maps import HashMap, PerfEventArray, RingBuffer from .maps_pass import maps_proc +from .map_types import BPFMapType -__all__ = ["HashMap", "PerfEventArray", "maps_proc", "RingBuffer"] +__all__ = ["HashMap", "PerfEventArray", "maps_proc", "RingBuffer", "BPFMapType"] diff --git a/pythonbpf/maps/maps_pass.py b/pythonbpf/maps/maps_pass.py index 42d7a14..87b9c03 100644 --- a/pythonbpf/maps/maps_pass.py +++ b/pythonbpf/maps/maps_pass.py @@ -3,7 +3,7 @@ import logging from logging import Logger from llvmlite import ir -from .maps_utils import MapProcessorRegistry +from .maps_utils import MapProcessorRegistry, MapSymbol from .map_types import BPFMapType from .map_debug_info import create_map_debug_info, create_ringbuf_debug_info from pythonbpf.expr.vmlinux_registry import VmlinuxHandlerRegistry @@ -46,7 +46,7 @@ def create_bpf_map(module, map_name, map_params): map_global.align = 8 logger.info(f"Created BPF map: {map_name} with params {map_params}") - return map_global + return MapSymbol(type=map_params["type"], sym=map_global) def _parse_map_params(rval, expected_args=None): @@ -100,7 +100,7 @@ def process_ringbuf_map(map_name, rval, module): 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) + create_ringbuf_debug_info(module, map_global.sym, map_name, map_params) return map_global @@ -114,7 +114,7 @@ def process_hash_map(map_name, rval, module): logger.info(f"Map parameters: {map_params}") map_global = create_bpf_map(module, map_name, map_params) # Generate debug info for BTF - create_map_debug_info(module, map_global, map_name, map_params) + create_map_debug_info(module, map_global.sym, map_name, map_params) return map_global @@ -128,7 +128,7 @@ def process_perf_event_map(map_name, rval, module): logger.info(f"Map parameters: {map_params}") map_global = create_bpf_map(module, map_name, map_params) # Generate debug info for BTF - create_map_debug_info(module, map_global, map_name, map_params) + create_map_debug_info(module, map_global.sym, map_name, map_params) return map_global diff --git a/pythonbpf/maps/maps_utils.py b/pythonbpf/maps/maps_utils.py index ee3ad08..eaa43b2 100644 --- a/pythonbpf/maps/maps_utils.py +++ b/pythonbpf/maps/maps_utils.py @@ -1,5 +1,16 @@ from collections.abc import Callable +from dataclasses import dataclass +from llvmlite import ir from typing import Any +from .map_types import BPFMapType + + +@dataclass +class MapSymbol: + """Class representing a symbol on the map""" + + type: BPFMapType + sym: ir.GlobalVariable class MapProcessorRegistry: From 64cd2d2fc268012870ff5eea65f261b5f34cdb83 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 12 Nov 2025 14:06:00 +0530 Subject: [PATCH 54/68] Set minimum supported Python version to 3.10 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3d0f11d..9ab259d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ ] readme = "README.md" license = {text = "Apache-2.0"} -requires-python = ">=3.8" +requires-python = ">=3.10" dependencies = [ "llvmlite", From a55efc6469770dbde60ac1249289278fd04a05d7 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 12 Nov 2025 14:06:09 +0530 Subject: [PATCH 55/68] Implement output helper for RingBuf maps, add a match-case based dispatch for output helper handlers for multiple map types --- pythonbpf/helper/bpf_helper_handler.py | 98 ++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 5 deletions(-) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 294985e..696ebe6 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -12,6 +12,7 @@ from .helper_utils import ( get_int_value_from_arg, ) from .printk_formatter import simple_string_print, handle_fstring_print +from pythonbpf.maps import BPFMapType import logging logger = logging.getLogger(__name__) @@ -360,11 +361,6 @@ def bpf_get_current_pid_tgid_emitter( return pid, ir.IntType(64) -@HelperHandlerRegistry.register( - "output", - param_types=[ir.PointerType(ir.IntType(8))], - return_type=ir.IntType(64), -) def bpf_perf_event_output_handler( call, map_ptr, @@ -416,6 +412,98 @@ def bpf_perf_event_output_handler( return result, None +def bpf_ringbuf_output_emitter( + call, + map_ptr, + module, + builder, + func, + local_sym_tab=None, + struct_sym_tab=None, + map_sym_tab=None, +): + """ + Emit LLVM IR for bpf_ringbuf_output helper function call. + """ + + if len(call.args) != 1: + raise ValueError( + f"Ringbuf output expects exactly one argument, got {len(call.args)}" + ) + data_arg = call.args[0] + data_ptr, size_val = get_data_ptr_and_size(data_arg, local_sym_tab, struct_sym_tab) + flags_val = ir.Constant(ir.IntType(64), 0) + + map_void_ptr = builder.bitcast(map_ptr, ir.PointerType()) + data_void_ptr = builder.bitcast(data_ptr, ir.PointerType()) + fn_type = ir.FunctionType( + ir.IntType(64), + [ + ir.PointerType(), + ir.PointerType(), + ir.IntType(64), + ir.IntType(64), + ], + var_arg=False, + ) + fn_ptr_type = ir.PointerType(fn_type) + + # helper id + fn_addr = ir.Constant(ir.IntType(64), BPFHelperID.BPF_RINGBUF_OUTPUT.value) + fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type) + + result = builder.call( + fn_ptr, [map_void_ptr, data_void_ptr, size_val, flags_val], tail=False + ) + return result, None + + +@HelperHandlerRegistry.register( + "output", + param_types=[ir.PointerType(ir.IntType(8))], + return_type=ir.IntType(64), +) +def handle_output_helper( + call, + map_ptr, + module, + builder, + func, + local_sym_tab=None, + struct_sym_tab=None, + map_sym_tab=None, +): + """ + Route output helper to the appropriate emitter based on map type. + """ + match map_sym_tab[map_ptr.name].type: + case BPFMapType.PERF_EVENT_ARRAY: + return bpf_perf_event_output_handler( + call, + map_ptr, + module, + builder, + func, + local_sym_tab, + struct_sym_tab, + map_sym_tab, + ) + case BPFMapType.RINGBUF: + return bpf_ringbuf_output_emitter( + call, + map_ptr, + module, + builder, + func, + local_sym_tab, + struct_sym_tab, + map_sym_tab, + ) + case _: + logger.error("Unsupported map type for output helper.") + raise NotImplementedError("Output helper for this map type is not implemented.") + + def emit_probe_read_kernel_str_call(builder, dst_ptr, dst_size, src_ptr): """Emit LLVM IR call to bpf_probe_read_kernel_str""" From 56df05a93cd827a2763ed6e3bafc12b8abb9429b Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 12 Nov 2025 14:38:35 +0530 Subject: [PATCH 56/68] Janitorial formatting --- pythonbpf/codegen.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pythonbpf/codegen.py b/pythonbpf/codegen.py index 60628e6..c9b75d3 100644 --- a/pythonbpf/codegen.py +++ b/pythonbpf/codegen.py @@ -218,13 +218,11 @@ def compile(loglevel=logging.WARNING) -> bool: def BPF(loglevel=logging.WARNING) -> BpfObject: caller_frame = inspect.stack()[1] src = inspect.getsource(caller_frame.frame) - with tempfile.NamedTemporaryFile( - mode="w+", delete=True, suffix=".py" - ) as f, tempfile.NamedTemporaryFile( - mode="w+", delete=True, suffix=".ll" - ) as inter, tempfile.NamedTemporaryFile( - mode="w+", delete=False, suffix=".o" - ) as obj_file: + with ( + tempfile.NamedTemporaryFile(mode="w+", delete=True, suffix=".py") as f, + tempfile.NamedTemporaryFile(mode="w+", delete=True, suffix=".ll") as inter, + tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".o") as obj_file, + ): f.write(src) f.flush() source = f.name From 29e90601b7b2a5b0fce2c82b4259b6955eb9ab8f Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Thu, 13 Nov 2025 00:51:48 +0530 Subject: [PATCH 57/68] Init bpf_get_stack emitter --- pythonbpf/helper/bpf_helper_handler.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 696ebe6..06c64f4 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -32,6 +32,7 @@ class BPFHelperID(Enum): BPF_GET_CURRENT_UID_GID = 15 BPF_GET_CURRENT_COMM = 16 BPF_PERF_EVENT_OUTPUT = 25 + BPF_GET_STACK = 67 BPF_PROBE_READ_KERNEL_STR = 115 BPF_RINGBUF_OUTPUT = 130 BPF_RINGBUF_RESERVE = 131 @@ -940,6 +941,25 @@ def bpf_ringbuf_submit_emitter( return result, None +@HelperHandlerRegistry.register( + "get_stack", + param_types=[ir.PointerType(ir.IntType(8)), ir.IntType(64)], + return_type=ir.IntType(64), +) +def bpf_get_stack_emitter( + call, + map_ptr, + module, + builder, + func, + local_sym_tab=None, + struct_sym_tab=None, + map_sym_tab=None, +): + # TODO: Implement bpf_get_stack emitter + pass + + def handle_helper_call( call, module, From a10da4a277944930861a09db6ee554ad526519a1 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Thu, 13 Nov 2025 00:59:50 +0530 Subject: [PATCH 58/68] Implement bpf_get_stack handler --- pythonbpf/helper/__init__.py | 2 ++ pythonbpf/helper/bpf_helper_handler.py | 33 ++++++++++++++++++++++++-- pythonbpf/helper/helpers.py | 5 ++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/pythonbpf/helper/__init__.py b/pythonbpf/helper/__init__.py index 4c1d283..6d38e79 100644 --- a/pythonbpf/helper/__init__.py +++ b/pythonbpf/helper/__init__.py @@ -12,6 +12,7 @@ from .helpers import ( smp_processor_id, uid, skb_store_bytes, + get_stack, XDP_DROP, XDP_PASS, ) @@ -83,6 +84,7 @@ __all__ = [ "smp_processor_id", "uid", "skb_store_bytes", + "get_stack", "XDP_DROP", "XDP_PASS", ] diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 06c64f4..96ef8d8 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -956,8 +956,37 @@ def bpf_get_stack_emitter( struct_sym_tab=None, map_sym_tab=None, ): - # TODO: Implement bpf_get_stack emitter - pass + if len(call.args) not in (1, 2): + raise ValueError( + f"get_stack expects atmost two arguments (buf, flags), got {len(call.args)}" + ) + ctx_ptr = func.args[0] # First argument to the function is ctx + buf_arg = call.args[0] + flags_arg = call.args[1] if len(call.args) == 2 else 0 + buf_ptr, buf_size = get_buffer_ptr_and_size( + buf_arg, builder, local_sym_tab, struct_sym_tab + ) + flags_val = get_flags_val(flags_arg, builder, local_sym_tab) + buf_void_ptr = builder.bitcast(buf_ptr, ir.PointerType()) + fn_type = ir.FunctionType( + ir.IntType(64), + [ + ir.PointerType(ir.IntType(8)), + ir.PointerType(), + ir.IntType(64), + ir.IntType(64), + ], + var_arg=False, + ) + fn_ptr_type = ir.PointerType(fn_type) + fn_addr = ir.Constant(ir.IntType(64), BPFHelperID.BPF_GET_STACK.value) + fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type) + result = builder.call( + fn_ptr, + [ctx_ptr, buf_void_ptr, ir.Constant(ir.IntType(64), buf_size), flags_val], + tail=False, + ) + return result, ir.IntType(64) def handle_helper_call( diff --git a/pythonbpf/helper/helpers.py b/pythonbpf/helper/helpers.py index 302b526..c80d57d 100644 --- a/pythonbpf/helper/helpers.py +++ b/pythonbpf/helper/helpers.py @@ -52,6 +52,11 @@ def skb_store_bytes(offset, from_buf, size, flags=0): return ctypes.c_int64(0) +def get_stack(buf, flags=0): + """get the current stack trace""" + return ctypes.c_int64(0) + + XDP_ABORTED = ctypes.c_int64(0) XDP_DROP = ctypes.c_int64(1) XDP_PASS = ctypes.c_int64(2) From 9d76502d5aabbce41bd1e3d73dc02564d3657ea4 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Thu, 13 Nov 2025 02:24:35 +0530 Subject: [PATCH 59/68] Fix get_flags_val usage --- pythonbpf/helper/bpf_helper_handler.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index 96ef8d8..d54fda3 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -806,7 +806,10 @@ def bpf_skb_store_bytes_emitter( flags_val = get_flags_val(call.args[3], builder, local_sym_tab) else: flags_val = 0 - flags = ir.Constant(ir.IntType(64), flags_val) + if isinstance(flags_val, int): + flags = ir.Constant(ir.IntType(64), flags_val) + else: + flags = flags_val fn_type = ir.FunctionType( ir.IntType(64), args_signature, @@ -922,8 +925,8 @@ def bpf_ringbuf_submit_emitter( ) flags_const = get_flags_val(flags_arg, builder, local_sym_tab) - if not flags_arg: - flags_const = ir.Constant(ir.IntType(64), 0) + if isinstance(flags_const, int): + flags_const = ir.Constant(ir.IntType(64), flags_const) map_void_ptr = builder.bitcast(map_ptr, ir.PointerType()) fn_type = ir.FunctionType( @@ -962,11 +965,14 @@ def bpf_get_stack_emitter( ) ctx_ptr = func.args[0] # First argument to the function is ctx buf_arg = call.args[0] - flags_arg = call.args[1] if len(call.args) == 2 else 0 + flags_arg = call.args[1] if len(call.args) == 2 else None buf_ptr, buf_size = get_buffer_ptr_and_size( buf_arg, builder, local_sym_tab, struct_sym_tab ) flags_val = get_flags_val(flags_arg, builder, local_sym_tab) + if isinstance(flags_val, int): + flags_val = ir.Constant(ir.IntType(64), flags_val) + buf_void_ptr = builder.bitcast(buf_ptr, ir.PointerType()) fn_type = ir.FunctionType( ir.IntType(64), From e9bb90cb706372346fa009c399d1de1043ed27ec Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 17 Nov 2025 01:46:57 +0530 Subject: [PATCH 60/68] Add docstring for bpf_get_stack_emitter --- pythonbpf/helper/bpf_helper_handler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pythonbpf/helper/bpf_helper_handler.py b/pythonbpf/helper/bpf_helper_handler.py index d54fda3..ba35cc4 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -959,6 +959,9 @@ def bpf_get_stack_emitter( struct_sym_tab=None, map_sym_tab=None, ): + """ + Emit LLVM IR for bpf_get_stack helper function call. + """ if len(call.args) not in (1, 2): raise ValueError( f"get_stack expects atmost two arguments (buf, flags), got {len(call.args)}" From 4f8af16a17ef63cfb4ca4299da2825f5b521f39f Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 18 Nov 2025 03:29:58 +0530 Subject: [PATCH 61/68] Pass structs_sym_tab to maps_proc --- pythonbpf/codegen.py | 2 +- pythonbpf/maps/maps_pass.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pythonbpf/codegen.py b/pythonbpf/codegen.py index c9b75d3..e22b7bd 100644 --- a/pythonbpf/codegen.py +++ b/pythonbpf/codegen.py @@ -86,7 +86,7 @@ def processor(source_code, filename, module): license_processing(tree, module) globals_processing(tree, module) structs_sym_tab = structs_proc(tree, module, bpf_chunks) - map_sym_tab = maps_proc(tree, module, bpf_chunks) + map_sym_tab = maps_proc(tree, module, bpf_chunks, structs_sym_tab) func_proc(tree, module, bpf_chunks, map_sym_tab, structs_sym_tab) globals_list_creation(tree, module) diff --git a/pythonbpf/maps/maps_pass.py b/pythonbpf/maps/maps_pass.py index 87b9c03..8592041 100644 --- a/pythonbpf/maps/maps_pass.py +++ b/pythonbpf/maps/maps_pass.py @@ -12,7 +12,7 @@ from pythonbpf.expr.vmlinux_registry import VmlinuxHandlerRegistry logger: Logger = logging.getLogger(__name__) -def maps_proc(tree, module, chunks): +def maps_proc(tree, module, chunks, structs_sym_tab): """Process all functions decorated with @map to find BPF maps""" map_sym_tab = {} for func_node in chunks: From 5667facf23c2054740ccbc62d98a0becc7ace11e Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 18 Nov 2025 04:33:44 +0530 Subject: [PATCH 62/68] Pass down structs_sym_tab to maps_debug_info, allow vmlinux enums to be used in an indexed format for map declaration --- pythonbpf/maps/map_debug_info.py | 10 ++++++++-- pythonbpf/maps/maps_pass.py | 29 +++++++++++++++++------------ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/pythonbpf/maps/map_debug_info.py b/pythonbpf/maps/map_debug_info.py index b1a25d9..4da8b36 100644 --- a/pythonbpf/maps/map_debug_info.py +++ b/pythonbpf/maps/map_debug_info.py @@ -2,7 +2,7 @@ from pythonbpf.debuginfo import DebugInfoGenerator from .map_types import BPFMapType -def create_map_debug_info(module, map_global, map_name, map_params): +def create_map_debug_info(module, map_global, map_name, map_params, structs_sym_tab): """Generate debug info metadata for BPF maps HASH and PERF_EVENT_ARRAY""" generator = DebugInfoGenerator(module) @@ -64,7 +64,13 @@ 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): +# TODO: This should not be exposed outside of the module. +# Ideally we should expose a single create_map_debug_info function that handles all map types. +# We can probably use a registry pattern to register different map types and their debug info generators. +# map_params["type"] will be used to determine which generator to use. +def create_ringbuf_debug_info( + module, map_global, map_name, map_params, structs_sym_tab +): """Generate debug information metadata for BPF RINGBUF map""" generator = DebugInfoGenerator(module) diff --git a/pythonbpf/maps/maps_pass.py b/pythonbpf/maps/maps_pass.py index 8592041..907be57 100644 --- a/pythonbpf/maps/maps_pass.py +++ b/pythonbpf/maps/maps_pass.py @@ -18,7 +18,9 @@ def maps_proc(tree, module, chunks, structs_sym_tab): for func_node in chunks: if is_map(func_node): logger.info(f"Found BPF map: {func_node.name}") - map_sym_tab[func_node.name] = process_bpf_map(func_node, module) + map_sym_tab[func_node.name] = process_bpf_map( + func_node, module, structs_sym_tab + ) return map_sym_tab @@ -60,7 +62,8 @@ def _parse_map_params(rval, expected_args=None): if i < len(rval.args): arg = rval.args[i] if isinstance(arg, ast.Name): - params[arg_name] = arg.id + result = _get_vmlinux_enum(handler, arg.id) + params[arg_name] = result if result is not None else arg.id elif isinstance(arg, ast.Constant): params[arg_name] = arg.value @@ -68,19 +71,21 @@ def _parse_map_params(rval, expected_args=None): for keyword in rval.keywords: if isinstance(keyword.value, ast.Name): name = keyword.value.id - if handler and handler.is_vmlinux_enum(name): - result = handler.get_vmlinux_enum_value(name) - params[keyword.arg] = result if result is not None else name - else: - params[keyword.arg] = name + result = _get_vmlinux_enum(handler, name) + params[keyword.arg] = result if result is not None else name elif isinstance(keyword.value, ast.Constant): params[keyword.arg] = keyword.value.value return params +def _get_vmlinux_enum(handler, name): + if handler and handler.is_vmlinux_enum(name): + return handler.get_vmlinux_enum_value(name) + + @MapProcessorRegistry.register("RingBuffer") -def process_ringbuf_map(map_name, rval, module): +def process_ringbuf_map(map_name, rval, module, structs_sym_tab): """Process a BPF_RINGBUF map declaration""" logger.info(f"Processing Ringbuf: {map_name}") map_params = _parse_map_params(rval, expected_args=["max_entries"]) @@ -105,7 +110,7 @@ def process_ringbuf_map(map_name, rval, module): @MapProcessorRegistry.register("HashMap") -def process_hash_map(map_name, rval, module): +def process_hash_map(map_name, rval, module, structs_sym_tab): """Process a BPF_HASH map declaration""" logger.info(f"Processing HashMap: {map_name}") map_params = _parse_map_params(rval, expected_args=["key", "value", "max_entries"]) @@ -119,7 +124,7 @@ def process_hash_map(map_name, rval, module): @MapProcessorRegistry.register("PerfEventArray") -def process_perf_event_map(map_name, rval, module): +def process_perf_event_map(map_name, rval, module, structs_sym_tab): """Process a BPF_PERF_EVENT_ARRAY map declaration""" logger.info(f"Processing PerfEventArray: {map_name}") map_params = _parse_map_params(rval, expected_args=["key_size", "value_size"]) @@ -132,7 +137,7 @@ def process_perf_event_map(map_name, rval, module): return map_global -def process_bpf_map(func_node, module): +def process_bpf_map(func_node, module, structs_sym_tab): """Process a BPF map (a function decorated with @map)""" map_name = func_node.name logger.info(f"Processing BPF map: {map_name}") @@ -151,7 +156,7 @@ def process_bpf_map(func_node, module): if isinstance(rval, ast.Call) and isinstance(rval.func, ast.Name): handler = MapProcessorRegistry.get_processor(rval.func.id) if handler: - return handler(map_name, rval, module) + return handler(map_name, rval, module, structs_sym_tab) else: logger.warning(f"Unknown map type {rval.func.id}, defaulting to HashMap") return process_hash_map(map_name, rval, module) From 64679f8072f0e5ed661b322ab81b9ffd16d70e69 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Tue, 18 Nov 2025 05:00:00 +0530 Subject: [PATCH 63/68] Add skeleton _get_key_val_dbg_type in maps_debug_info.py --- pythonbpf/maps/map_debug_info.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pythonbpf/maps/map_debug_info.py b/pythonbpf/maps/map_debug_info.py index 4da8b36..c6d46cc 100644 --- a/pythonbpf/maps/map_debug_info.py +++ b/pythonbpf/maps/map_debug_info.py @@ -1,6 +1,9 @@ +import logging from pythonbpf.debuginfo import DebugInfoGenerator from .map_types import BPFMapType +logger: logging.Logger = logging.getLogger(__name__) + def create_map_debug_info(module, map_global, map_name, map_params, structs_sym_tab): """Generate debug info metadata for BPF maps HASH and PERF_EVENT_ARRAY""" @@ -97,3 +100,24 @@ def create_ringbuf_debug_info( ) map_global.set_metadata("dbg", global_var) return global_var + + +def _get_key_val_dbg_type(name, generator, structs_sym_tab): + """Get the debug type for key/value based on type object""" + type_obj = structs_sym_tab.get(name) + if type_obj: + return _get_struct_debug_type(type_obj, generator, structs_sym_tab) + + # Fallback to basic types + logger.info(f"No struct named {name}, falling back to basic type") + + # NOTE: Only handling int and long for now + if name in ["c_int32", "c_uint32"]: + return generator.get_uint32_type() + + # Default fallback for now + return generator.get_uint64_type() + + +def _get_struct_debug_type(struct_obj, generator, structs_sym_tab): + pass From 573bbb350eb2641830c383c30f64bd80cb0ee50c Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 19 Nov 2025 00:08:15 +0530 Subject: [PATCH 64/68] Allow structs to be key/val type for hashmaps --- pythonbpf/maps/map_debug_info.py | 57 +++++++++++++++++++++++++++++--- pythonbpf/maps/maps_pass.py | 6 ++-- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/pythonbpf/maps/map_debug_info.py b/pythonbpf/maps/map_debug_info.py index c6d46cc..77a5888 100644 --- a/pythonbpf/maps/map_debug_info.py +++ b/pythonbpf/maps/map_debug_info.py @@ -1,4 +1,5 @@ import logging +from llvmlite import ir from pythonbpf.debuginfo import DebugInfoGenerator from .map_types import BPFMapType @@ -8,18 +9,23 @@ logger: logging.Logger = logging.getLogger(__name__) def create_map_debug_info(module, map_global, map_name, map_params, structs_sym_tab): """Generate debug info metadata for BPF maps HASH and PERF_EVENT_ARRAY""" generator = DebugInfoGenerator(module) - + logger.info(f"Creating debug info for map {map_name} with params {map_params}") uint_type = generator.get_uint32_type() - ulong_type = generator.get_uint64_type() array_type = generator.create_array_type( uint_type, map_params.get("type", BPFMapType.UNSPEC).value ) type_ptr = generator.create_pointer_type(array_type, 64) key_ptr = generator.create_pointer_type( - array_type if "key_size" in map_params else ulong_type, 64 + array_type + if "key_size" in map_params + else _get_key_val_dbg_type(map_params.get("key"), generator, structs_sym_tab), + 64, ) value_ptr = generator.create_pointer_type( - array_type if "value_size" in map_params else ulong_type, 64 + array_type + if "value_size" in map_params + else _get_key_val_dbg_type(map_params.get("value"), generator, structs_sym_tab), + 64, ) elements_arr = [] @@ -104,6 +110,11 @@ def create_ringbuf_debug_info( def _get_key_val_dbg_type(name, generator, structs_sym_tab): """Get the debug type for key/value based on type object""" + + if not name: + logger.warn("No name provided for key/value type, defaulting to uint64") + return generator.get_uint64_type() + type_obj = structs_sym_tab.get(name) if type_obj: return _get_struct_debug_type(type_obj, generator, structs_sym_tab) @@ -120,4 +131,40 @@ def _get_key_val_dbg_type(name, generator, structs_sym_tab): def _get_struct_debug_type(struct_obj, generator, structs_sym_tab): - pass + """Recursively create debug type for struct""" + elements_arr = [] + for fld in struct_obj.fields.keys(): + fld_type = struct_obj.field_type(fld) + if isinstance(fld_type, ir.IntType): + if fld_type.width == 32: + fld_dbg_type = generator.get_uint32_type() + else: + # NOTE: Assuming 64-bit for all other int types + fld_dbg_type = generator.get_uint64_type() + elif isinstance(fld_type, ir.ArrayType): + # NOTE: Array types have u8 elements only for now + # Debug info generation should fail for other types + elem_type = fld_type.element + if isinstance(elem_type, ir.IntType) and elem_type.width == 8: + char_type = generator.get_uint8_type() + fld_dbg_type = generator.create_array_type(char_type, fld_type.count) + else: + logger.warning( + f"Array element type {str(elem_type)} not supported for debug info, skipping" + ) + continue + else: + # NOTE: Only handling int and char arrays for now + logger.warning( + f"Field type {str(fld_type)} not supported for debug info, skipping" + ) + continue + + member = generator.create_struct_member( + fld, fld_dbg_type, struct_obj.field_size(fld) + ) + elements_arr.append(member) + struct_type = generator.create_struct_type( + elements_arr, struct_obj.size, is_distinct=True + ) + return struct_type diff --git a/pythonbpf/maps/maps_pass.py b/pythonbpf/maps/maps_pass.py index 907be57..ed60958 100644 --- a/pythonbpf/maps/maps_pass.py +++ b/pythonbpf/maps/maps_pass.py @@ -105,7 +105,9 @@ def process_ringbuf_map(map_name, rval, module, structs_sym_tab): 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.sym, map_name, map_params) + create_ringbuf_debug_info( + module, map_global.sym, map_name, map_params, structs_sym_tab + ) return map_global @@ -119,7 +121,7 @@ def process_hash_map(map_name, rval, module, structs_sym_tab): logger.info(f"Map parameters: {map_params}") map_global = create_bpf_map(module, map_name, map_params) # Generate debug info for BTF - create_map_debug_info(module, map_global.sym, map_name, map_params) + create_map_debug_info(module, map_global.sym, map_name, map_params, structs_sym_tab) return map_global From 7af54df7c05936fe3460b0b6ac473261c997ad28 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 19 Nov 2025 00:17:01 +0530 Subject: [PATCH 65/68] Add passing test hash_map_struct.py for using structs as hashmap key/val types --- tests/passing_tests/hash_map_struct.py | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/passing_tests/hash_map_struct.py diff --git a/tests/passing_tests/hash_map_struct.py b/tests/passing_tests/hash_map_struct.py new file mode 100644 index 0000000..9f6cbac --- /dev/null +++ b/tests/passing_tests/hash_map_struct.py @@ -0,0 +1,42 @@ +from pythonbpf import bpf, section, struct, bpfglobal, compile, map +from pythonbpf.maps import HashMap +from pythonbpf.helper import pid +from ctypes import c_void_p, c_int64 + + +@bpf +@struct +class val_type: + counter: c_int64 + shizzle: c_int64 + + +@bpf +@map +def last() -> HashMap: + return HashMap(key=val_type, value=c_int64, max_entries=16) + + +@bpf +@section("tracepoint/syscalls/sys_enter_clone") +def hello_world(ctx: c_void_p) -> c_int64: + obj = val_type() + obj.counter, obj.shizzle = 42, 96 + t = last.lookup(obj) + if t: + print(f"Found existing entry: counter={obj.counter}, pid={t}") + last.delete(obj) + return 0 # type: ignore [return-value] + val = pid() + last.update(obj, val) + print(f"Map updated!, {obj.counter}, {obj.shizzle}, {val}") + return 0 # type: ignore [return-value] + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() From 9f5ec62383f07b87ac7513d9a6fdea236f4a4401 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 19 Nov 2025 03:24:40 +0530 Subject: [PATCH 66/68] Add get_uint8_type to DebugInfoGenerator --- 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 cca467e..4b96d22 100644 --- a/pythonbpf/debuginfo/debug_info_generator.py +++ b/pythonbpf/debuginfo/debug_info_generator.py @@ -49,6 +49,10 @@ class DebugInfoGenerator: ) return self._type_cache[key] + def get_uint8_type(self) -> Any: + """Get debug info for signed 8-bit integer""" + return self.get_basic_type("char", 8, dc.DW_ATE_unsigned) + 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) From 19b42b9a19ad111c9a93f23574b4823dd354adb1 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 19 Nov 2025 04:09:51 +0530 Subject: [PATCH 67/68] Allocate hashmap lookup return vars based on the value type of said hashmap --- pythonbpf/allocation_pass.py | 80 +++++++++++++++++++++++++-- pythonbpf/functions/functions_pass.py | 4 +- pythonbpf/maps/maps_pass.py | 2 +- pythonbpf/maps/maps_utils.py | 1 + 4 files changed, 79 insertions(+), 8 deletions(-) diff --git a/pythonbpf/allocation_pass.py b/pythonbpf/allocation_pass.py index f16c36f..5e44eb4 100644 --- a/pythonbpf/allocation_pass.py +++ b/pythonbpf/allocation_pass.py @@ -7,6 +7,7 @@ from pythonbpf.helper import HelperHandlerRegistry from pythonbpf.vmlinux_parser.dependency_node import Field from .expr import VmlinuxHandlerRegistry from pythonbpf.type_deducer import ctypes_to_ir +from pythonbpf.maps import BPFMapType logger = logging.getLogger(__name__) @@ -25,7 +26,9 @@ def create_targets_and_rvals(stmt): return stmt.targets, [stmt.value] -def handle_assign_allocation(builder, stmt, local_sym_tab, structs_sym_tab): +def handle_assign_allocation( + builder, stmt, local_sym_tab, map_sym_tab, structs_sym_tab +): """Handle memory allocation for assignment statements.""" logger.info(f"Handling assignment for allocation: {ast.dump(stmt)}") @@ -55,7 +58,9 @@ def handle_assign_allocation(builder, stmt, local_sym_tab, structs_sym_tab): # Determine type and allocate based on rval if isinstance(rval, ast.Call): - _allocate_for_call(builder, var_name, rval, local_sym_tab, structs_sym_tab) + _allocate_for_call( + builder, var_name, rval, local_sym_tab, map_sym_tab, structs_sym_tab + ) elif isinstance(rval, ast.Constant): _allocate_for_constant(builder, var_name, rval, local_sym_tab) elif isinstance(rval, ast.BinOp): @@ -74,7 +79,9 @@ def handle_assign_allocation(builder, stmt, local_sym_tab, structs_sym_tab): ) -def _allocate_for_call(builder, var_name, rval, local_sym_tab, structs_sym_tab): +def _allocate_for_call( + builder, var_name, rval, local_sym_tab, map_sym_tab, structs_sym_tab +): """Allocate memory for variable assigned from a call.""" if isinstance(rval.func, ast.Name): @@ -116,15 +123,74 @@ def _allocate_for_call(builder, var_name, rval, local_sym_tab, structs_sym_tab): elif isinstance(rval.func, ast.Attribute): # Map method calls - need double allocation for ptr handling - _allocate_for_map_method(builder, var_name, local_sym_tab) + _allocate_for_map_method( + builder, var_name, rval, local_sym_tab, map_sym_tab, structs_sym_tab + ) else: logger.warning(f"Unsupported call function type for {var_name}") -def _allocate_for_map_method(builder, var_name, local_sym_tab): +def _allocate_for_map_method( + builder, var_name, rval, local_sym_tab, map_sym_tab, structs_sym_tab +): """Allocate memory for variable assigned from map method (double alloc).""" + map_name = rval.func.value.id + method_name = rval.func.attr + + # NOTE: We will have to special case HashMap.lookup which returns a pointer to value type + # The value type can be a struct as well, so we need to handle that properly + # This special casing is not ideal, as over time other map methods may need similar handling + # But for now, we will just handle lookup specifically + if map_name not in map_sym_tab: + logger.error(f"Map '{map_name}' not found for allocation") + return + + if method_name != "lookup": + # Fallback allocation for other map methods + _allocate_for_map_method_fallback(builder, var_name, local_sym_tab) + return + + map_params = map_sym_tab[map_name].params + if map_params["type"] != BPFMapType.HASH: + logger.warning( + "Map method lookup used on non-hash map, using fallback allocation" + ) + _allocate_for_map_method_fallback(builder, var_name, local_sym_tab) + return + + value_type = map_params["value"] + # Determine IR type for value + if isinstance(value_type, str) and value_type in structs_sym_tab: + struct_info = structs_sym_tab[value_type] + value_ir_type = struct_info.ir_type + else: + value_ir_type = ctypes_to_ir(value_type) + + if value_ir_type is None: + logger.warning( + f"Could not determine IR type for map value '{value_type}', using fallback allocation" + ) + _allocate_for_map_method_fallback(builder, var_name, local_sym_tab) + return + + # Main variable (pointer to pointer) + ir_type = ir.PointerType(value_ir_type) + var = builder.alloca(ir_type, name=var_name) + local_sym_tab[var_name] = LocalSymbol(var, ir_type) + # Temporary variable for computed values + tmp_ir_type = value_ir_type + var_tmp = builder.alloca(tmp_ir_type, name=f"{var_name}_tmp") + local_sym_tab[f"{var_name}_tmp"] = LocalSymbol(var_tmp, tmp_ir_type) + logger.info( + f"Pre-allocated {var_name} and {var_name}_tmp for map method lookup of type {value_ir_type}" + ) + + +def _allocate_for_map_method_fallback(builder, var_name, local_sym_tab): + """Fallback allocation for map method variable (i64* and i64**).""" + # Main variable (pointer to pointer) ir_type = ir.PointerType(ir.IntType(64)) var = builder.alloca(ir_type, name=var_name) @@ -135,7 +201,9 @@ def _allocate_for_map_method(builder, var_name, local_sym_tab): var_tmp = builder.alloca(tmp_ir_type, name=f"{var_name}_tmp") local_sym_tab[f"{var_name}_tmp"] = LocalSymbol(var_tmp, tmp_ir_type) - logger.info(f"Pre-allocated {var_name} and {var_name}_tmp for map method") + logger.info( + f"Pre-allocated {var_name} and {var_name}_tmp for map method (fallback)" + ) def _allocate_for_constant(builder, var_name, rval, local_sym_tab): diff --git a/pythonbpf/functions/functions_pass.py b/pythonbpf/functions/functions_pass.py index 8e0a995..f78ed92 100644 --- a/pythonbpf/functions/functions_pass.py +++ b/pythonbpf/functions/functions_pass.py @@ -147,7 +147,9 @@ def allocate_mem( structs_sym_tab, ) elif isinstance(stmt, ast.Assign): - handle_assign_allocation(builder, stmt, local_sym_tab, structs_sym_tab) + handle_assign_allocation( + builder, stmt, local_sym_tab, map_sym_tab, structs_sym_tab + ) allocate_temp_pool(builder, max_temps_needed, local_sym_tab) diff --git a/pythonbpf/maps/maps_pass.py b/pythonbpf/maps/maps_pass.py index ed60958..ac498dc 100644 --- a/pythonbpf/maps/maps_pass.py +++ b/pythonbpf/maps/maps_pass.py @@ -48,7 +48,7 @@ def create_bpf_map(module, map_name, map_params): map_global.align = 8 logger.info(f"Created BPF map: {map_name} with params {map_params}") - return MapSymbol(type=map_params["type"], sym=map_global) + return MapSymbol(type=map_params["type"], sym=map_global, params=map_params) def _parse_map_params(rval, expected_args=None): diff --git a/pythonbpf/maps/maps_utils.py b/pythonbpf/maps/maps_utils.py index eaa43b2..194b408 100644 --- a/pythonbpf/maps/maps_utils.py +++ b/pythonbpf/maps/maps_utils.py @@ -11,6 +11,7 @@ class MapSymbol: type: BPFMapType sym: ir.GlobalVariable + params: dict[str, Any] | None = None class MapProcessorRegistry: From e5b3b001ce09fb5875f23aff99c071de10fbabc0 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 19 Nov 2025 04:29:35 +0530 Subject: [PATCH 68/68] Minor fix for PTR_TO_MAP_VALUE_OR_NULL target --- pythonbpf/allocation_pass.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonbpf/allocation_pass.py b/pythonbpf/allocation_pass.py index 5e44eb4..b5fa37c 100644 --- a/pythonbpf/allocation_pass.py +++ b/pythonbpf/allocation_pass.py @@ -176,7 +176,7 @@ def _allocate_for_map_method( return # Main variable (pointer to pointer) - ir_type = ir.PointerType(value_ir_type) + ir_type = ir.PointerType(ir.IntType(64)) var = builder.alloca(ir_type, name=var_name) local_sym_tab[var_name] = LocalSymbol(var, ir_type) # Temporary variable for computed values