diff --git a/pythonbpf/assign_pass.py b/pythonbpf/assign_pass.py index e0ef2db..fbec338 100644 --- a/pythonbpf/assign_pass.py +++ b/pythonbpf/assign_pass.py @@ -3,6 +3,8 @@ import logging from llvmlite import ir from pythonbpf.expr import eval_expr from pythonbpf.helper import emit_probe_read_kernel_str_call +from pythonbpf.type_deducer import ctypes_to_ir +from pythonbpf.vmlinux_parser.dependency_node import Field logger = logging.getLogger(__name__) @@ -148,7 +150,16 @@ def handle_variable_assignment( val, val_type = val_result logger.info(f"Evaluated value for {var_name}: {val} of type {val_type}, {var_type}") if val_type != var_type: - if isinstance(val_type, ir.IntType) and isinstance(var_type, ir.IntType): + if isinstance(val_type, Field): + logger.info("Handling assignment to struct field") + #TODO: handling only ctype struct fields for now. Handle other stuff too later. + if 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 + elif isinstance(val_type, ir.IntType) and isinstance(var_type, ir.IntType): # Allow implicit int widening if val_type.width < var_type.width: val = builder.sext(val, var_type) diff --git a/pythonbpf/codegen.py b/pythonbpf/codegen.py index cbda193..919236e 100644 --- a/pythonbpf/codegen.py +++ b/pythonbpf/codegen.py @@ -157,7 +157,8 @@ def compile_to_ir(filename: str, output: str, loglevel=logging.INFO): module.add_named_metadata("llvm.ident", [f"PythonBPF {VERSION}"]) - module_string = finalize_module(str(module)) + module_string: str = finalize_module(str(module)) + module_string += '\ndeclare ptr @llvm.preserve.struct.access.index.p0.p0(ptr, i32 immarg, i32 immarg) "nocallback" "nofree" "nosync" "nounwind" "willreturn" "memory(none)"' logger.info(f"IR written to {output}") with open(output, "w") as f: diff --git a/pythonbpf/type_deducer.py b/pythonbpf/type_deducer.py index 9867cc6..3f9c6a8 100644 --- a/pythonbpf/type_deducer.py +++ b/pythonbpf/type_deducer.py @@ -13,6 +13,8 @@ mapping = { "c_float": ir.FloatType(), "c_double": ir.DoubleType(), "c_void_p": ir.IntType(64), + "c_long": ir.IntType(64), + "c_longlong": ir.IntType(64), # 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 a3e0ad1..7f53a15 100644 --- a/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py +++ b/pythonbpf/vmlinux_parser/vmlinux_exports_handler.py @@ -73,17 +73,6 @@ class VmlinuxHandler: return value return None - def handle_vmlinux_struct(self, struct_name, module, builder): - """Handle vmlinux struct initializations""" - if self.is_vmlinux_struct(struct_name): - # TODO: Implement core-specific struct handling - # This will be more complex and depends on the BTF information - logger.info(f"Handling vmlinux struct {struct_name}") - # Return struct type and allocated pointer - # This is a stub, actual implementation will be more complex - return None - return None - def handle_vmlinux_struct_field( self, struct_var_name, field_name, module, builder, local_sym_tab ): @@ -97,27 +86,83 @@ class VmlinuxHandler: globvar_ir, field_data = self.get_field_type( python_type.__name__, field_name ) - - # Load the offset value - offset = builder.load(globvar_ir) - - # Convert pointer to integer - i64_type = ir.IntType(64) - ptr_as_int = builder.ptrtoint(var_info.var, i64_type) - - # Add the offset - field_addr = builder.add(ptr_as_int, offset) - - # Convert back to pointer - i8_ptr_type = ir.IntType(8).as_pointer() - field_ptr_i8 = builder.inttoptr(field_addr, i8_ptr_type) - logger.info(f"field_ptr_i8: {field_ptr_i8}") - + 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) + print(field_ptr) # Return pointer to field and field type - return field_ptr_i8, field_data + 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): + """ + Generate LLVM IR to load a field from BPF context using offset. + + Args: + builder: llvmlite IRBuilder instance + ctx_arg: The context pointer argument (ptr/i8*) + offset_global: Global variable containing the field offset (i64) + + Returns: + The loaded value (i64 register) + """ + + # Load the offset value + offset = builder.load(offset_global) + + # Ensure ctx_arg is treated as i8* (byte pointer) + i8_type = ir.IntType(8) + i8_ptr_type = ir.PointerType(i8_type) + + # Cast ctx_arg to i8* if it isn't already + if str(ctx_arg.type) != str(i8_ptr_type): + ctx_i8_ptr = builder.bitcast(ctx_arg, i8_ptr_type) + else: + ctx_i8_ptr = ctx_arg + + # GEP with explicit type - this is the key fix + field_ptr = builder.gep( + ctx_i8_ptr, + [offset], + inbounds=False, + ) + + # Get or declare the BPF passthrough intrinsic + module = builder.function.module + + try: + passthrough_fn = module.globals.get('llvm.bpf.passthrough.p0.p0') + if passthrough_fn is None: + raise KeyError + except (KeyError, AttributeError): + passthrough_type = ir.FunctionType( + i8_ptr_type, + [ir.IntType(32), i8_ptr_type] + ) + passthrough_fn = ir.Function( + module, + passthrough_type, + name='llvm.bpf.passthrough.p0.p0' + ) + + # Call passthrough to satisfy BPF verifier + verified_ptr = builder.call( + passthrough_fn, + [ir.Constant(ir.IntType(32), 0), field_ptr], + ) + + # 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) + + # Load and return the value + value = builder.load(typed_ptr) + + return value + + def has_field(self, struct_name, field_name): """Check if a vmlinux struct has a specific field""" if self.is_vmlinux_struct(struct_name): diff --git a/tests/c-form/struct_field_tests.bpf.c b/tests/c-form/struct_field_tests.bpf.c index 6f447e0..c0c3020 100644 --- a/tests/c-form/struct_field_tests.bpf.c +++ b/tests/c-form/struct_field_tests.bpf.c @@ -19,8 +19,8 @@ There is no point of SEC("tp/syscalls/sys_enter_execve") int handle_setuid_entry(struct trace_event_raw_sys_enter *ctx) { // Access each argument separately with clear variable assignments - long int arg0 = ctx->id; - bpf_printk("args[0]: %d", arg0); + long int id = ctx->id; + bpf_printk("This is context field %d", id); /* * the IR to aim for is * %2 = alloca ptr, align 8 diff --git a/tests/failing_tests/vmlinux/struct_field_access.py b/tests/failing_tests/vmlinux/struct_field_access.py index 4ff90c2..0a6d68d 100644 --- a/tests/failing_tests/vmlinux/struct_field_access.py +++ b/tests/failing_tests/vmlinux/struct_field_access.py @@ -14,11 +14,9 @@ from ctypes import c_int64, c_void_p # noqa: F401 @bpf @section("tracepoint/syscalls/sys_enter_execve") def hello_world(ctx: struct_trace_event_raw_sys_enter) -> c_int64: - a = 2 + TASK_COMM_LEN + TASK_COMM_LEN b = ctx.id - print(f"Hello, World{TASK_COMM_LEN} and {a}") print(f"This is context field {b}") - return c_int64(TASK_COMM_LEN + 2) + return c_int64(0) @bpf @@ -28,4 +26,4 @@ def LICENSE() -> str: compile_to_ir("struct_field_access.py", "struct_field_access.ll", loglevel=logging.INFO) -# compile() +compile()