From a8595ff1d2a3063981a6a58eb781380342317a12 Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Thu, 27 Nov 2025 14:02:00 +0530 Subject: [PATCH] feat: allocate tmp variable for pointer to vmlinux struct field access. --- pythonbpf/allocation_pass.py | 20 ++++++++++--- .../vmlinux_parser/vmlinux_exports_handler.py | 29 +++++++++++++++---- tests/failing_tests/xdp/xdp_test_1.py | 15 ++++++---- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/pythonbpf/allocation_pass.py b/pythonbpf/allocation_pass.py index 9099ced..db6f0ba 100644 --- a/pythonbpf/allocation_pass.py +++ b/pythonbpf/allocation_pass.py @@ -384,6 +384,7 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_ f"Could not determine size for ctypes field {field_name}: {e}" ) actual_ir_type = ir.IntType(64) + field_size_bits = 64 # Check if it's a nested vmlinux struct or complex type elif field.type.__module__ == "vmlinux": @@ -392,23 +393,34 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_ field.ctype_complex_type, ctypes._Pointer ): actual_ir_type = ir.IntType(64) # Pointer is always 64-bit + field_size_bits = 64 # 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) + field_size_bits = 64 else: logger.warning( f"Unknown field type module {field.type.__module__} for {field_name}" ) actual_ir_type = ir.IntType(64) + field_size_bits = 64 - # Allocate with the actual IR type + # Pre-allocate the tmp storage used by load_struct_field (so we don't alloca inside handler) + tmp_name = f"{struct_var}_{field_name}_tmp" + tmp_ir_type = ir.IntType(field_size_bits) + tmp_var = builder.alloca(tmp_ir_type, name=tmp_name) + tmp_var.align = tmp_ir_type.width // 8 + local_sym_tab[tmp_name] = LocalSymbol(tmp_var, tmp_ir_type) + logger.info( + f"Pre-allocated temp {tmp_name} (i{field_size_bits}) for vmlinux field read {vmlinux_struct_name}.{field_name}" + ) + + # Allocate with the actual IR type for the destination var var = _allocate_with_type(builder, var_name, actual_ir_type) - local_sym_tab[var_name] = LocalSymbol( - var, actual_ir_type, field - ) # <-- Store Field metadata + local_sym_tab[var_name] = LocalSymbol(var, actual_ir_type, field) logger.info( f"Pre-allocated {var_name} as {actual_ir_type} from vmlinux struct {vmlinux_struct_name}.{field_name}" diff --git a/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py b/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py index e5bc153..bba0cd3 100644 --- a/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py +++ b/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py @@ -77,7 +77,7 @@ class VmlinuxHandler: return None def get_vmlinux_enum_value(self, name): - """Handle vmlinux enum constants by returning LLVM IR constants""" + """Handle vmlinux.enum constants by returning LLVM IR constants""" if self.is_vmlinux_enum(name): value = self.vmlinux_symtab[name].value logger.info(f"The value of vmlinux enum {name} = {value}") @@ -119,6 +119,9 @@ class VmlinuxHandler: # Load the struct pointer from the local variable struct_ptr = builder.load(var_info.var) + # Determine the preallocated tmp name that assignment pass should have created + tmp_name = f"{struct_var_name}_{field_name}_tmp" + # Use bpf_probe_read_kernel for non-context struct field access field_value = self.load_struct_field( builder, @@ -127,6 +130,7 @@ class VmlinuxHandler: field_data, struct_name, local_sym_tab, + tmp_name, ) # Return field value and field type return field_value, field_data @@ -141,6 +145,7 @@ class VmlinuxHandler: field_data, struct_name=None, local_sym_tab=None, + tmp_name: str = None, ): """ Generate LLVM IR to load a field from a regular (non-context) struct using bpf_probe_read_kernel. @@ -151,6 +156,8 @@ class VmlinuxHandler: 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) + local_sym_tab: symbol table (optional) - used to locate preallocated tmp storage + tmp_name: name of the preallocated temporary storage to use (preferred) Returns: The loaded value """ @@ -213,10 +220,20 @@ class VmlinuxHandler: else: logger.warning("Complex vmlinux field type, using default 64 bits") - # Allocate local storage for the field value - # TODO: CRITICAL BUG. alloca cannot be used anywhere other than the basic block - local_storage = builder.alloca(ir.IntType(int_width)) - local_storage_i8_ptr = builder.bitcast(local_storage, i8_ptr_type) + # Use preallocated temporary storage if provided by allocation pass + + local_storage_i8_ptr = None + if tmp_name and local_sym_tab and tmp_name in local_sym_tab: + # Expect the tmp to be an alloca created during allocation pass + tmp_alloca = local_sym_tab[tmp_name].var + local_storage_i8_ptr = builder.bitcast(tmp_alloca, i8_ptr_type) + else: + # Fallback: allocate inline (not ideal, but preserves behavior) + local_storage = builder.alloca(ir.IntType(int_width)) + local_storage_i8_ptr = builder.bitcast(local_storage, i8_ptr_type) + logger.warning( + f"Temp storage '{tmp_name}' not found. Allocating inline" + ) # Use bpf_probe_read_kernel to safely read the field # This generates: @@ -230,7 +247,7 @@ class VmlinuxHandler: ) # Load the value from local storage - value = builder.load(local_storage) + value = builder.load(builder.bitcast(local_storage_i8_ptr, ir.PointerType(ir.IntType(int_width)))) # Zero-extend i32 to i64 if needed if needs_zext: diff --git a/tests/failing_tests/xdp/xdp_test_1.py b/tests/failing_tests/xdp/xdp_test_1.py index bda7664..cdd3dca 100644 --- a/tests/failing_tests/xdp/xdp_test_1.py +++ b/tests/failing_tests/xdp/xdp_test_1.py @@ -26,15 +26,18 @@ class iphdr: def ip_detector(ctx: struct_xdp_md) -> c_int64: data = ctx.data data_end = ctx.data_end - eth = struct_ethhdr(ctx.data) - nh = ctx.data + 14 + if data + 14 > data_end: + return c_int64(XDP_DROP) + + eth = struct_ethhdr(data) + nh = data + 14 if nh + 20 > data_end: return c_int64(XDP_DROP) + iph = iphdr(nh) - h_proto = eth.h_proto - h_proto_ext = c_int64(h_proto) - ipv4 = iph.saddr - print(f"ipaddress: {ipv4}") + + print(f"ipaddress: {iph.saddr}") + return c_int64(XDP_PASS)