From 101183c3158a1536a9ce2a23f09abc44067045e1 Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Sat, 18 Oct 2025 21:45:26 +0530 Subject: [PATCH] members generated for simple ctypes --- pythonbpf/debuginfo/debug_info_generator.py | 31 ++++ .../vmlinux_parser/ir_gen/debug_info_gen.py | 155 ++++++++++++++++-- tests/passing_tests/vmlinux/xdp_pass.py | 20 +-- 3 files changed, 177 insertions(+), 29 deletions(-) diff --git a/pythonbpf/debuginfo/debug_info_generator.py b/pythonbpf/debuginfo/debug_info_generator.py index ab9fed4..e9703db 100644 --- a/pythonbpf/debuginfo/debug_info_generator.py +++ b/pythonbpf/debuginfo/debug_info_generator.py @@ -101,6 +101,21 @@ class DebugInfoGenerator: }, ) + def create_struct_member_vmlinux(self, name: str, base_type_with_size: Any, offset: int) -> Any: + """Create a struct member with the given name, type, and offset""" + base_type, type_size = base_type_with_size + return self.module.add_debug_info( + "DIDerivedType", + { + "tag": dc.DW_TAG_member, + "name": name, + "file": self.module._file_metadata, + "baseType": base_type, + "size": getattr(base_type, "size", type_size), + "offset": offset, + }, + ) + def create_struct_type( self, members: List[Any], size: int, is_distinct: bool ) -> Any: @@ -116,6 +131,22 @@ class DebugInfoGenerator: is_distinct=is_distinct, ) + def create_struct_type_with_name( + self, name: str, members: List[Any], size: int, is_distinct: bool + ) -> Any: + """Create a struct type with the given members and size""" + return self.module.add_debug_info( + "DICompositeType", + { + "name": name, + "tag": dc.DW_TAG_structure_type, + "file": self.module._file_metadata, + "size": size, + "elements": members, + }, + is_distinct=is_distinct, + ) + def create_global_var_debug_info( self, name: str, var_type: Any, is_local: bool = False ) -> Any: diff --git a/pythonbpf/vmlinux_parser/ir_gen/debug_info_gen.py b/pythonbpf/vmlinux_parser/ir_gen/debug_info_gen.py index 8127d01..ab0bf46 100644 --- a/pythonbpf/vmlinux_parser/ir_gen/debug_info_gen.py +++ b/pythonbpf/vmlinux_parser/ir_gen/debug_info_gen.py @@ -1,20 +1,155 @@ -from pythonbpf.debuginfo import DebugInfoGenerator +from pythonbpf.debuginfo import DebugInfoGenerator, dwarf_constants as dc from ..dependency_node import DependencyNode +import ctypes +import logging +from typing import List, Any, Tuple, Optional +logger = logging.getLogger(__name__) def debug_info_generation( - struct: DependencyNode, llvm_module, generated_debug_info: list -): + struct: DependencyNode, llvm_module, generated_debug_info: List[Tuple[DependencyNode, Any]] +) -> Any: + """ + Generate DWARF debug information for a struct defined in a DependencyNode. + + Args: + struct: The dependency node containing struct information + llvm_module: The LLVM module to add debug info to + generated_debug_info: List of tuples (struct, debug_info) to track generated debug info + + Returns: + The generated global variable debug info + """ + # Set up debug info generator generator = DebugInfoGenerator(llvm_module) + + # Check if debug info for this struct has already been generated + for existing_struct, debug_info in generated_debug_info: + if existing_struct.name == struct.name: + return debug_info + + # Process all fields and create members for the struct members = [] - uint32type = generator.get_uint32_type() for field_name, field in struct.fields.items(): - members.append(generator.create_struct_member(field_name, uint32type, field.offset)) + # Get appropriate debug type for this field + field_type = _get_field_debug_type( + field_name, field, generator, struct, generated_debug_info + ) + # Create struct member with proper offset + member = generator.create_struct_member_vmlinux( + field_name, field_type, field.offset * 8 + ) + members.append(member) - struct_type = generator.create_struct_type(members, struct.__sizeof__(), is_distinct=True) - - global_var = generator.create_global_var_debug_info( - struct.name, struct_type, is_local=False + if struct.name.startswith("struct_"): + struct_name = struct.name.removeprefix("struct_") + else: + raise ValueError("Unions are not supported in the current version") + # Create struct type with all members + struct_type = generator.create_struct_type_with_name( + struct_name, members, struct.__sizeof__() * 8, is_distinct=True ) - return global_var + return struct_type + + +def _get_field_debug_type( + field_name: str, + field, + generator: DebugInfoGenerator, + parent_struct: DependencyNode, + generated_debug_info: List[Tuple[DependencyNode, Any]] +) -> Any: + """ + Determine the appropriate debug type for a field based on its Python/ctypes type. + + Args: + field_name: Name of the field + field: Field object containing type information + generator: DebugInfoGenerator instance + parent_struct: The parent struct containing this field + generated_debug_info: List of already generated debug info + + Returns: + The debug info type for this field + """ + # Handle complex types (arrays, pointers) + if field.ctype_complex_type is not None: + if issubclass(field.ctype_complex_type, ctypes.Array): + # Handle array types + element_type = _get_basic_debug_type(field.containing_type, generator) + return generator.create_array_type(element_type, field.type_size) + elif issubclass(field.ctype_complex_type, ctypes._Pointer): + # Handle pointer types + pointee_type = _get_basic_debug_type(field.containing_type, generator) + return generator.create_pointer_type(pointee_type) + + # Handle other vmlinux types (nested structs) + if field.type.__module__ == "vmlinux": + # If it's a struct from vmlinux, check if we've already generated debug info for it + struct_name = field.type.__name__ + + # Look for existing debug info in the list + for existing_struct, debug_info in generated_debug_info: + if existing_struct.name == struct_name: + # Use existing debug info + return debug_info + + # If not found, create a forward declaration + # This will be completed when the actual struct is processed + logger.warning("Forward declaration in struct created") + forward_type = generator.create_struct_type([], 0, is_distinct=True) + return forward_type + + # Handle basic C types + return _get_basic_debug_type(field.type, generator) + + +def _get_basic_debug_type(ctype, generator: DebugInfoGenerator) -> Any: + """ + Map a ctypes type to a DWARF debug type. + + Args: + ctype: A ctypes type or Python type + generator: DebugInfoGenerator instance + + Returns: + The corresponding debug type + """ + # Map ctypes to debug info types + if ctype == ctypes.c_char or ctype == ctypes.c_byte: + return generator.get_basic_type("char", 8, dc.DW_ATE_signed_char), 8 + elif ctype == ctypes.c_ubyte or ctype == ctypes.c_uint8: + return generator.get_basic_type("unsigned char", 8, dc.DW_ATE_unsigned_char), 8 + elif ctype == ctypes.c_short or ctype == ctypes.c_int16: + return generator.get_basic_type("short", 16, dc.DW_ATE_signed), 16 + elif ctype == ctypes.c_ushort or ctype == ctypes.c_uint16: + return generator.get_basic_type("unsigned short", 16, dc.DW_ATE_unsigned), 16 + elif ctype == ctypes.c_int or ctype == ctypes.c_int32: + return generator.get_basic_type("int", 32, dc.DW_ATE_signed), 32 + elif ctype == ctypes.c_uint or ctype == ctypes.c_uint32: + return generator.get_basic_type("unsigned int", 32, dc.DW_ATE_unsigned), 32 + elif ctype == ctypes.c_long: + return generator.get_basic_type("long", 64, dc.DW_ATE_signed), 64 + elif ctype == ctypes.c_ulong: + return generator.get_basic_type("unsigned long", 64, dc.DW_ATE_unsigned), 64 + elif ctype == ctypes.c_longlong or ctype == ctypes.c_int64: + return generator.get_basic_type("long long", 64, dc.DW_ATE_signed), 64 + elif ctype == ctypes.c_ulonglong or ctype == ctypes.c_uint64: + return generator.get_basic_type("unsigned long long", 64, dc.DW_ATE_unsigned), 64 + elif ctype == ctypes.c_float: + return generator.get_basic_type("float", 32, dc.DW_ATE_float), 32 + elif ctype == ctypes.c_double: + return generator.get_basic_type("double", 64, dc.DW_ATE_float), 64 + elif ctype == ctypes.c_bool: + return generator.get_basic_type("bool", 8, dc.DW_ATE_boolean), 8 + elif ctype == ctypes.c_char_p: + char_type = generator.get_basic_type("char", 8, dc.DW_ATE_signed_char), 8 + return generator.create_pointer_type(char_type) + elif ctype == ctypes.c_void_p: + void_type = generator.module.add_debug_info( + "DIBasicType", {"name": "void"} + ) + return generator.create_pointer_type(void_type), 64 + else: + return generator.get_uint64_type(), 64 diff --git a/tests/passing_tests/vmlinux/xdp_pass.py b/tests/passing_tests/vmlinux/xdp_pass.py index 1e73614..484784b 100644 --- a/tests/passing_tests/vmlinux/xdp_pass.py +++ b/tests/passing_tests/vmlinux/xdp_pass.py @@ -3,7 +3,7 @@ from pythonbpf.maps import HashMap from pythonbpf.helper import XDP_PASS from vmlinux import TASK_COMM_LEN # noqa: F401 from vmlinux import struct_xdp_md -from vmlinux import struct_trace_event_raw_sys_enter # noqa: F401 +# from vmlinux import struct_trace_event_raw_sys_enter # noqa: F401 from ctypes import c_int64 # Instructions to how to run this program @@ -13,27 +13,9 @@ from ctypes import c_int64 # 4. Attach object file to any network device with something like ./check.sh xdp examples/xdp_pass.o tailscale0 # 5. send traffic through the device and observe effects - -@bpf -@map -def count() -> HashMap: - return HashMap(key=c_int64, value=c_int64, max_entries=1) - - @bpf @section("xdp") def hello_world(ctx: struct_xdp_md) -> c_int64: - key = 0 - one = 1 - prev = count().lookup(key) - if prev: - prevval = prev + 1 - print(f"count: {prevval}") - count().update(key, prevval) - return XDP_PASS - else: - count().update(key, one) - return XDP_PASS