From 5cbd9a531e0190530a89204c6e35d44d648a9c0f Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 27 Oct 2025 01:08:56 +0530 Subject: [PATCH 01/36] 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 2f9c3473..5c38137f 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 @@ def helper_call_handler( "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 78686778..fa0e1e14 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 cb1a8e12..f8f2bd61 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/36] 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 00000000..396927ba --- /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/36] 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 fa0e1e14..196c9d97 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/36] 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 196c9d97..362bb416 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -10,6 +10,7 @@ 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 fdfd4524..841698ce 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/36] 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 5c38137f..1bfa7ace 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 @@ def helper_call_handler( "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 362bb416..420c1c56 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 f8f2bd61..440d3110 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/36] 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 00000000..fcece4d6 --- /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/36] 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 420c1c56..e231db91 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/36] 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 e231db91..230d4a8e 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/36] 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 1bfa7ace..ee07d5af 100644 --- a/pythonbpf/helper/__init__.py +++ b/pythonbpf/helper/__init__.py @@ -9,6 +9,7 @@ probe_read_str, random, probe_read, + smp_processor_id, XDP_DROP, XDP_PASS, ) @@ -77,6 +78,7 @@ def helper_call_handler( "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 440d3110..6bed1311 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/36] 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 00000000..8c17a756 --- /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 8b28a927c3cb2c5ab0edcc11fe3486199e3a3bed Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 2 Nov 2025 03:27:27 +0530 Subject: [PATCH 11/36] 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 230d4a8e..f755ce99 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 12/36] 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 f755ce99..c434f34b 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 13/36] 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 c434f34b..762a0b2a 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 14/36] 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 ee07d5af..a243140c 100644 --- a/pythonbpf/helper/__init__.py +++ b/pythonbpf/helper/__init__.py @@ -10,6 +10,7 @@ random, probe_read, smp_processor_id, + uid, XDP_DROP, XDP_PASS, ) @@ -79,6 +80,7 @@ def helper_call_handler( "random", "probe_read", "smp_processor_id", + "uid", "XDP_DROP", "XDP_PASS", ] diff --git a/pythonbpf/helper/helpers.py b/pythonbpf/helper/helpers.py index 6bed1311..84ffd9a0 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 15/36] 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 00000000..e4e50b44 --- /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 16/36] 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 762a0b2a..9a6304f6 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 17/36] 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 9a6304f6..abf35efe 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 18/36] 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 a243140c..4c1d2831 100644 --- a/pythonbpf/helper/__init__.py +++ b/pythonbpf/helper/__init__.py @@ -11,6 +11,7 @@ probe_read, smp_processor_id, uid, + skb_store_bytes, XDP_DROP, XDP_PASS, ) @@ -81,6 +82,7 @@ def helper_call_handler( "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 84ffd9a0..0e55a20e 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 19/36] 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 abf35efe..8421c459 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 0e55a20e..302b5263 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 20/36] 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 476e3b60..7fbb597e 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 @@ def decorator(func): @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 21/36] 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 7fbb597e..dccb8c26 100644 --- a/pythonbpf/helper/helper_registry.py +++ b/pythonbpf/helper/helper_registry.py @@ -40,3 +40,22 @@ def get_handler(cls, helper_name): 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 22/36] 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 e391092b..47d83b52 100644 --- a/pythonbpf/functions/functions_pass.py +++ b/pythonbpf/functions/functions_pass.py @@ -33,7 +33,7 @@ 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 23/36] 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 49c787f4..ae748dd1 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 47d83b52..c652d3eb 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 update_max_temps_for_stmt(stmt): 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 24/36] 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 c652d3eb..33342b7d 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 dccb8c26..0e09d70c 100644 --- a/pythonbpf/helper/helper_registry.py +++ b/pythonbpf/helper/helper_registry.py @@ -50,7 +50,7 @@ def get_signature(cls, helper_name): 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 841698ce..4ec901db 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 25/36] 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 4ec901db..71665c87 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 26/36] 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 8421c459..21b73d5a 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 27/36] 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 33342b7d..c9943682 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 3e6cea2b67c08f6bf6eeca9b20952daeaa5f55bb Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Wed, 5 Nov 2025 19:10:58 +0530 Subject: [PATCH 28/36] 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 71665c87..125011ba 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 a18f1354..221be104 100644 --- a/pythonbpf/helper/printk_formatter.py +++ b/pythonbpf/helper/printk_formatter.py @@ -4,6 +4,7 @@ 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 29/36] 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 21b73d5a..4c757833 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 30/36] 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 4c757833..5ac87646 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 125011ba..613e0255 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 31/36] 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 5ac87646..cf08327d 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 32/36] 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 cf08327d..b3bf6635 100644 --- a/pythonbpf/helper/bpf_helper_handler.py +++ b/pythonbpf/helper/bpf_helper_handler.py @@ -8,7 +8,6 @@ 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 613e0255..4d7de220 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 221be104..58990c05 100644 --- a/pythonbpf/helper/printk_formatter.py +++ b/pythonbpf/helper/printk_formatter.py @@ -4,7 +4,7 @@ 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 33/36] 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 4d7de220..d7019b8f 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -18,7 +18,7 @@ def __init__(self): @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 34/36] 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 d7019b8f..0a921492 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 35/36] 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 0a921492..06d3cf14 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 36/36] 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 b3bf6635..acfedefa 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)