5 Commits

5 changed files with 151 additions and 21 deletions

View File

@ -39,7 +39,7 @@ def finalize_module(original_str):
def bpf_passthrough_gen(module): def bpf_passthrough_gen(module):
i32_ty = ir.IntType(32) i32_ty = ir.IntType(32)
ptr_ty = ir.PointerType(ir.IntType(64)) ptr_ty = ir.PointerType(ir.IntType(8))
fnty = ir.FunctionType(ptr_ty, [i32_ty, ptr_ty]) fnty = ir.FunctionType(ptr_ty, [i32_ty, ptr_ty])
# Declare the intrinsic # Declare the intrinsic

View File

@ -184,3 +184,83 @@ class DebugInfoGenerator:
"DIGlobalVariableExpression", "DIGlobalVariableExpression",
{"var": global_var, "expr": self.module.add_debug_info("DIExpression", {})}, {"var": global_var, "expr": self.module.add_debug_info("DIExpression", {})},
) )
def get_int64_type(self):
return self.get_basic_type("long", 64, dc.DW_ATE_signed)
def create_subroutine_type(self, return_type, param_types):
"""
Create a DISubroutineType given return type and list of parameter types.
Equivalent to: !DISubroutineType(types: !{ret, args...})
"""
type_array = [return_type]
if isinstance(param_types, (list, tuple)):
type_array.extend(param_types)
else:
type_array.append(param_types)
return self.module.add_debug_info("DISubroutineType", {"types": type_array})
def create_local_variable_debug_info(
self, name: str, arg: int, var_type: Any
) -> Any:
"""
Create debug info for a local variable (DILocalVariable) without scope.
Example:
!DILocalVariable(name: "ctx", arg: 1, file: !3, line: 20, type: !7)
"""
return self.module.add_debug_info(
"DILocalVariable",
{
"name": name,
"arg": arg,
"file": self.module._file_metadata,
"type": var_type,
},
)
def add_scope_to_local_variable(self, local_variable_debug_info, scope_value):
"""
Add scope information to an existing local variable debug info object.
"""
# TODO: this is a workaround a flaw in the debug info generation. Fix this if possible in the future.
# We should not be touching llvmlite's internals like this.
if hasattr(local_variable_debug_info, "operands"):
# LLVM metadata operands is a tuple, so we need to rebuild it
existing_operands = local_variable_debug_info.operands
# Convert tuple to list, add scope, convert back to tuple
operands_list = list(existing_operands)
operands_list.append(("scope", scope_value))
# Reassign the new tuple
local_variable_debug_info.operands = tuple(operands_list)
def create_subprogram(
self, name: str, subroutine_type: Any, retained_nodes: List[Any]
) -> Any:
"""
Create a DISubprogram for a function.
Args:
name: Function name
subroutine_type: DISubroutineType for the function signature
retained_nodes: List of DILocalVariable nodes for function parameters/variables
Returns:
DISubprogram metadata
"""
return self.module.add_debug_info(
"DISubprogram",
{
"name": name,
"scope": self.module._file_metadata,
"file": self.module._file_metadata,
"type": subroutine_type,
# TODO: the following flags do not exist at the moment in our dwarf constants file. We need to add them.
# "flags": dc.DW_FLAG_Prototyped | dc.DW_FLAG_AllCallsDescribed,
# "spFlags": dc.DW_SPFLAG_Definition | dc.DW_SPFLAG_Optimized,
"unit": self.module._debug_compile_unit,
"retainedNodes": retained_nodes,
},
is_distinct=True,
)

View File

@ -1,9 +1,12 @@
import ast import ast
import llvmlite.ir as ir import llvmlite.ir as ir
import logging
from pythonbpf.debuginfo import DebugInfoGenerator from pythonbpf.debuginfo import DebugInfoGenerator
from pythonbpf.expr import VmlinuxHandlerRegistry from pythonbpf.expr import VmlinuxHandlerRegistry
import ctypes
logger = logging.getLogger(__name__)
def generate_function_debug_info( def generate_function_debug_info(
@ -12,10 +15,58 @@ def generate_function_debug_info(
generator = DebugInfoGenerator(module) generator = DebugInfoGenerator(module)
leading_argument = func_node.args.args[0] leading_argument = func_node.args.args[0]
leading_argument_name = leading_argument.arg leading_argument_name = leading_argument.arg
# TODO: add ctypes handling as well here annotation = leading_argument.annotation
print(leading_argument.arg, leading_argument.annotation.id) if func_node.returns is None:
context_debug_info = VmlinuxHandlerRegistry.get_struct_debug_info( # TODO: should check if this logic is consistent with function return type handling elsewhere
name=leading_argument.annotation.id return_type = ctypes.c_int64()
) elif hasattr(func_node.returns, "id"):
print(context_debug_info) return_type = func_node.returns.id
pass if return_type == "c_int32":
return_type = generator.get_int32_type()
elif return_type == "c_int64":
return_type = generator.get_int64_type()
elif return_type == "c_uint32":
return_type = generator.get_uint32_type()
elif return_type == "c_uint64":
return_type = generator.get_uint64_type()
else:
logger.warning(
"Return type should be int32, int64, uint32 or uint64 only. Falling back to int64"
)
return_type = generator.get_int64_type()
else:
return_type = ctypes.c_int64()
# context processing
if annotation is None:
logger.warning("Type of context of function not found.")
return
if hasattr(annotation, "id"):
ctype_name = annotation.id
if ctype_name == "c_void_p":
return
elif ctype_name.startswith("ctypes"):
raise SyntaxError(
"The first argument should always be a pointer to a struct or a void pointer"
)
context_debug_info = VmlinuxHandlerRegistry.get_struct_debug_info(annotation.id)
pointer_to_context_debug_info = generator.create_pointer_type(
context_debug_info, 64
)
subroutine_type = generator.create_subroutine_type(
return_type, pointer_to_context_debug_info
)
context_local_variable = generator.create_local_variable_debug_info(
leading_argument_name, 1, pointer_to_context_debug_info
)
retained_nodes = [context_local_variable]
print("function name", func_node.name)
subprogram_debug_info = generator.create_subprogram(
func_node.name, subroutine_type, retained_nodes
)
generator.add_scope_to_local_variable(
context_local_variable, subprogram_debug_info
)
func.set_metadata("dbg", subprogram_debug_info)
else:
logger.error(f"Invalid annotation type for argument '{leading_argument_name}'")

View File

@ -97,7 +97,7 @@ class VmlinuxHandler:
globvar_ir, field_data = self.get_field_type( globvar_ir, field_data = self.get_field_type(
python_type.__name__, field_name python_type.__name__, field_name
) )
builder.function.args[0].type = ir.PointerType(ir.IntType(64)) builder.function.args[0].type = ir.PointerType(ir.IntType(8))
print(builder.function.args[0]) print(builder.function.args[0])
field_ptr = self.load_ctx_field( field_ptr = self.load_ctx_field(
builder, builder.function.args[0], globvar_ir builder, builder.function.args[0], globvar_ir
@ -125,19 +125,18 @@ class VmlinuxHandler:
# Load the offset value # Load the offset value
offset = builder.load(offset_global) offset = builder.load(offset_global)
# # Ensure ctx_arg is treated as i8* (byte pointer) # Ensure ctx_arg is treated as i8* (byte pointer)
# # i8_type = ir.IntType(8) i8_ptr_type = ir.PointerType()
# i8_ptr_type = ir.PointerType()
# Cast ctx_arg to i8* if it isn't already # Cast ctx_arg to i8* if it isn't already
# if str(ctx_arg.type) != str(i8_ptr_type): if str(ctx_arg.type) != str(i8_ptr_type):
# ctx_i8_ptr = builder.bitcast(ctx_arg, i8_ptr_type) ctx_i8_ptr = builder.bitcast(ctx_arg, i8_ptr_type)
# else: else:
# ctx_i8_ptr = ctx_arg ctx_i8_ptr = ctx_arg
# GEP with explicit type - this is the key fix # GEP with explicit type - this is the key fix
field_ptr = builder.gep( field_ptr = builder.gep(
ctx_arg, ctx_i8_ptr,
[offset], [offset],
inbounds=False, inbounds=False,
) )
@ -151,8 +150,8 @@ class VmlinuxHandler:
raise KeyError raise KeyError
except (KeyError, AttributeError): except (KeyError, AttributeError):
passthrough_type = ir.FunctionType( passthrough_type = ir.FunctionType(
ir.PointerType(), i8_ptr_type,
[ir.IntType(32), ir.PointerType()], [ir.IntType(32), i8_ptr_type],
) )
passthrough_fn = ir.Function( passthrough_fn = ir.Function(
module, module,

View File

@ -4,7 +4,7 @@ from pythonbpf import bpf, section, bpfglobal, compile_to_ir
from pythonbpf import compile # noqa: F401 from pythonbpf import compile # noqa: F401
from vmlinux import TASK_COMM_LEN # noqa: F401 from vmlinux import TASK_COMM_LEN # noqa: F401
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, c_void_p # noqa: F401 from ctypes import c_int64, c_int32, c_void_p # noqa: F401
# from vmlinux import struct_uinput_device # from vmlinux import struct_uinput_device