mirror of
https://github.com/varun-r-mallya/Python-BPF.git
synced 2026-02-12 16:10:59 +00:00
Compare commits
3 Commits
93285dbdd8
...
8bd210cede
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bd210cede | |||
| 7bf6f9c48c | |||
| a1fe2ed4bc |
@ -152,12 +152,14 @@ def handle_variable_assignment(
|
|||||||
if val_type != var_type:
|
if val_type != var_type:
|
||||||
if isinstance(val_type, Field):
|
if isinstance(val_type, Field):
|
||||||
logger.info("Handling assignment to struct field")
|
logger.info("Handling assignment to struct field")
|
||||||
#TODO: handling only ctype struct fields for now. Handle other stuff too later.
|
# TODO: handling only ctype struct fields for now. Handle other stuff too later.
|
||||||
if var_type == ctypes_to_ir(val_type.type.__name__):
|
if var_type == ctypes_to_ir(val_type.type.__name__):
|
||||||
builder.store(val, var_ptr)
|
builder.store(val, var_ptr)
|
||||||
logger.info(f"Assigned ctype struct field to {var_name}")
|
logger.info(f"Assigned ctype struct field to {var_name}")
|
||||||
return True
|
return True
|
||||||
logger.error(f"Failed to assign ctype struct field to {var_name}: {val_type} != {var_type}")
|
logger.error(
|
||||||
|
f"Failed to assign ctype struct field to {var_name}: {val_type} != {var_type}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
elif isinstance(val_type, ir.IntType) and isinstance(var_type, ir.IntType):
|
elif isinstance(val_type, ir.IntType) and isinstance(var_type, ir.IntType):
|
||||||
# Allow implicit int widening
|
# Allow implicit int widening
|
||||||
|
|||||||
@ -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(8))
|
ptr_ty = ir.PointerType(ir.IntType(64))
|
||||||
fnty = ir.FunctionType(ptr_ty, [i32_ty, ptr_ty])
|
fnty = ir.FunctionType(ptr_ty, [i32_ty, ptr_ty])
|
||||||
|
|
||||||
# Declare the intrinsic
|
# Declare the intrinsic
|
||||||
@ -158,7 +158,6 @@ def compile_to_ir(filename: str, output: str, loglevel=logging.INFO):
|
|||||||
module.add_named_metadata("llvm.ident", [f"PythonBPF {VERSION}"])
|
module.add_named_metadata("llvm.ident", [f"PythonBPF {VERSION}"])
|
||||||
|
|
||||||
module_string: str = 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}")
|
logger.info(f"IR written to {output}")
|
||||||
with open(output, "w") as f:
|
with open(output, "w") as f:
|
||||||
|
|||||||
@ -39,6 +39,12 @@ class VmlinuxHandlerRegistry:
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_struct_debug_info(cls, name):
|
||||||
|
if cls._handler is None:
|
||||||
|
return False
|
||||||
|
return cls._handler.get_struct_debug_info(name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_vmlinux_struct(cls, name):
|
def is_vmlinux_struct(cls, name):
|
||||||
"""Check if a name refers to a vmlinux struct"""
|
"""Check if a name refers to a vmlinux struct"""
|
||||||
|
|||||||
21
pythonbpf/functions/function_debug_info.py
Normal file
21
pythonbpf/functions/function_debug_info.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import ast
|
||||||
|
|
||||||
|
import llvmlite.ir as ir
|
||||||
|
|
||||||
|
from pythonbpf.debuginfo import DebugInfoGenerator
|
||||||
|
from pythonbpf.expr import VmlinuxHandlerRegistry
|
||||||
|
|
||||||
|
|
||||||
|
def generate_function_debug_info(
|
||||||
|
func_node: ast.FunctionDef, module: ir.Module, func: ir.Function
|
||||||
|
):
|
||||||
|
generator = DebugInfoGenerator(module)
|
||||||
|
leading_argument = func_node.args.args[0]
|
||||||
|
leading_argument_name = leading_argument.arg
|
||||||
|
# TODO: add ctypes handling as well here
|
||||||
|
print(leading_argument.arg, leading_argument.annotation.id)
|
||||||
|
context_debug_info = VmlinuxHandlerRegistry.get_struct_debug_info(
|
||||||
|
name=leading_argument.annotation.id
|
||||||
|
)
|
||||||
|
print(context_debug_info)
|
||||||
|
pass
|
||||||
@ -23,7 +23,7 @@ from pythonbpf.allocation_pass import (
|
|||||||
create_targets_and_rvals,
|
create_targets_and_rvals,
|
||||||
LocalSymbol,
|
LocalSymbol,
|
||||||
)
|
)
|
||||||
|
from .function_debug_info import generate_function_debug_info
|
||||||
from .return_utils import handle_none_return, handle_xdp_return, is_xdp_name
|
from .return_utils import handle_none_return, handle_xdp_return, is_xdp_name
|
||||||
from .function_metadata import get_probe_string, is_global_function, infer_return_type
|
from .function_metadata import get_probe_string, is_global_function, infer_return_type
|
||||||
|
|
||||||
@ -402,7 +402,7 @@ def process_bpf_chunk(func_node, module, return_type, map_sym_tab, structs_sym_t
|
|||||||
func.linkage = "dso_local"
|
func.linkage = "dso_local"
|
||||||
func.attributes.add("nounwind")
|
func.attributes.add("nounwind")
|
||||||
func.attributes.add("noinline")
|
func.attributes.add("noinline")
|
||||||
func.attributes.add("optnone")
|
# func.attributes.add("optnone")
|
||||||
|
|
||||||
if func_node.args.args:
|
if func_node.args.args:
|
||||||
# Only look at the first argument for now
|
# Only look at the first argument for now
|
||||||
@ -440,7 +440,7 @@ def func_proc(tree, module, chunks, map_sym_tab, structs_sym_tab):
|
|||||||
func_type = get_probe_string(func_node)
|
func_type = get_probe_string(func_node)
|
||||||
logger.info(f"Found probe_string of {func_node.name}: {func_type}")
|
logger.info(f"Found probe_string of {func_node.name}: {func_type}")
|
||||||
|
|
||||||
process_bpf_chunk(
|
func = process_bpf_chunk(
|
||||||
func_node,
|
func_node,
|
||||||
module,
|
module,
|
||||||
ctypes_to_ir(infer_return_type(func_node)),
|
ctypes_to_ir(infer_return_type(func_node)),
|
||||||
@ -448,6 +448,9 @@ def func_proc(tree, module, chunks, map_sym_tab, structs_sym_tab):
|
|||||||
structs_sym_tab,
|
structs_sym_tab,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.info(f"Generating Debug Info for Function {func_node.name}")
|
||||||
|
generate_function_debug_info(func_node, module, func)
|
||||||
|
|
||||||
|
|
||||||
# TODO: WIP, for string assignment to fixed-size arrays
|
# TODO: WIP, for string assignment to fixed-size arrays
|
||||||
def assign_string_to_array(builder, target_array_ptr, source_string_ptr, array_length):
|
def assign_string_to_array(builder, target_array_ptr, source_string_ptr, array_length):
|
||||||
|
|||||||
@ -33,3 +33,4 @@ class AssignmentInfo:
|
|||||||
# Value is a tuple that contains the global variable representing that field
|
# Value is a tuple that contains the global variable representing that field
|
||||||
# along with all the information about that field as a Field type.
|
# along with all the information about that field as a Field type.
|
||||||
members: Optional[Dict[str, tuple[ir.GlobalVariable, Field]]] # For structs.
|
members: Optional[Dict[str, tuple[ir.GlobalVariable, Field]]] # For structs.
|
||||||
|
debug_info: Any
|
||||||
|
|||||||
@ -148,6 +148,7 @@ def process_vmlinux_assign(node, module, assignments: dict[str, AssignmentInfo])
|
|||||||
pointer_level=None,
|
pointer_level=None,
|
||||||
signature=None,
|
signature=None,
|
||||||
members=None,
|
members=None,
|
||||||
|
debug_info=None,
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Added assignment: {target_name} = {node.value.value!r} of type {type(node.value.value)}"
|
f"Added assignment: {target_name} = {node.value.value!r} of type {type(node.value.value)}"
|
||||||
|
|||||||
@ -73,9 +73,8 @@ class IRGenerator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Generate IR first to populate field names
|
# Generate IR first to populate field names
|
||||||
self.generated_debug_info.append(
|
struct_debug_info = self.gen_ir(struct, self.generated_debug_info)
|
||||||
(struct, self.gen_ir(struct, self.generated_debug_info))
|
self.generated_debug_info.append((struct, struct_debug_info))
|
||||||
)
|
|
||||||
|
|
||||||
# Fill the assignments dictionary with struct information
|
# Fill the assignments dictionary with struct information
|
||||||
if struct.name not in self.assignments:
|
if struct.name not in self.assignments:
|
||||||
@ -105,6 +104,7 @@ class IRGenerator:
|
|||||||
pointer_level=None,
|
pointer_level=None,
|
||||||
signature=None,
|
signature=None,
|
||||||
members=members_dict,
|
members=members_dict,
|
||||||
|
debug_info=struct_debug_info,
|
||||||
)
|
)
|
||||||
logger.info(f"Added struct assignment info for {struct.name}")
|
logger.info(f"Added struct assignment info for {struct.name}")
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from llvmlite import ir
|
from llvmlite import ir
|
||||||
|
|
||||||
from pythonbpf.local_symbol import LocalSymbol
|
from pythonbpf.local_symbol import LocalSymbol
|
||||||
@ -40,6 +42,15 @@ class VmlinuxHandler:
|
|||||||
and self.vmlinux_symtab[name].value_type == AssignmentType.CONSTANT
|
and self.vmlinux_symtab[name].value_type == AssignmentType.CONSTANT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_struct_debug_info(self, name: str) -> Any:
|
||||||
|
if (
|
||||||
|
name in self.vmlinux_symtab
|
||||||
|
and self.vmlinux_symtab[name].value_type == AssignmentType.STRUCT
|
||||||
|
):
|
||||||
|
return self.vmlinux_symtab[name].debug_info
|
||||||
|
else:
|
||||||
|
raise ValueError(f"{name} is not a vmlinux struct type")
|
||||||
|
|
||||||
def get_vmlinux_struct_type(self, name):
|
def get_vmlinux_struct_type(self, name):
|
||||||
"""Check if name is a vmlinux struct type"""
|
"""Check if name is a vmlinux struct type"""
|
||||||
if (
|
if (
|
||||||
@ -86,9 +97,11 @@ 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(8))
|
builder.function.args[0].type = ir.PointerType(ir.IntType(64))
|
||||||
print(builder.function.args[0])
|
print(builder.function.args[0])
|
||||||
field_ptr = self.load_ctx_field(builder, builder.function.args[0], globvar_ir)
|
field_ptr = self.load_ctx_field(
|
||||||
|
builder, builder.function.args[0], globvar_ir
|
||||||
|
)
|
||||||
print(field_ptr)
|
print(field_ptr)
|
||||||
# Return pointer to field and field type
|
# Return pointer to field and field type
|
||||||
return field_ptr, field_data
|
return field_ptr, field_data
|
||||||
@ -112,19 +125,19 @@ 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_type = ir.IntType(8)
|
||||||
i8_ptr_type = ir.PointerType(i8_type)
|
# 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_i8_ptr,
|
ctx_arg,
|
||||||
[offset],
|
[offset],
|
||||||
inbounds=False,
|
inbounds=False,
|
||||||
)
|
)
|
||||||
@ -133,24 +146,23 @@ class VmlinuxHandler:
|
|||||||
module = builder.function.module
|
module = builder.function.module
|
||||||
|
|
||||||
try:
|
try:
|
||||||
passthrough_fn = module.globals.get('llvm.bpf.passthrough.p0.p0')
|
passthrough_fn = module.globals.get("llvm.bpf.passthrough.p0.p0")
|
||||||
if passthrough_fn is None:
|
if passthrough_fn is None:
|
||||||
raise KeyError
|
raise KeyError
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError):
|
||||||
passthrough_type = ir.FunctionType(
|
passthrough_type = ir.FunctionType(
|
||||||
i8_ptr_type,
|
ir.PointerType(),
|
||||||
[ir.IntType(32), i8_ptr_type]
|
[ir.IntType(32), ir.PointerType()],
|
||||||
)
|
)
|
||||||
passthrough_fn = ir.Function(
|
passthrough_fn = ir.Function(
|
||||||
module,
|
module,
|
||||||
passthrough_type,
|
passthrough_type,
|
||||||
name='llvm.bpf.passthrough.p0.p0'
|
name="llvm.bpf.passthrough.p0.p0",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Call passthrough to satisfy BPF verifier
|
# Call passthrough to satisfy BPF verifier
|
||||||
verified_ptr = builder.call(
|
verified_ptr = builder.call(
|
||||||
passthrough_fn,
|
passthrough_fn, [ir.Constant(ir.IntType(32), 0), field_ptr], tail=True
|
||||||
[ir.Constant(ir.IntType(32), 0), field_ptr],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Bitcast to i64* (assuming field is 64-bit, adjust if needed)
|
# Bitcast to i64* (assuming field is 64-bit, adjust if needed)
|
||||||
@ -162,7 +174,6 @@ class VmlinuxHandler:
|
|||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def has_field(self, struct_name, field_name):
|
def has_field(self, struct_name, field_name):
|
||||||
"""Check if a vmlinux struct has a specific field"""
|
"""Check if a vmlinux struct has a specific field"""
|
||||||
if self.is_vmlinux_struct(struct_name):
|
if self.is_vmlinux_struct(struct_name):
|
||||||
|
|||||||
Reference in New Issue
Block a user