From b47063c49c475f61d7678b5807efacb96f2800aa Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 7 Aug 2026 04:38:49 +0530 Subject: [PATCH 1/2] Core: Resolve plain-ctypes context parameter in helper args process_func_body only entered the context parameter into local_sym_tab when its annotation was a vmlinux struct, so a program declared as `def prog(ctx: c_void_p)` had no `ctx` symbol at all. Passing `ctx` to a helper that takes a raw pointer -- probe_read(dst, size, ctx) -- failed with "Variable 'ctx' not found in local symbol table". Register the parameter for plain ctypes annotations too, keeping `var` as None: that is the established sentinel meaning "this symbol is the context parameter, read it from func.args[0]" (see VmlinuxHandler.handle_vmlinux_struct_field). Metadata holds the annotated Python type, mirroring the vmlinux branch. Teach the consumer side accordingly: get_or_create_ptr_from_arg now recognises the sentinel and hands back the function's first argument instead of an alloca. The special case lives at the call site rather than inside get_var_ptr_from_name so that helper stays a pure symbol table lookup -- the call site already has `func` and `builder` in scope, and get_var_ptr_from_name has no other callers to update. Co-Authored-By: Claude Opus 5 (1M context) --- pythonbpf/functions/functions_pass.py | 18 +++++++++++++++++- pythonbpf/helper/helper_utils.py | 16 ++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/pythonbpf/functions/functions_pass.py b/pythonbpf/functions/functions_pass.py index 23a81ded..89e87af6 100644 --- a/pythonbpf/functions/functions_pass.py +++ b/pythonbpf/functions/functions_pass.py @@ -1,11 +1,12 @@ from llvmlite import ir import ast +import ctypes import logging from pythonbpf.helper import ( HelperHandlerRegistry, ) -from pythonbpf.type_deducer import ctypes_to_ir +from pythonbpf.type_deducer import ctypes_to_ir, is_ctypes from pythonbpf.expr import ( eval_expr, handle_expr, @@ -325,6 +326,9 @@ def process_func_body( f"Unsupported annotation type: {ast.dump(context_arg.annotation)}" ) + # NOTE: `var` is None for the context parameter. It is the sentinel + # that tells consumers the symbol is an incoming function argument + # (`func.args[0]`) rather than a stack slot they can load from. if VmlinuxHandlerRegistry.is_vmlinux_struct(context_type_name): resolved_type = VmlinuxHandlerRegistry.get_struct_type( context_type_name @@ -332,6 +336,18 @@ def process_func_body( context_type = LocalSymbol(None, None, resolved_type) local_sym_tab[context_name] = context_type logger.info(f"Added argument '{context_name}' to local symbol table") + elif is_ctypes(context_type_name): + # Plain ctypes annotation, e.g. `ctx: c_void_p`. Register it so + # helpers that take the raw context (probe_read, ...) can resolve + # the name. Metadata is the annotated Python type, mirroring the + # vmlinux branch above. + context_type = LocalSymbol( + None, + ir.PointerType(), + getattr(ctypes, context_type_name, None), + ) + local_sym_tab[context_name] = context_type + logger.info(f"Added argument '{context_name}' to local symbol table") # pre-allocate dynamic variables local_sym_tab = allocate_mem( diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index 211a925c..958db55c 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -50,8 +50,20 @@ def get_or_create_ptr_from_arg( 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) + symbol = local_sym_tab.get(arg.id) if local_sym_tab else None + if symbol is not None and symbol.var is None: + # A None `var` marks the context parameter (see process_func_body): + # it arrives as the function's first argument, not as a stack slot, + # so there is no alloca to hand back. Use the argument itself. + if not func.args: + raise ValueError( + f"'{arg.id}' is the context parameter but " + f"'{func.name}' takes no arguments" + ) + ptr = builder.bitcast(func.args[0], ir.PointerType()) + else: + # 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 # Default to i64 if expected_type and isinstance(expected_type, ir.IntType): From d7a763639a2f3eda4da2bdb7ade58c2c1327ab0f Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 7 Aug 2026 04:39:01 +0530 Subject: [PATCH 2/2] Core: Guard vmlinux struct lookup against non-class metadata _allocate_for_attribute reached for `metadata.__name__` whenever the base symbol was not a known user struct, but `metadata` is only a struct class for struct-typed symbols. For a symbol carrying None (any plainly typed local) or a ctypes class (a `c_void_p` context parameter), that raised AttributeError instead of reporting the unsupported access. Resolve the name via getattr and fall through to the existing "struct type not found" diagnostic when there is none. Co-Authored-By: Claude Opus 5 (1M context) --- pythonbpf/allocation_pass.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pythonbpf/allocation_pass.py b/pythonbpf/allocation_pass.py index 9aaaf19e..293839da 100644 --- a/pythonbpf/allocation_pass.py +++ b/pythonbpf/allocation_pass.py @@ -340,9 +340,15 @@ def _allocate_for_attribute( struct_type: type = local_sym_tab[struct_var].metadata if not struct_type or struct_type not in structs_sym_tab: - if VmlinuxHandlerRegistry.is_vmlinux_struct(struct_type.__name__): + # `metadata` only names a struct for struct-typed symbols. For anything + # else (None, an IR type, or a plain ctypes class such as a `c_void_p` + # context parameter) there is no vmlinux struct name to look up, so guard + # the attribute access instead of blowing up with an AttributeError. + vmlinux_struct_name = getattr(struct_type, "__name__", None) + if vmlinux_struct_name and VmlinuxHandlerRegistry.is_vmlinux_struct( + vmlinux_struct_name + ): # Handle vmlinux struct field access - vmlinux_struct_name = struct_type.__name__ if not VmlinuxHandlerRegistry.has_field(vmlinux_struct_name, field_name): logger.error( f"Field '{field_name}' not found in vmlinux struct '{vmlinux_struct_name}'"