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}'" 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):