mirror of
https://github.com/varun-r-mallya/Python-BPF.git
synced 2025-12-31 21:06:25 +00:00
Compare commits
36 Commits
f7dee329cb
...
all_helper
| Author | SHA1 | Date | |
|---|---|---|---|
| cf99b3bb9a | |||
| 6c85b248ce | |||
| b5a3494cc6 | |||
| be62972974 | |||
| 2f4a7d2f90 | |||
| 3ccd3f767e | |||
| 2e37726922 | |||
| 5b36726b7d | |||
| 3e6cea2b67 | |||
| 338d4994d8 | |||
| 3078d4224d | |||
| 7d29790f00 | |||
| 963e2a8171 | |||
| 123a92af1d | |||
| 752f564d3f | |||
| d8cddb9799 | |||
| 33e18f6d6d | |||
| 5e371787eb | |||
| 67c9d9b932 | |||
| f757a32a63 | |||
| c5de92b9d0 | |||
| 4efd3223cd | |||
| 4884ed7577 | |||
| 5b7769dd38 | |||
| b7c1e92f05 | |||
| 8b28a927c3 | |||
| f9ee43e7ef | |||
| dabb8bf0df | |||
| 19dedede53 | |||
| 82cac8f8ef | |||
| 70a04f54d1 | |||
| ec2ea835e5 | |||
| 2257c175ed | |||
| 5bf60d69b8 | |||
| 0006e26b08 | |||
| 5cbd9a531e |
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1,3 +1 @@
|
|||||||
tests/c-form/vmlinux.h linguist-vendored
|
tests/c-form/vmlinux.h linguist-vendored
|
||||||
examples/ linguist-vendored
|
|
||||||
BCC-Examples/ linguist-vendored
|
|
||||||
|
|||||||
4
.github/workflows/python-publish.yml
vendored
4
.github/workflows/python-publish.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
|||||||
python -m build
|
python -m build
|
||||||
|
|
||||||
- name: Upload distributions
|
- name: Upload distributions
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: release-dists
|
name: release-dists
|
||||||
path: dist/
|
path: dist/
|
||||||
@ -59,7 +59,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Retrieve release distributions
|
- name: Retrieve release distributions
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: release-dists
|
name: release-dists
|
||||||
path: dist/
|
path: dist/
|
||||||
|
|||||||
@ -2,15 +2,27 @@ import ast
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from llvmlite import ir
|
from llvmlite import ir
|
||||||
from .local_symbol import LocalSymbol
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
from pythonbpf.helper import HelperHandlerRegistry
|
from pythonbpf.helper import HelperHandlerRegistry
|
||||||
from pythonbpf.vmlinux_parser.dependency_node import Field
|
|
||||||
from .expr import VmlinuxHandlerRegistry
|
from .expr import VmlinuxHandlerRegistry
|
||||||
from pythonbpf.type_deducer import ctypes_to_ir
|
from pythonbpf.type_deducer import ctypes_to_ir
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LocalSymbol:
|
||||||
|
var: ir.AllocaInstr
|
||||||
|
ir_type: ir.Type
|
||||||
|
metadata: Any = None
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield self.var
|
||||||
|
yield self.ir_type
|
||||||
|
yield self.metadata
|
||||||
|
|
||||||
|
|
||||||
def create_targets_and_rvals(stmt):
|
def create_targets_and_rvals(stmt):
|
||||||
"""Create lists of targets and right-hand values from an assignment statement."""
|
"""Create lists of targets and right-hand values from an assignment statement."""
|
||||||
if isinstance(stmt.targets[0], ast.Tuple):
|
if isinstance(stmt.targets[0], ast.Tuple):
|
||||||
@ -48,11 +60,21 @@ def handle_assign_allocation(builder, stmt, local_sym_tab, structs_sym_tab):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
var_name = target.id
|
var_name = target.id
|
||||||
|
|
||||||
# Skip if already allocated
|
# Skip if already allocated
|
||||||
if var_name in local_sym_tab:
|
if var_name in local_sym_tab:
|
||||||
logger.debug(f"Variable {var_name} already allocated, skipping")
|
logger.debug(f"Variable {var_name} already allocated, skipping")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# When allocating a variable, check if it's a vmlinux struct type
|
||||||
|
if isinstance(
|
||||||
|
stmt.value, ast.Name
|
||||||
|
) and VmlinuxHandlerRegistry.is_vmlinux_struct(stmt.value.id):
|
||||||
|
# Handle vmlinux struct allocation
|
||||||
|
# This requires more implementation
|
||||||
|
print(stmt.value)
|
||||||
|
pass
|
||||||
|
|
||||||
# Determine type and allocate based on rval
|
# Determine type and allocate based on rval
|
||||||
if isinstance(rval, ast.Call):
|
if isinstance(rval, ast.Call):
|
||||||
_allocate_for_call(builder, var_name, rval, local_sym_tab, structs_sym_tab)
|
_allocate_for_call(builder, var_name, rval, local_sym_tab, structs_sym_tab)
|
||||||
@ -177,17 +199,33 @@ def _allocate_for_binop(builder, var_name, local_sym_tab):
|
|||||||
logger.info(f"Pre-allocated {var_name} for binop result")
|
logger.info(f"Pre-allocated {var_name} for binop result")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_type_name(ir_type):
|
||||||
|
"""Get a string representation of an IR type."""
|
||||||
|
if isinstance(ir_type, ir.IntType):
|
||||||
|
return f"i{ir_type.width}"
|
||||||
|
elif isinstance(ir_type, ir.PointerType):
|
||||||
|
return "ptr"
|
||||||
|
elif isinstance(ir_type, ir.ArrayType):
|
||||||
|
return f"[{ir_type.count}x{_get_type_name(ir_type.element)}]"
|
||||||
|
else:
|
||||||
|
return str(ir_type).replace(" ", "")
|
||||||
|
|
||||||
|
|
||||||
def allocate_temp_pool(builder, max_temps, local_sym_tab):
|
def allocate_temp_pool(builder, max_temps, local_sym_tab):
|
||||||
"""Allocate the temporary scratch space pool for helper arguments."""
|
"""Allocate the temporary scratch space pool for helper arguments."""
|
||||||
if max_temps == 0:
|
if not max_temps:
|
||||||
|
logger.info("No temp pool allocation needed")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"Allocating temp pool of {max_temps} variables")
|
for tmp_type, cnt in max_temps.items():
|
||||||
for i in range(max_temps):
|
type_name = _get_type_name(tmp_type)
|
||||||
temp_name = f"__helper_temp_{i}"
|
logger.info(f"Allocating temp pool of {cnt} variables of type {type_name}")
|
||||||
temp_var = builder.alloca(ir.IntType(64), name=temp_name)
|
for i in range(cnt):
|
||||||
temp_var.align = 8
|
temp_name = f"__helper_temp_{type_name}_{i}"
|
||||||
local_sym_tab[temp_name] = LocalSymbol(temp_var, ir.IntType(64))
|
temp_var = builder.alloca(tmp_type, name=temp_name)
|
||||||
|
temp_var.align = _get_alignment(tmp_type)
|
||||||
|
local_sym_tab[temp_name] = LocalSymbol(temp_var, tmp_type)
|
||||||
|
logger.debug(f"Allocated temp variable: {temp_name}")
|
||||||
|
|
||||||
|
|
||||||
def _allocate_for_name(builder, var_name, rval, local_sym_tab):
|
def _allocate_for_name(builder, var_name, rval, local_sym_tab):
|
||||||
@ -226,41 +264,9 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_
|
|||||||
logger.error(f"Struct variable '{struct_var}' not found")
|
logger.error(f"Struct variable '{struct_var}' not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
struct_type: type = local_sym_tab[struct_var].metadata
|
struct_type = local_sym_tab[struct_var].metadata
|
||||||
if not struct_type or struct_type not in structs_sym_tab:
|
if not struct_type or struct_type not in structs_sym_tab:
|
||||||
if VmlinuxHandlerRegistry.is_vmlinux_struct(struct_type.__name__):
|
logger.error(f"Struct type '{struct_type}' not found")
|
||||||
# Handle vmlinux struct field access
|
|
||||||
vmlinux_struct_name = struct_type.__name__
|
|
||||||
if not VmlinuxHandlerRegistry.has_field(vmlinux_struct_name, field_name):
|
|
||||||
logger.error(
|
|
||||||
f"Field '{field_name}' not found in vmlinux struct '{vmlinux_struct_name}'"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
field_type: tuple[ir.GlobalVariable, Field] = (
|
|
||||||
VmlinuxHandlerRegistry.get_field_type(vmlinux_struct_name, field_name)
|
|
||||||
)
|
|
||||||
field_ir, field = field_type
|
|
||||||
# TODO: For now, we only support integer type allocations.
|
|
||||||
# This always assumes first argument of function to be the context struct
|
|
||||||
base_ptr = builder.function.args[0]
|
|
||||||
local_sym_tab[
|
|
||||||
struct_var
|
|
||||||
].var = base_ptr # This is repurposing of var to store the pointer of the base type
|
|
||||||
local_sym_tab[struct_var].ir_type = field_ir
|
|
||||||
|
|
||||||
actual_ir_type = ir.IntType(64)
|
|
||||||
|
|
||||||
# Allocate with the actual IR type, not the GlobalVariable
|
|
||||||
var = _allocate_with_type(builder, var_name, actual_ir_type)
|
|
||||||
local_sym_tab[var_name] = LocalSymbol(var, actual_ir_type, field)
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"Pre-allocated {var_name} from vmlinux struct {vmlinux_struct_name}.{field_name}"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
logger.error(f"Struct type '{struct_type}' not found")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
struct_info = structs_sym_tab[struct_type]
|
struct_info = structs_sym_tab[struct_type]
|
||||||
|
|||||||
@ -3,8 +3,6 @@ import logging
|
|||||||
from llvmlite import ir
|
from llvmlite import ir
|
||||||
from pythonbpf.expr import eval_expr
|
from pythonbpf.expr import eval_expr
|
||||||
from pythonbpf.helper import emit_probe_read_kernel_str_call
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -150,18 +148,7 @@ def handle_variable_assignment(
|
|||||||
val, val_type = val_result
|
val, val_type = val_result
|
||||||
logger.info(f"Evaluated value for {var_name}: {val} of type {val_type}, {var_type}")
|
logger.info(f"Evaluated value for {var_name}: {val} of type {val_type}, {var_type}")
|
||||||
if val_type != var_type:
|
if val_type != var_type:
|
||||||
if isinstance(val_type, Field):
|
if isinstance(val_type, ir.IntType) and isinstance(var_type, ir.IntType):
|
||||||
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
|
# Allow implicit int widening
|
||||||
if val_type.width < var_type.width:
|
if val_type.width < var_type.width:
|
||||||
val = builder.sext(val, var_type)
|
val = builder.sext(val, var_type)
|
||||||
|
|||||||
@ -37,24 +37,6 @@ def finalize_module(original_str):
|
|||||||
return re.sub(pattern, replacement, original_str)
|
return re.sub(pattern, replacement, original_str)
|
||||||
|
|
||||||
|
|
||||||
def bpf_passthrough_gen(module):
|
|
||||||
i32_ty = ir.IntType(32)
|
|
||||||
ptr_ty = ir.PointerType(ir.IntType(8))
|
|
||||||
fnty = ir.FunctionType(ptr_ty, [i32_ty, ptr_ty])
|
|
||||||
|
|
||||||
# Declare the intrinsic
|
|
||||||
passthrough = ir.Function(module, fnty, "llvm.bpf.passthrough.p0.p0")
|
|
||||||
|
|
||||||
# Set function attributes
|
|
||||||
# TODO: the ones commented are supposed to be there but cannot be added due to llvmlite limitations at the moment
|
|
||||||
# passthrough.attributes.add("nofree")
|
|
||||||
# passthrough.attributes.add("nosync")
|
|
||||||
passthrough.attributes.add("nounwind")
|
|
||||||
# passthrough.attributes.add("memory(none)")
|
|
||||||
|
|
||||||
return passthrough
|
|
||||||
|
|
||||||
|
|
||||||
def find_bpf_chunks(tree):
|
def find_bpf_chunks(tree):
|
||||||
"""Find all functions decorated with @bpf in the AST."""
|
"""Find all functions decorated with @bpf in the AST."""
|
||||||
bpf_functions = []
|
bpf_functions = []
|
||||||
@ -75,8 +57,6 @@ def processor(source_code, filename, module):
|
|||||||
for func_node in bpf_chunks:
|
for func_node in bpf_chunks:
|
||||||
logger.info(f"Found BPF function/struct: {func_node.name}")
|
logger.info(f"Found BPF function/struct: {func_node.name}")
|
||||||
|
|
||||||
bpf_passthrough_gen(module)
|
|
||||||
|
|
||||||
vmlinux_symtab = vmlinux_proc(tree, module)
|
vmlinux_symtab = vmlinux_proc(tree, module)
|
||||||
if vmlinux_symtab:
|
if vmlinux_symtab:
|
||||||
handler = VmlinuxHandler.initialize(vmlinux_symtab)
|
handler = VmlinuxHandler.initialize(vmlinux_symtab)
|
||||||
@ -157,7 +137,7 @@ 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 = finalize_module(str(module))
|
||||||
|
|
||||||
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:
|
||||||
|
|||||||
@ -184,83 +184,3 @@ 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,
|
|
||||||
)
|
|
||||||
|
|||||||
@ -72,28 +72,20 @@ def _handle_attribute_expr(
|
|||||||
if var_name in local_sym_tab:
|
if var_name in local_sym_tab:
|
||||||
var_ptr, var_type, var_metadata = local_sym_tab[var_name]
|
var_ptr, var_type, var_metadata = local_sym_tab[var_name]
|
||||||
logger.info(f"Loading attribute {attr_name} from variable {var_name}")
|
logger.info(f"Loading attribute {attr_name} from variable {var_name}")
|
||||||
logger.info(
|
logger.info(f"Variable type: {var_type}, Variable ptr: {var_ptr}")
|
||||||
f"Variable type: {var_type}, Variable ptr: {var_ptr}, Variable Metadata: {var_metadata}"
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
hasattr(var_metadata, "__module__")
|
|
||||||
and var_metadata.__module__ == "vmlinux"
|
|
||||||
):
|
|
||||||
# Try vmlinux handler when var_metadata is not a string, but has a module attribute.
|
|
||||||
# This has been done to keep everything separate in vmlinux struct handling.
|
|
||||||
vmlinux_result = VmlinuxHandlerRegistry.handle_attribute(
|
|
||||||
expr, local_sym_tab, None, builder
|
|
||||||
)
|
|
||||||
if vmlinux_result is not None:
|
|
||||||
return vmlinux_result
|
|
||||||
else:
|
|
||||||
raise RuntimeError("Vmlinux struct did not process successfully")
|
|
||||||
metadata = structs_sym_tab[var_metadata]
|
metadata = structs_sym_tab[var_metadata]
|
||||||
if attr_name in metadata.fields:
|
if attr_name in metadata.fields:
|
||||||
gep = metadata.gep(builder, var_ptr, attr_name)
|
gep = metadata.gep(builder, var_ptr, attr_name)
|
||||||
val = builder.load(gep)
|
val = builder.load(gep)
|
||||||
field_type = metadata.field_type(attr_name)
|
field_type = metadata.field_type(attr_name)
|
||||||
return val, field_type
|
return val, field_type
|
||||||
|
|
||||||
|
# Try vmlinux handler as fallback
|
||||||
|
vmlinux_result = VmlinuxHandlerRegistry.handle_attribute(
|
||||||
|
expr, local_sym_tab, None, builder
|
||||||
|
)
|
||||||
|
if vmlinux_result is not None:
|
||||||
|
return vmlinux_result
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import ast
|
import ast
|
||||||
|
|
||||||
from pythonbpf.vmlinux_parser.vmlinux_exports_handler import VmlinuxHandler
|
|
||||||
|
|
||||||
|
|
||||||
class VmlinuxHandlerRegistry:
|
class VmlinuxHandlerRegistry:
|
||||||
"""Registry for vmlinux handler operations"""
|
"""Registry for vmlinux handler operations"""
|
||||||
@ -9,7 +7,7 @@ class VmlinuxHandlerRegistry:
|
|||||||
_handler = None
|
_handler = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_handler(cls, handler: VmlinuxHandler):
|
def set_handler(cls, handler):
|
||||||
"""Set the vmlinux handler"""
|
"""Set the vmlinux handler"""
|
||||||
cls._handler = handler
|
cls._handler = handler
|
||||||
|
|
||||||
@ -39,37 +37,9 @@ 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"""
|
||||||
if cls._handler is None:
|
if cls._handler is None:
|
||||||
return False
|
return False
|
||||||
return cls._handler.is_vmlinux_struct(name)
|
return cls._handler.is_vmlinux_struct(name)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_struct_type(cls, name):
|
|
||||||
"""Try to handle a struct name as vmlinux struct"""
|
|
||||||
if cls._handler is None:
|
|
||||||
return None
|
|
||||||
return cls._handler.get_vmlinux_struct_type(name)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def has_field(cls, vmlinux_struct_name, field_name):
|
|
||||||
"""Check if a vmlinux struct has a specific field"""
|
|
||||||
if cls._handler is None:
|
|
||||||
return False
|
|
||||||
return cls._handler.has_field(vmlinux_struct_name, field_name)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_field_type(cls, vmlinux_struct_name, field_name):
|
|
||||||
"""Get the type of a field in a vmlinux struct"""
|
|
||||||
if cls._handler is None:
|
|
||||||
return None
|
|
||||||
assert isinstance(cls._handler, VmlinuxHandler)
|
|
||||||
return cls._handler.get_field_type(vmlinux_struct_name, field_name)
|
|
||||||
|
|||||||
@ -1,72 +0,0 @@
|
|||||||
import ast
|
|
||||||
|
|
||||||
import llvmlite.ir as ir
|
|
||||||
import logging
|
|
||||||
from pythonbpf.debuginfo import DebugInfoGenerator
|
|
||||||
from pythonbpf.expr import VmlinuxHandlerRegistry
|
|
||||||
import ctypes
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
annotation = leading_argument.annotation
|
|
||||||
if func_node.returns is None:
|
|
||||||
# TODO: should check if this logic is consistent with function return type handling elsewhere
|
|
||||||
return_type = ctypes.c_int64()
|
|
||||||
elif hasattr(func_node.returns, "id"):
|
|
||||||
return_type = func_node.returns.id
|
|
||||||
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}'")
|
|
||||||
@ -7,12 +7,7 @@ from pythonbpf.helper import (
|
|||||||
reset_scratch_pool,
|
reset_scratch_pool,
|
||||||
)
|
)
|
||||||
from pythonbpf.type_deducer import ctypes_to_ir
|
from pythonbpf.type_deducer import ctypes_to_ir
|
||||||
from pythonbpf.expr import (
|
from pythonbpf.expr import eval_expr, handle_expr, convert_to_bool
|
||||||
eval_expr,
|
|
||||||
handle_expr,
|
|
||||||
convert_to_bool,
|
|
||||||
VmlinuxHandlerRegistry,
|
|
||||||
)
|
|
||||||
from pythonbpf.assign_pass import (
|
from pythonbpf.assign_pass import (
|
||||||
handle_variable_assignment,
|
handle_variable_assignment,
|
||||||
handle_struct_field_assignment,
|
handle_struct_field_assignment,
|
||||||
@ -21,9 +16,8 @@ from pythonbpf.allocation_pass import (
|
|||||||
handle_assign_allocation,
|
handle_assign_allocation,
|
||||||
allocate_temp_pool,
|
allocate_temp_pool,
|
||||||
create_targets_and_rvals,
|
create_targets_and_rvals,
|
||||||
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
|
||||||
|
|
||||||
@ -39,7 +33,7 @@ logger = logging.getLogger(__name__)
|
|||||||
def count_temps_in_call(call_node, local_sym_tab):
|
def count_temps_in_call(call_node, local_sym_tab):
|
||||||
"""Count the number of temporary variables needed for a function call."""
|
"""Count the number of temporary variables needed for a function call."""
|
||||||
|
|
||||||
count = 0
|
count = {}
|
||||||
is_helper = False
|
is_helper = False
|
||||||
|
|
||||||
# NOTE: We exclude print calls for now
|
# NOTE: We exclude print calls for now
|
||||||
@ -49,21 +43,28 @@ def count_temps_in_call(call_node, local_sym_tab):
|
|||||||
and call_node.func.id != "print"
|
and call_node.func.id != "print"
|
||||||
):
|
):
|
||||||
is_helper = True
|
is_helper = True
|
||||||
|
func_name = call_node.func.id
|
||||||
elif isinstance(call_node.func, ast.Attribute):
|
elif isinstance(call_node.func, ast.Attribute):
|
||||||
if HelperHandlerRegistry.has_handler(call_node.func.attr):
|
if HelperHandlerRegistry.has_handler(call_node.func.attr):
|
||||||
is_helper = True
|
is_helper = True
|
||||||
|
func_name = call_node.func.attr
|
||||||
|
|
||||||
if not is_helper:
|
if not is_helper:
|
||||||
return 0
|
return {} # No temps needed
|
||||||
|
|
||||||
for arg in call_node.args:
|
for arg_idx in range(len(call_node.args)):
|
||||||
# NOTE: Count all non-name arguments
|
# NOTE: Count all non-name arguments
|
||||||
# For struct fields, if it is being passed as an argument,
|
# For struct fields, if it is being passed as an argument,
|
||||||
# The struct object should already exist in the local_sym_tab
|
# The struct object should already exist in the local_sym_tab
|
||||||
if not isinstance(arg, ast.Name) and not (
|
arg = call_node.args[arg_idx]
|
||||||
|
if isinstance(arg, ast.Name) or (
|
||||||
isinstance(arg, ast.Attribute) and arg.value.id in local_sym_tab
|
isinstance(arg, ast.Attribute) and arg.value.id in local_sym_tab
|
||||||
):
|
):
|
||||||
count += 1
|
continue
|
||||||
|
param_type = HelperHandlerRegistry.get_param_type(func_name, arg_idx)
|
||||||
|
if isinstance(param_type, ir.PointerType):
|
||||||
|
pointee_type = param_type.pointee
|
||||||
|
count[pointee_type] = count.get(pointee_type, 0) + 1
|
||||||
|
|
||||||
return count
|
return count
|
||||||
|
|
||||||
@ -99,11 +100,15 @@ def handle_if_allocation(
|
|||||||
def allocate_mem(
|
def allocate_mem(
|
||||||
module, builder, body, func, ret_type, map_sym_tab, local_sym_tab, structs_sym_tab
|
module, builder, body, func, ret_type, map_sym_tab, local_sym_tab, structs_sym_tab
|
||||||
):
|
):
|
||||||
max_temps_needed = 0
|
max_temps_needed = {}
|
||||||
|
|
||||||
|
def merge_type_counts(count_dict):
|
||||||
|
nonlocal max_temps_needed
|
||||||
|
for typ, cnt in count_dict.items():
|
||||||
|
max_temps_needed[typ] = max(max_temps_needed.get(typ, 0), cnt)
|
||||||
|
|
||||||
def update_max_temps_for_stmt(stmt):
|
def update_max_temps_for_stmt(stmt):
|
||||||
nonlocal max_temps_needed
|
nonlocal max_temps_needed
|
||||||
temps_needed = 0
|
|
||||||
|
|
||||||
if isinstance(stmt, ast.If):
|
if isinstance(stmt, ast.If):
|
||||||
for s in stmt.body:
|
for s in stmt.body:
|
||||||
@ -112,10 +117,13 @@ def allocate_mem(
|
|||||||
update_max_temps_for_stmt(s)
|
update_max_temps_for_stmt(s)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
stmt_temps = {}
|
||||||
for node in ast.walk(stmt):
|
for node in ast.walk(stmt):
|
||||||
if isinstance(node, ast.Call):
|
if isinstance(node, ast.Call):
|
||||||
temps_needed += count_temps_in_call(node, local_sym_tab)
|
call_temps = count_temps_in_call(node, local_sym_tab)
|
||||||
max_temps_needed = max(max_temps_needed, temps_needed)
|
for typ, cnt in call_temps.items():
|
||||||
|
stmt_temps[typ] = stmt_temps.get(typ, 0) + cnt
|
||||||
|
merge_type_counts(stmt_temps)
|
||||||
|
|
||||||
for stmt in body:
|
for stmt in body:
|
||||||
update_max_temps_for_stmt(stmt)
|
update_max_temps_for_stmt(stmt)
|
||||||
@ -330,28 +338,6 @@ def process_func_body(
|
|||||||
|
|
||||||
local_sym_tab = {}
|
local_sym_tab = {}
|
||||||
|
|
||||||
# Add the context parameter (first function argument) to the local symbol table
|
|
||||||
if func_node.args.args and len(func_node.args.args) > 0:
|
|
||||||
context_arg = func_node.args.args[0]
|
|
||||||
context_name = context_arg.arg
|
|
||||||
|
|
||||||
if hasattr(context_arg, "annotation") and context_arg.annotation:
|
|
||||||
if isinstance(context_arg.annotation, ast.Name):
|
|
||||||
context_type_name = context_arg.annotation.id
|
|
||||||
elif isinstance(context_arg.annotation, ast.Attribute):
|
|
||||||
context_type_name = context_arg.annotation.attr
|
|
||||||
else:
|
|
||||||
raise TypeError(
|
|
||||||
f"Unsupported annotation type: {ast.dump(context_arg.annotation)}"
|
|
||||||
)
|
|
||||||
if VmlinuxHandlerRegistry.is_vmlinux_struct(context_type_name):
|
|
||||||
resolved_type = VmlinuxHandlerRegistry.get_struct_type(
|
|
||||||
context_type_name
|
|
||||||
)
|
|
||||||
context_type = LocalSymbol(None, None, resolved_type)
|
|
||||||
local_sym_tab[context_name] = context_type
|
|
||||||
logger.info(f"Added argument '{context_name}' to local symbol table")
|
|
||||||
|
|
||||||
# pre-allocate dynamic variables
|
# pre-allocate dynamic variables
|
||||||
local_sym_tab = allocate_mem(
|
local_sym_tab = allocate_mem(
|
||||||
module,
|
module,
|
||||||
@ -402,7 +388,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 +426,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}")
|
||||||
|
|
||||||
func = process_bpf_chunk(
|
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,9 +434,6 @@ 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):
|
||||||
|
|||||||
@ -1,7 +1,20 @@
|
|||||||
from .helper_registry import HelperHandlerRegistry
|
from .helper_registry import HelperHandlerRegistry
|
||||||
from .helper_utils import reset_scratch_pool
|
from .helper_utils import reset_scratch_pool
|
||||||
from .bpf_helper_handler import handle_helper_call, emit_probe_read_kernel_str_call
|
from .bpf_helper_handler import handle_helper_call, emit_probe_read_kernel_str_call
|
||||||
from .helpers import ktime, pid, deref, comm, probe_read_str, XDP_DROP, XDP_PASS
|
from .helpers import (
|
||||||
|
ktime,
|
||||||
|
pid,
|
||||||
|
deref,
|
||||||
|
comm,
|
||||||
|
probe_read_str,
|
||||||
|
random,
|
||||||
|
probe_read,
|
||||||
|
smp_processor_id,
|
||||||
|
uid,
|
||||||
|
skb_store_bytes,
|
||||||
|
XDP_DROP,
|
||||||
|
XDP_PASS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Register the helper handler with expr module
|
# Register the helper handler with expr module
|
||||||
@ -65,6 +78,11 @@ __all__ = [
|
|||||||
"deref",
|
"deref",
|
||||||
"comm",
|
"comm",
|
||||||
"probe_read_str",
|
"probe_read_str",
|
||||||
|
"random",
|
||||||
|
"probe_read",
|
||||||
|
"smp_processor_id",
|
||||||
|
"uid",
|
||||||
|
"skb_store_bytes",
|
||||||
"XDP_DROP",
|
"XDP_DROP",
|
||||||
"XDP_PASS",
|
"XDP_PASS",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -8,8 +8,8 @@ from .helper_utils import (
|
|||||||
get_flags_val,
|
get_flags_val,
|
||||||
get_data_ptr_and_size,
|
get_data_ptr_and_size,
|
||||||
get_buffer_ptr_and_size,
|
get_buffer_ptr_and_size,
|
||||||
get_char_array_ptr_and_size,
|
|
||||||
get_ptr_from_arg,
|
get_ptr_from_arg,
|
||||||
|
get_int_value_from_arg,
|
||||||
)
|
)
|
||||||
from .printk_formatter import simple_string_print, handle_fstring_print
|
from .printk_formatter import simple_string_print, handle_fstring_print
|
||||||
|
|
||||||
@ -23,15 +23,24 @@ class BPFHelperID(Enum):
|
|||||||
BPF_MAP_LOOKUP_ELEM = 1
|
BPF_MAP_LOOKUP_ELEM = 1
|
||||||
BPF_MAP_UPDATE_ELEM = 2
|
BPF_MAP_UPDATE_ELEM = 2
|
||||||
BPF_MAP_DELETE_ELEM = 3
|
BPF_MAP_DELETE_ELEM = 3
|
||||||
|
BPF_PROBE_READ = 4
|
||||||
BPF_KTIME_GET_NS = 5
|
BPF_KTIME_GET_NS = 5
|
||||||
BPF_PRINTK = 6
|
BPF_PRINTK = 6
|
||||||
|
BPF_GET_PRANDOM_U32 = 7
|
||||||
|
BPF_GET_SMP_PROCESSOR_ID = 8
|
||||||
|
BPF_SKB_STORE_BYTES = 9
|
||||||
BPF_GET_CURRENT_PID_TGID = 14
|
BPF_GET_CURRENT_PID_TGID = 14
|
||||||
|
BPF_GET_CURRENT_UID_GID = 15
|
||||||
BPF_GET_CURRENT_COMM = 16
|
BPF_GET_CURRENT_COMM = 16
|
||||||
BPF_PERF_EVENT_OUTPUT = 25
|
BPF_PERF_EVENT_OUTPUT = 25
|
||||||
BPF_PROBE_READ_KERNEL_STR = 115
|
BPF_PROBE_READ_KERNEL_STR = 115
|
||||||
|
|
||||||
|
|
||||||
@HelperHandlerRegistry.register("ktime")
|
@HelperHandlerRegistry.register(
|
||||||
|
"ktime",
|
||||||
|
param_types=[],
|
||||||
|
return_type=ir.IntType(64),
|
||||||
|
)
|
||||||
def bpf_ktime_get_ns_emitter(
|
def bpf_ktime_get_ns_emitter(
|
||||||
call,
|
call,
|
||||||
map_ptr,
|
map_ptr,
|
||||||
@ -54,7 +63,11 @@ def bpf_ktime_get_ns_emitter(
|
|||||||
return result, ir.IntType(64)
|
return result, ir.IntType(64)
|
||||||
|
|
||||||
|
|
||||||
@HelperHandlerRegistry.register("lookup")
|
@HelperHandlerRegistry.register(
|
||||||
|
"lookup",
|
||||||
|
param_types=[ir.PointerType(ir.IntType(64))],
|
||||||
|
return_type=ir.PointerType(ir.IntType(64)),
|
||||||
|
)
|
||||||
def bpf_map_lookup_elem_emitter(
|
def bpf_map_lookup_elem_emitter(
|
||||||
call,
|
call,
|
||||||
map_ptr,
|
map_ptr,
|
||||||
@ -78,9 +91,9 @@ def bpf_map_lookup_elem_emitter(
|
|||||||
map_void_ptr = builder.bitcast(map_ptr, ir.PointerType())
|
map_void_ptr = builder.bitcast(map_ptr, ir.PointerType())
|
||||||
|
|
||||||
# TODO: I have changed the return type to i64*, as we are
|
# TODO: I have changed the return type to i64*, as we are
|
||||||
# allocating space for that type in allocate_mem. This is
|
# allocating space for that type in allocate_mem. This is
|
||||||
# temporary, and we will honour other widths later. But this
|
# temporary, and we will honour other widths later. But this
|
||||||
# allows us to have cool binary ops on the returned value.
|
# allows us to have cool binary ops on the returned value.
|
||||||
fn_type = ir.FunctionType(
|
fn_type = ir.FunctionType(
|
||||||
ir.PointerType(ir.IntType(64)), # Return type: void*
|
ir.PointerType(ir.IntType(64)), # Return type: void*
|
||||||
[ir.PointerType(), ir.PointerType()], # Args: (void*, void*)
|
[ir.PointerType(), ir.PointerType()], # Args: (void*, void*)
|
||||||
@ -96,6 +109,7 @@ def bpf_map_lookup_elem_emitter(
|
|||||||
return result, ir.PointerType()
|
return result, ir.PointerType()
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: This has special handling so we won't reflect the signature here.
|
||||||
@HelperHandlerRegistry.register("print")
|
@HelperHandlerRegistry.register("print")
|
||||||
def bpf_printk_emitter(
|
def bpf_printk_emitter(
|
||||||
call,
|
call,
|
||||||
@ -144,7 +158,15 @@ def bpf_printk_emitter(
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@HelperHandlerRegistry.register("update")
|
@HelperHandlerRegistry.register(
|
||||||
|
"update",
|
||||||
|
param_types=[
|
||||||
|
ir.PointerType(ir.IntType(64)),
|
||||||
|
ir.PointerType(ir.IntType(64)),
|
||||||
|
ir.IntType(64),
|
||||||
|
],
|
||||||
|
return_type=ir.PointerType(ir.IntType(64)),
|
||||||
|
)
|
||||||
def bpf_map_update_elem_emitter(
|
def bpf_map_update_elem_emitter(
|
||||||
call,
|
call,
|
||||||
map_ptr,
|
map_ptr,
|
||||||
@ -199,7 +221,11 @@ def bpf_map_update_elem_emitter(
|
|||||||
return result, None
|
return result, None
|
||||||
|
|
||||||
|
|
||||||
@HelperHandlerRegistry.register("delete")
|
@HelperHandlerRegistry.register(
|
||||||
|
"delete",
|
||||||
|
param_types=[ir.PointerType(ir.IntType(64))],
|
||||||
|
return_type=ir.PointerType(ir.IntType(64)),
|
||||||
|
)
|
||||||
def bpf_map_delete_elem_emitter(
|
def bpf_map_delete_elem_emitter(
|
||||||
call,
|
call,
|
||||||
map_ptr,
|
map_ptr,
|
||||||
@ -239,7 +265,11 @@ def bpf_map_delete_elem_emitter(
|
|||||||
return result, None
|
return result, None
|
||||||
|
|
||||||
|
|
||||||
@HelperHandlerRegistry.register("comm")
|
@HelperHandlerRegistry.register(
|
||||||
|
"comm",
|
||||||
|
param_types=[ir.PointerType(ir.IntType(8))],
|
||||||
|
return_type=ir.IntType(64),
|
||||||
|
)
|
||||||
def bpf_get_current_comm_emitter(
|
def bpf_get_current_comm_emitter(
|
||||||
call,
|
call,
|
||||||
map_ptr,
|
map_ptr,
|
||||||
@ -296,7 +326,11 @@ def bpf_get_current_comm_emitter(
|
|||||||
return result, None
|
return result, None
|
||||||
|
|
||||||
|
|
||||||
@HelperHandlerRegistry.register("pid")
|
@HelperHandlerRegistry.register(
|
||||||
|
"pid",
|
||||||
|
param_types=[],
|
||||||
|
return_type=ir.IntType(64),
|
||||||
|
)
|
||||||
def bpf_get_current_pid_tgid_emitter(
|
def bpf_get_current_pid_tgid_emitter(
|
||||||
call,
|
call,
|
||||||
map_ptr,
|
map_ptr,
|
||||||
@ -318,12 +352,17 @@ def bpf_get_current_pid_tgid_emitter(
|
|||||||
result = builder.call(fn_ptr, [], tail=False)
|
result = builder.call(fn_ptr, [], tail=False)
|
||||||
|
|
||||||
# Extract the lower 32 bits (PID) using bitwise AND with 0xFFFFFFFF
|
# Extract the lower 32 bits (PID) using bitwise AND with 0xFFFFFFFF
|
||||||
|
# TODO: return both PID and TGID if we end up needing TGID somewhere
|
||||||
mask = ir.Constant(ir.IntType(64), 0xFFFFFFFF)
|
mask = ir.Constant(ir.IntType(64), 0xFFFFFFFF)
|
||||||
pid = builder.and_(result, mask)
|
pid = builder.and_(result, mask)
|
||||||
return pid, ir.IntType(64)
|
return pid, ir.IntType(64)
|
||||||
|
|
||||||
|
|
||||||
@HelperHandlerRegistry.register("output")
|
@HelperHandlerRegistry.register(
|
||||||
|
"output",
|
||||||
|
param_types=[ir.PointerType(ir.IntType(8))],
|
||||||
|
return_type=ir.IntType(64),
|
||||||
|
)
|
||||||
def bpf_perf_event_output_handler(
|
def bpf_perf_event_output_handler(
|
||||||
call,
|
call,
|
||||||
map_ptr,
|
map_ptr,
|
||||||
@ -398,7 +437,14 @@ def emit_probe_read_kernel_str_call(builder, dst_ptr, dst_size, src_ptr):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@HelperHandlerRegistry.register("probe_read_str")
|
@HelperHandlerRegistry.register(
|
||||||
|
"probe_read_str",
|
||||||
|
param_types=[
|
||||||
|
ir.PointerType(ir.IntType(8)),
|
||||||
|
ir.PointerType(ir.IntType(8)),
|
||||||
|
],
|
||||||
|
return_type=ir.IntType(64),
|
||||||
|
)
|
||||||
def bpf_probe_read_kernel_str_emitter(
|
def bpf_probe_read_kernel_str_emitter(
|
||||||
call,
|
call,
|
||||||
map_ptr,
|
map_ptr,
|
||||||
@ -417,8 +463,8 @@ def bpf_probe_read_kernel_str_emitter(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Get destination buffer (char array -> i8*)
|
# Get destination buffer (char array -> i8*)
|
||||||
dst_ptr, dst_size = get_char_array_ptr_and_size(
|
dst_ptr, dst_size = get_or_create_ptr_from_arg(
|
||||||
call.args[0], builder, local_sym_tab, struct_sym_tab
|
func, module, call.args[0], builder, local_sym_tab, map_sym_tab, struct_sym_tab
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get source pointer (evaluate expression)
|
# Get source pointer (evaluate expression)
|
||||||
@ -433,6 +479,263 @@ def bpf_probe_read_kernel_str_emitter(
|
|||||||
return result, ir.IntType(64)
|
return result, ir.IntType(64)
|
||||||
|
|
||||||
|
|
||||||
|
@HelperHandlerRegistry.register(
|
||||||
|
"random",
|
||||||
|
param_types=[],
|
||||||
|
return_type=ir.IntType(32),
|
||||||
|
)
|
||||||
|
def bpf_get_prandom_u32_emitter(
|
||||||
|
call,
|
||||||
|
map_ptr,
|
||||||
|
module,
|
||||||
|
builder,
|
||||||
|
func,
|
||||||
|
local_sym_tab=None,
|
||||||
|
struct_sym_tab=None,
|
||||||
|
map_sym_tab=None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Emit LLVM IR for bpf_get_prandom_u32 helper function call.
|
||||||
|
"""
|
||||||
|
helper_id = ir.Constant(ir.IntType(64), BPFHelperID.BPF_GET_PRANDOM_U32.value)
|
||||||
|
fn_type = ir.FunctionType(ir.IntType(32), [], var_arg=False)
|
||||||
|
fn_ptr_type = ir.PointerType(fn_type)
|
||||||
|
fn_ptr = builder.inttoptr(helper_id, fn_ptr_type)
|
||||||
|
result = builder.call(fn_ptr, [], tail=False)
|
||||||
|
return result, ir.IntType(32)
|
||||||
|
|
||||||
|
|
||||||
|
@HelperHandlerRegistry.register(
|
||||||
|
"probe_read",
|
||||||
|
param_types=[
|
||||||
|
ir.PointerType(ir.IntType(8)),
|
||||||
|
ir.IntType(32),
|
||||||
|
ir.PointerType(ir.IntType(8)),
|
||||||
|
],
|
||||||
|
return_type=ir.IntType(64),
|
||||||
|
)
|
||||||
|
def bpf_probe_read_emitter(
|
||||||
|
call,
|
||||||
|
map_ptr,
|
||||||
|
module,
|
||||||
|
builder,
|
||||||
|
func,
|
||||||
|
local_sym_tab=None,
|
||||||
|
struct_sym_tab=None,
|
||||||
|
map_sym_tab=None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Emit LLVM IR for bpf_probe_read helper function
|
||||||
|
"""
|
||||||
|
|
||||||
|
if len(call.args) != 3:
|
||||||
|
logger.warn("Expected 3 args for probe_read helper")
|
||||||
|
return
|
||||||
|
dst_ptr = get_or_create_ptr_from_arg(
|
||||||
|
func,
|
||||||
|
module,
|
||||||
|
call.args[0],
|
||||||
|
builder,
|
||||||
|
local_sym_tab,
|
||||||
|
map_sym_tab,
|
||||||
|
struct_sym_tab,
|
||||||
|
ir.IntType(8),
|
||||||
|
)
|
||||||
|
size_val = get_int_value_from_arg(
|
||||||
|
call.args[1],
|
||||||
|
func,
|
||||||
|
module,
|
||||||
|
builder,
|
||||||
|
local_sym_tab,
|
||||||
|
map_sym_tab,
|
||||||
|
struct_sym_tab,
|
||||||
|
)
|
||||||
|
src_ptr = get_or_create_ptr_from_arg(
|
||||||
|
func,
|
||||||
|
module,
|
||||||
|
call.args[2],
|
||||||
|
builder,
|
||||||
|
local_sym_tab,
|
||||||
|
map_sym_tab,
|
||||||
|
struct_sym_tab,
|
||||||
|
ir.IntType(8),
|
||||||
|
)
|
||||||
|
fn_type = ir.FunctionType(
|
||||||
|
ir.IntType(64),
|
||||||
|
[ir.PointerType(), ir.IntType(32), ir.PointerType()],
|
||||||
|
var_arg=False,
|
||||||
|
)
|
||||||
|
fn_ptr = builder.inttoptr(
|
||||||
|
ir.Constant(ir.IntType(64), BPFHelperID.BPF_PROBE_READ.value),
|
||||||
|
ir.PointerType(fn_type),
|
||||||
|
)
|
||||||
|
result = builder.call(
|
||||||
|
fn_ptr,
|
||||||
|
[
|
||||||
|
builder.bitcast(dst_ptr, ir.PointerType()),
|
||||||
|
builder.trunc(size_val, ir.IntType(32)),
|
||||||
|
builder.bitcast(src_ptr, ir.PointerType()),
|
||||||
|
],
|
||||||
|
tail=False,
|
||||||
|
)
|
||||||
|
logger.info(f"Emitted bpf_probe_read (size={size_val})")
|
||||||
|
return result, ir.IntType(64)
|
||||||
|
|
||||||
|
|
||||||
|
@HelperHandlerRegistry.register(
|
||||||
|
"smp_processor_id",
|
||||||
|
param_types=[],
|
||||||
|
return_type=ir.IntType(32),
|
||||||
|
)
|
||||||
|
def bpf_get_smp_processor_id_emitter(
|
||||||
|
call,
|
||||||
|
map_ptr,
|
||||||
|
module,
|
||||||
|
builder,
|
||||||
|
func,
|
||||||
|
local_sym_tab=None,
|
||||||
|
struct_sym_tab=None,
|
||||||
|
map_sym_tab=None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Emit LLVM IR for bpf_get_smp_processor_id helper function call.
|
||||||
|
"""
|
||||||
|
helper_id = ir.Constant(ir.IntType(64), BPFHelperID.BPF_GET_SMP_PROCESSOR_ID.value)
|
||||||
|
fn_type = ir.FunctionType(ir.IntType(32), [], var_arg=False)
|
||||||
|
fn_ptr_type = ir.PointerType(fn_type)
|
||||||
|
fn_ptr = builder.inttoptr(helper_id, fn_ptr_type)
|
||||||
|
result = builder.call(fn_ptr, [], tail=False)
|
||||||
|
logger.info("Emitted bpf_get_smp_processor_id call")
|
||||||
|
return result, ir.IntType(32)
|
||||||
|
|
||||||
|
|
||||||
|
@HelperHandlerRegistry.register(
|
||||||
|
"uid",
|
||||||
|
param_types=[],
|
||||||
|
return_type=ir.IntType(64),
|
||||||
|
)
|
||||||
|
def bpf_get_current_uid_gid_emitter(
|
||||||
|
call,
|
||||||
|
map_ptr,
|
||||||
|
module,
|
||||||
|
builder,
|
||||||
|
func,
|
||||||
|
local_sym_tab=None,
|
||||||
|
struct_sym_tab=None,
|
||||||
|
map_sym_tab=None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Emit LLVM IR for bpf_get_current_uid_gid helper function call.
|
||||||
|
"""
|
||||||
|
helper_id = ir.Constant(ir.IntType(64), BPFHelperID.BPF_GET_CURRENT_UID_GID.value)
|
||||||
|
fn_type = ir.FunctionType(ir.IntType(64), [], var_arg=False)
|
||||||
|
fn_ptr_type = ir.PointerType(fn_type)
|
||||||
|
fn_ptr = builder.inttoptr(helper_id, fn_ptr_type)
|
||||||
|
result = builder.call(fn_ptr, [], tail=False)
|
||||||
|
|
||||||
|
# Extract the lower 32 bits (UID) using bitwise AND with 0xFFFFFFFF
|
||||||
|
# TODO: return both UID and GID if we end up needing GID somewhere
|
||||||
|
mask = ir.Constant(ir.IntType(64), 0xFFFFFFFF)
|
||||||
|
pid = builder.and_(result, mask)
|
||||||
|
return pid, ir.IntType(64)
|
||||||
|
|
||||||
|
|
||||||
|
@HelperHandlerRegistry.register(
|
||||||
|
"skb_store_bytes",
|
||||||
|
param_types=[
|
||||||
|
ir.IntType(32),
|
||||||
|
ir.PointerType(ir.IntType(8)),
|
||||||
|
ir.IntType(32),
|
||||||
|
ir.IntType(64),
|
||||||
|
],
|
||||||
|
return_type=ir.IntType(64),
|
||||||
|
)
|
||||||
|
def bpf_skb_store_bytes_emitter(
|
||||||
|
call,
|
||||||
|
map_ptr,
|
||||||
|
module,
|
||||||
|
builder,
|
||||||
|
func,
|
||||||
|
local_sym_tab=None,
|
||||||
|
struct_sym_tab=None,
|
||||||
|
map_sym_tab=None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Emit LLVM IR for bpf_skb_store_bytes helper function call.
|
||||||
|
Expected call signature: skb_store_bytes(skb, offset, from, len, flags)
|
||||||
|
"""
|
||||||
|
|
||||||
|
args_signature = [
|
||||||
|
ir.PointerType(), # skb pointer
|
||||||
|
ir.IntType(32), # offset
|
||||||
|
ir.PointerType(), # from
|
||||||
|
ir.IntType(32), # len
|
||||||
|
ir.IntType(64), # flags
|
||||||
|
]
|
||||||
|
|
||||||
|
if len(call.args) not in (3, 4):
|
||||||
|
raise ValueError(
|
||||||
|
f"skb_store_bytes expects 3 or 4 args (offset, from, len, flags), got {len(call.args)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
skb_ptr = func.args[0] # First argument to the function is skb
|
||||||
|
offset_val = get_int_value_from_arg(
|
||||||
|
call.args[0],
|
||||||
|
func,
|
||||||
|
module,
|
||||||
|
builder,
|
||||||
|
local_sym_tab,
|
||||||
|
map_sym_tab,
|
||||||
|
struct_sym_tab,
|
||||||
|
)
|
||||||
|
from_ptr = get_or_create_ptr_from_arg(
|
||||||
|
func,
|
||||||
|
module,
|
||||||
|
call.args[1],
|
||||||
|
builder,
|
||||||
|
local_sym_tab,
|
||||||
|
map_sym_tab,
|
||||||
|
struct_sym_tab,
|
||||||
|
args_signature[2],
|
||||||
|
)
|
||||||
|
len_val = get_int_value_from_arg(
|
||||||
|
call.args[2],
|
||||||
|
func,
|
||||||
|
module,
|
||||||
|
builder,
|
||||||
|
local_sym_tab,
|
||||||
|
map_sym_tab,
|
||||||
|
struct_sym_tab,
|
||||||
|
)
|
||||||
|
if len(call.args) == 4:
|
||||||
|
flags_val = get_flags_val(call.args[3], builder, local_sym_tab)
|
||||||
|
else:
|
||||||
|
flags_val = 0
|
||||||
|
flags = ir.Constant(ir.IntType(64), flags_val)
|
||||||
|
fn_type = ir.FunctionType(
|
||||||
|
ir.IntType(64),
|
||||||
|
args_signature,
|
||||||
|
var_arg=False,
|
||||||
|
)
|
||||||
|
fn_ptr = builder.inttoptr(
|
||||||
|
ir.Constant(ir.IntType(64), BPFHelperID.BPF_SKB_STORE_BYTES.value),
|
||||||
|
ir.PointerType(fn_type),
|
||||||
|
)
|
||||||
|
result = builder.call(
|
||||||
|
fn_ptr,
|
||||||
|
[
|
||||||
|
builder.bitcast(skb_ptr, ir.PointerType()),
|
||||||
|
builder.trunc(offset_val, ir.IntType(32)),
|
||||||
|
builder.bitcast(from_ptr, ir.PointerType()),
|
||||||
|
builder.trunc(len_val, ir.IntType(32)),
|
||||||
|
flags,
|
||||||
|
],
|
||||||
|
tail=False,
|
||||||
|
)
|
||||||
|
logger.info("Emitted bpf_skb_store_bytes call")
|
||||||
|
return result, ir.IntType(64)
|
||||||
|
|
||||||
|
|
||||||
def handle_helper_call(
|
def handle_helper_call(
|
||||||
call,
|
call,
|
||||||
module,
|
module,
|
||||||
|
|||||||
@ -1,17 +1,31 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from llvmlite import ir
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HelperSignature:
|
||||||
|
"""Signature of a BPF helper function"""
|
||||||
|
|
||||||
|
arg_types: list[ir.Type]
|
||||||
|
return_type: ir.Type
|
||||||
|
func: Callable
|
||||||
|
|
||||||
|
|
||||||
class HelperHandlerRegistry:
|
class HelperHandlerRegistry:
|
||||||
"""Registry for BPF helpers"""
|
"""Registry for BPF helpers"""
|
||||||
|
|
||||||
_handlers: dict[str, Callable] = {}
|
_handlers: dict[str, HelperSignature] = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register(cls, helper_name):
|
def register(cls, helper_name, param_types=None, return_type=None):
|
||||||
"""Decorator to register a handler function for a helper"""
|
"""Decorator to register a handler function for a helper"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
cls._handlers[helper_name] = func
|
helper_sig = HelperSignature(
|
||||||
|
arg_types=param_types, return_type=return_type, func=func
|
||||||
|
)
|
||||||
|
cls._handlers[helper_name] = helper_sig
|
||||||
return func
|
return func
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
@ -19,9 +33,29 @@ class HelperHandlerRegistry:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_handler(cls, helper_name):
|
def get_handler(cls, helper_name):
|
||||||
"""Get the handler function for a helper"""
|
"""Get the handler function for a helper"""
|
||||||
return cls._handlers.get(helper_name)
|
handler = cls._handlers.get(helper_name)
|
||||||
|
return handler.func if handler else None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def has_handler(cls, helper_name):
|
def has_handler(cls, helper_name):
|
||||||
"""Check if a handler function is registered for a helper"""
|
"""Check if a handler function is registered for a helper"""
|
||||||
return helper_name in cls._handlers
|
return helper_name in cls._handlers
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_signature(cls, helper_name):
|
||||||
|
"""Get the signature of a helper function"""
|
||||||
|
return cls._handlers.get(helper_name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_param_type(cls, helper_name, index):
|
||||||
|
"""Get the type of a parameter of a helper function by the index"""
|
||||||
|
signature = cls.get_signature(helper_name)
|
||||||
|
if signature and signature.arg_types and 0 <= index < len(signature.arg_types):
|
||||||
|
return signature.arg_types[index]
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_return_type(cls, helper_name):
|
||||||
|
"""Get the return type of a helper function"""
|
||||||
|
signature = cls.get_signature(helper_name)
|
||||||
|
return signature.return_type if signature else None
|
||||||
|
|||||||
@ -14,26 +14,43 @@ class ScratchPoolManager:
|
|||||||
"""Manage the temporary helper variables in local_sym_tab"""
|
"""Manage the temporary helper variables in local_sym_tab"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._counter = 0
|
self._counters = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def counter(self):
|
def counter(self):
|
||||||
return self._counter
|
return sum(self._counters.values())
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self._counter = 0
|
self._counters.clear()
|
||||||
logger.debug("Scratch pool counter reset to 0")
|
logger.debug("Scratch pool counter reset to 0")
|
||||||
|
|
||||||
def get_next_temp(self, local_sym_tab):
|
def _get_type_name(self, ir_type):
|
||||||
temp_name = f"__helper_temp_{self._counter}"
|
if isinstance(ir_type, ir.PointerType):
|
||||||
self._counter += 1
|
return "ptr"
|
||||||
|
elif isinstance(ir_type, ir.IntType):
|
||||||
|
return f"i{ir_type.width}"
|
||||||
|
elif isinstance(ir_type, ir.ArrayType):
|
||||||
|
return f"[{ir_type.count}x{self._get_type_name(ir_type.element)}]"
|
||||||
|
else:
|
||||||
|
return str(ir_type).replace(" ", "")
|
||||||
|
|
||||||
|
def get_next_temp(self, local_sym_tab, expected_type=None):
|
||||||
|
# Default to i64 if no expected type provided
|
||||||
|
type_name = self._get_type_name(expected_type) if expected_type else "i64"
|
||||||
|
if type_name not in self._counters:
|
||||||
|
self._counters[type_name] = 0
|
||||||
|
|
||||||
|
counter = self._counters[type_name]
|
||||||
|
temp_name = f"__helper_temp_{type_name}_{counter}"
|
||||||
|
self._counters[type_name] += 1
|
||||||
|
|
||||||
if temp_name not in local_sym_tab:
|
if temp_name not in local_sym_tab:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Scratch pool exhausted or inadequate: {temp_name}. "
|
f"Scratch pool exhausted or inadequate: {temp_name}. "
|
||||||
f"Current counter: {self._counter}"
|
f"Type: {type_name} Counter: {counter}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.debug(f"Using {temp_name} for type {type_name}")
|
||||||
return local_sym_tab[temp_name].var, temp_name
|
return local_sym_tab[temp_name].var, temp_name
|
||||||
|
|
||||||
|
|
||||||
@ -60,24 +77,73 @@ def get_var_ptr_from_name(var_name, local_sym_tab):
|
|||||||
def create_int_constant_ptr(value, builder, local_sym_tab, int_width=64):
|
def create_int_constant_ptr(value, builder, local_sym_tab, int_width=64):
|
||||||
"""Create a pointer to an integer constant."""
|
"""Create a pointer to an integer constant."""
|
||||||
|
|
||||||
# Default to 64-bit integer
|
int_type = ir.IntType(int_width)
|
||||||
ptr, temp_name = _temp_pool_manager.get_next_temp(local_sym_tab)
|
ptr, temp_name = _temp_pool_manager.get_next_temp(local_sym_tab, int_type)
|
||||||
logger.info(f"Using temp variable '{temp_name}' for int constant {value}")
|
logger.info(f"Using temp variable '{temp_name}' for int constant {value}")
|
||||||
const_val = ir.Constant(ir.IntType(int_width), value)
|
const_val = ir.Constant(int_type, value)
|
||||||
builder.store(const_val, ptr)
|
builder.store(const_val, ptr)
|
||||||
return ptr
|
return ptr
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_ptr_from_arg(
|
def get_or_create_ptr_from_arg(
|
||||||
func, module, arg, builder, local_sym_tab, map_sym_tab, struct_sym_tab=None
|
func,
|
||||||
|
module,
|
||||||
|
arg,
|
||||||
|
builder,
|
||||||
|
local_sym_tab,
|
||||||
|
map_sym_tab,
|
||||||
|
struct_sym_tab=None,
|
||||||
|
expected_type=None,
|
||||||
):
|
):
|
||||||
"""Extract or create pointer from the call arguments."""
|
"""Extract or create pointer from the call arguments."""
|
||||||
|
|
||||||
|
logger.info(f"Getting pointer from arg: {ast.dump(arg)}")
|
||||||
|
sz = None
|
||||||
if isinstance(arg, ast.Name):
|
if isinstance(arg, ast.Name):
|
||||||
|
# Stack space is already allocated
|
||||||
ptr = get_var_ptr_from_name(arg.id, local_sym_tab)
|
ptr = get_var_ptr_from_name(arg.id, local_sym_tab)
|
||||||
elif isinstance(arg, ast.Constant) and isinstance(arg.value, int):
|
elif isinstance(arg, ast.Constant) and isinstance(arg.value, int):
|
||||||
ptr = create_int_constant_ptr(arg.value, builder, local_sym_tab)
|
int_width = 64 # Default to i64
|
||||||
|
if expected_type and isinstance(expected_type, ir.IntType):
|
||||||
|
int_width = expected_type.width
|
||||||
|
ptr = create_int_constant_ptr(arg.value, builder, local_sym_tab, int_width)
|
||||||
|
elif isinstance(arg, ast.Attribute):
|
||||||
|
# A struct field
|
||||||
|
struct_name = arg.value.id
|
||||||
|
field_name = arg.attr
|
||||||
|
|
||||||
|
if not local_sym_tab or struct_name not in local_sym_tab:
|
||||||
|
raise ValueError(f"Struct '{struct_name}' not found")
|
||||||
|
|
||||||
|
struct_type = local_sym_tab[struct_name].metadata
|
||||||
|
if not struct_sym_tab or struct_type not in struct_sym_tab:
|
||||||
|
raise ValueError(f"Struct type '{struct_type}' not found")
|
||||||
|
|
||||||
|
struct_info = struct_sym_tab[struct_type]
|
||||||
|
if field_name not in struct_info.fields:
|
||||||
|
raise ValueError(
|
||||||
|
f"Field '{field_name}' not found in struct '{struct_name}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
field_type = struct_info.field_type(field_name)
|
||||||
|
struct_ptr = local_sym_tab[struct_name].var
|
||||||
|
|
||||||
|
# Special handling for char arrays
|
||||||
|
if (
|
||||||
|
isinstance(field_type, ir.ArrayType)
|
||||||
|
and isinstance(field_type.element, ir.IntType)
|
||||||
|
and field_type.element.width == 8
|
||||||
|
):
|
||||||
|
ptr, sz = get_char_array_ptr_and_size(
|
||||||
|
arg, builder, local_sym_tab, struct_sym_tab
|
||||||
|
)
|
||||||
|
if not ptr:
|
||||||
|
raise ValueError("Failed to get char array pointer from struct field")
|
||||||
|
else:
|
||||||
|
ptr = struct_info.gep(builder, struct_ptr, field_name)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
# NOTE: For any integer expression reaching this branch, it is probably a struct field or a binop
|
||||||
# Evaluate the expression and store the result in a temp variable
|
# Evaluate the expression and store the result in a temp variable
|
||||||
val = get_operand_value(
|
val = get_operand_value(
|
||||||
func, module, arg, builder, local_sym_tab, map_sym_tab, struct_sym_tab
|
func, module, arg, builder, local_sym_tab, map_sym_tab, struct_sym_tab
|
||||||
@ -85,13 +151,20 @@ def get_or_create_ptr_from_arg(
|
|||||||
if val is None:
|
if val is None:
|
||||||
raise ValueError("Failed to evaluate expression for helper arg.")
|
raise ValueError("Failed to evaluate expression for helper arg.")
|
||||||
|
|
||||||
# NOTE: We assume the result is an int64 for now
|
ptr, temp_name = _temp_pool_manager.get_next_temp(local_sym_tab, expected_type)
|
||||||
# if isinstance(arg, ast.Attribute):
|
|
||||||
# return val
|
|
||||||
ptr, temp_name = _temp_pool_manager.get_next_temp(local_sym_tab)
|
|
||||||
logger.info(f"Using temp variable '{temp_name}' for expression result")
|
logger.info(f"Using temp variable '{temp_name}' for expression result")
|
||||||
|
if (
|
||||||
|
isinstance(val.type, ir.IntType)
|
||||||
|
and expected_type
|
||||||
|
and val.type.width > expected_type.width
|
||||||
|
):
|
||||||
|
val = builder.trunc(val, expected_type)
|
||||||
builder.store(val, ptr)
|
builder.store(val, ptr)
|
||||||
|
|
||||||
|
# NOTE: For char arrays, also return size
|
||||||
|
if sz:
|
||||||
|
return ptr, sz
|
||||||
|
|
||||||
return ptr
|
return ptr
|
||||||
|
|
||||||
|
|
||||||
@ -274,3 +347,23 @@ def get_ptr_from_arg(
|
|||||||
raise ValueError(f"Expected pointer type, got {val_type}")
|
raise ValueError(f"Expected pointer type, got {val_type}")
|
||||||
|
|
||||||
return val, val_type
|
return val, val_type
|
||||||
|
|
||||||
|
|
||||||
|
def get_int_value_from_arg(
|
||||||
|
arg, func, module, builder, local_sym_tab, map_sym_tab, struct_sym_tab
|
||||||
|
):
|
||||||
|
"""Evaluate argument and return integer value"""
|
||||||
|
|
||||||
|
result = eval_expr(
|
||||||
|
func, module, builder, arg, local_sym_tab, map_sym_tab, struct_sym_tab
|
||||||
|
)
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
raise ValueError("Failed to evaluate argument")
|
||||||
|
|
||||||
|
val, val_type = result
|
||||||
|
|
||||||
|
if not isinstance(val_type, ir.IntType):
|
||||||
|
raise ValueError(f"Expected integer type, got {val_type}")
|
||||||
|
|
||||||
|
return val
|
||||||
|
|||||||
@ -27,6 +27,31 @@ def probe_read_str(dst, src):
|
|||||||
return ctypes.c_int64(0)
|
return ctypes.c_int64(0)
|
||||||
|
|
||||||
|
|
||||||
|
def random():
|
||||||
|
"""get a pseudorandom u32 number"""
|
||||||
|
return ctypes.c_int32(0)
|
||||||
|
|
||||||
|
|
||||||
|
def probe_read(dst, size, src):
|
||||||
|
"""Safely read data from kernel memory"""
|
||||||
|
return ctypes.c_int64(0)
|
||||||
|
|
||||||
|
|
||||||
|
def smp_processor_id():
|
||||||
|
"""get the current CPU id"""
|
||||||
|
return ctypes.c_int32(0)
|
||||||
|
|
||||||
|
|
||||||
|
def uid():
|
||||||
|
"""get current user id"""
|
||||||
|
return ctypes.c_int32(0)
|
||||||
|
|
||||||
|
|
||||||
|
def skb_store_bytes(offset, from_buf, size, flags=0):
|
||||||
|
"""store bytes into a socket buffer"""
|
||||||
|
return ctypes.c_int64(0)
|
||||||
|
|
||||||
|
|
||||||
XDP_ABORTED = ctypes.c_int64(0)
|
XDP_ABORTED = ctypes.c_int64(0)
|
||||||
XDP_DROP = ctypes.c_int64(1)
|
XDP_DROP = ctypes.c_int64(1)
|
||||||
XDP_PASS = ctypes.c_int64(2)
|
XDP_PASS = ctypes.c_int64(2)
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import logging
|
|||||||
from llvmlite import ir
|
from llvmlite import ir
|
||||||
from pythonbpf.expr import eval_expr, get_base_type_and_depth, deref_to_depth
|
from pythonbpf.expr import eval_expr, get_base_type_and_depth, deref_to_depth
|
||||||
from pythonbpf.expr.vmlinux_registry import VmlinuxHandlerRegistry
|
from pythonbpf.expr.vmlinux_registry import VmlinuxHandlerRegistry
|
||||||
|
from pythonbpf.helper.helper_utils import get_char_array_ptr_and_size
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -219,7 +220,7 @@ def _prepare_expr_args(expr, func, module, builder, local_sym_tab, struct_sym_ta
|
|||||||
"""Evaluate and prepare an expression to use as an arg for bpf_printk."""
|
"""Evaluate and prepare an expression to use as an arg for bpf_printk."""
|
||||||
|
|
||||||
# Special case: struct field char array needs pointer to first element
|
# Special case: struct field char array needs pointer to first element
|
||||||
char_array_ptr = _get_struct_char_array_ptr(
|
char_array_ptr, _ = get_char_array_ptr_and_size(
|
||||||
expr, builder, local_sym_tab, struct_sym_tab
|
expr, builder, local_sym_tab, struct_sym_tab
|
||||||
)
|
)
|
||||||
if char_array_ptr:
|
if char_array_ptr:
|
||||||
@ -242,52 +243,6 @@ def _prepare_expr_args(expr, func, module, builder, local_sym_tab, struct_sym_ta
|
|||||||
return ir.Constant(ir.IntType(64), 0)
|
return ir.Constant(ir.IntType(64), 0)
|
||||||
|
|
||||||
|
|
||||||
def _get_struct_char_array_ptr(expr, builder, local_sym_tab, struct_sym_tab):
|
|
||||||
"""Get pointer to first element of char array in struct field, or None."""
|
|
||||||
if not (isinstance(expr, ast.Attribute) and isinstance(expr.value, ast.Name)):
|
|
||||||
return None
|
|
||||||
|
|
||||||
var_name = expr.value.id
|
|
||||||
field_name = expr.attr
|
|
||||||
|
|
||||||
# Check if it's a valid struct field
|
|
||||||
if not (
|
|
||||||
local_sym_tab
|
|
||||||
and var_name in local_sym_tab
|
|
||||||
and struct_sym_tab
|
|
||||||
and local_sym_tab[var_name].metadata in struct_sym_tab
|
|
||||||
):
|
|
||||||
return None
|
|
||||||
|
|
||||||
struct_type = local_sym_tab[var_name].metadata
|
|
||||||
struct_info = struct_sym_tab[struct_type]
|
|
||||||
|
|
||||||
if field_name not in struct_info.fields:
|
|
||||||
return None
|
|
||||||
|
|
||||||
field_type = struct_info.field_type(field_name)
|
|
||||||
|
|
||||||
# Check if it's a char array
|
|
||||||
is_char_array = (
|
|
||||||
isinstance(field_type, ir.ArrayType)
|
|
||||||
and isinstance(field_type.element, ir.IntType)
|
|
||||||
and field_type.element.width == 8
|
|
||||||
)
|
|
||||||
|
|
||||||
if not is_char_array:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Get field pointer and GEP to first element: [N x i8]* -> i8*
|
|
||||||
struct_ptr = local_sym_tab[var_name].var
|
|
||||||
field_ptr = struct_info.gep(builder, struct_ptr, field_name)
|
|
||||||
|
|
||||||
return builder.gep(
|
|
||||||
field_ptr,
|
|
||||||
[ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)],
|
|
||||||
inbounds=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _handle_pointer_arg(val, func, builder):
|
def _handle_pointer_arg(val, func, builder):
|
||||||
"""Convert pointer type for bpf_printk."""
|
"""Convert pointer type for bpf_printk."""
|
||||||
target, depth = get_base_type_and_depth(val.type)
|
target, depth = get_base_type_and_depth(val.type)
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
import llvmlite.ir as ir
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class LocalSymbol:
|
|
||||||
var: ir.AllocaInstr
|
|
||||||
ir_type: ir.Type
|
|
||||||
metadata: Any = None
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
yield self.var
|
|
||||||
yield self.ir_type
|
|
||||||
yield self.metadata
|
|
||||||
@ -13,9 +13,6 @@ mapping = {
|
|||||||
"c_float": ir.FloatType(),
|
"c_float": ir.FloatType(),
|
||||||
"c_double": ir.DoubleType(),
|
"c_double": ir.DoubleType(),
|
||||||
"c_void_p": ir.IntType(64),
|
"c_void_p": ir.IntType(64),
|
||||||
"c_long": ir.IntType(64),
|
|
||||||
"c_ulong": ir.IntType(64),
|
|
||||||
"c_longlong": ir.IntType(64),
|
|
||||||
# Not so sure about this one
|
# Not so sure about this one
|
||||||
"str": ir.PointerType(ir.IntType(8)),
|
"str": ir.PointerType(ir.IntType(8)),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import llvmlite.ir as ir
|
|||||||
from pythonbpf.vmlinux_parser.dependency_node import Field
|
from pythonbpf.vmlinux_parser.dependency_node import Field
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class AssignmentType(Enum):
|
class AssignmentType(Enum):
|
||||||
CONSTANT = auto()
|
CONSTANT = auto()
|
||||||
STRUCT = auto()
|
STRUCT = auto()
|
||||||
@ -33,4 +34,3 @@ 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
|
|
||||||
|
|||||||
@ -16,37 +16,10 @@ def get_module_symbols(module_name: str):
|
|||||||
return [name for name in dir(imported_module)], imported_module
|
return [name for name in dir(imported_module)], imported_module
|
||||||
|
|
||||||
|
|
||||||
def unwrap_pointer_type(type_obj: Any) -> Any:
|
|
||||||
"""
|
|
||||||
Recursively unwrap all pointer layers to get the base type.
|
|
||||||
|
|
||||||
This handles multiply nested pointers like LP_LP_struct_attribute_group
|
|
||||||
and returns the base type (struct_attribute_group).
|
|
||||||
|
|
||||||
Stops unwrapping when reaching a non-pointer type (one without _type_ attribute).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
type_obj: The type object to unwrap
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The base type after unwrapping all pointer layers
|
|
||||||
"""
|
|
||||||
current_type = type_obj
|
|
||||||
# Keep unwrapping while it's a pointer/array type (has _type_)
|
|
||||||
# But stop if _type_ is just a string or basic type marker
|
|
||||||
while hasattr(current_type, "_type_"):
|
|
||||||
next_type = current_type._type_
|
|
||||||
# Stop if _type_ is a string (like 'c' for c_char)
|
|
||||||
if isinstance(next_type, str):
|
|
||||||
break
|
|
||||||
current_type = next_type
|
|
||||||
return current_type
|
|
||||||
|
|
||||||
|
|
||||||
def process_vmlinux_class(
|
def process_vmlinux_class(
|
||||||
node,
|
node,
|
||||||
llvm_module,
|
llvm_module,
|
||||||
handler: DependencyHandler,
|
handler: DependencyHandler,
|
||||||
):
|
):
|
||||||
symbols_in_module, imported_module = get_module_symbols("vmlinux")
|
symbols_in_module, imported_module = get_module_symbols("vmlinux")
|
||||||
if node.name in symbols_in_module:
|
if node.name in symbols_in_module:
|
||||||
@ -57,10 +30,10 @@ def process_vmlinux_class(
|
|||||||
|
|
||||||
|
|
||||||
def process_vmlinux_post_ast(
|
def process_vmlinux_post_ast(
|
||||||
elem_type_class,
|
elem_type_class,
|
||||||
llvm_handler,
|
llvm_handler,
|
||||||
handler: DependencyHandler,
|
handler: DependencyHandler,
|
||||||
processing_stack=None,
|
processing_stack=None,
|
||||||
):
|
):
|
||||||
# Initialize processing stack on first call
|
# Initialize processing stack on first call
|
||||||
if processing_stack is None:
|
if processing_stack is None:
|
||||||
@ -140,7 +113,7 @@ def process_vmlinux_post_ast(
|
|||||||
|
|
||||||
# Process pointer to ctype
|
# Process pointer to ctype
|
||||||
if isinstance(elem_type, type) and issubclass(
|
if isinstance(elem_type, type) and issubclass(
|
||||||
elem_type, ctypes._Pointer
|
elem_type, ctypes._Pointer
|
||||||
):
|
):
|
||||||
# Get the pointed-to type
|
# Get the pointed-to type
|
||||||
pointed_type = elem_type._type_
|
pointed_type = elem_type._type_
|
||||||
@ -153,7 +126,7 @@ def process_vmlinux_post_ast(
|
|||||||
|
|
||||||
# Process function pointers (CFUNCTYPE)
|
# Process function pointers (CFUNCTYPE)
|
||||||
elif hasattr(elem_type, "_restype_") and hasattr(
|
elif hasattr(elem_type, "_restype_") and hasattr(
|
||||||
elem_type, "_argtypes_"
|
elem_type, "_argtypes_"
|
||||||
):
|
):
|
||||||
# This is a CFUNCTYPE or similar
|
# This is a CFUNCTYPE or similar
|
||||||
logger.info(
|
logger.info(
|
||||||
@ -185,19 +158,13 @@ def process_vmlinux_post_ast(
|
|||||||
if hasattr(elem_type, "_length_") and is_complex_type:
|
if hasattr(elem_type, "_length_") and is_complex_type:
|
||||||
type_length = elem_type._length_
|
type_length = elem_type._length_
|
||||||
|
|
||||||
# Unwrap all pointer layers to get the base type for dependency tracking
|
if containing_type.__module__ == "vmlinux":
|
||||||
base_type = unwrap_pointer_type(elem_type)
|
new_dep_node.add_dependent(
|
||||||
base_type_module = getattr(base_type, "__module__", None)
|
elem_type._type_.__name__
|
||||||
|
if hasattr(elem_type._type_, "__name__")
|
||||||
if base_type_module == "vmlinux":
|
else str(elem_type._type_)
|
||||||
base_type_name = (
|
|
||||||
base_type.__name__
|
|
||||||
if hasattr(base_type, "__name__")
|
|
||||||
else str(base_type)
|
|
||||||
)
|
)
|
||||||
new_dep_node.add_dependent(base_type_name)
|
elif containing_type.__module__ == ctypes.__name__:
|
||||||
elif base_type_module == ctypes.__name__ or base_type_module is None:
|
|
||||||
# Handle ctypes or types with no module (like some internal ctypes types)
|
|
||||||
if isinstance(elem_type, type):
|
if isinstance(elem_type, type):
|
||||||
if issubclass(elem_type, ctypes.Array):
|
if issubclass(elem_type, ctypes.Array):
|
||||||
ctype_complex_type = ctypes.Array
|
ctype_complex_type = ctypes.Array
|
||||||
@ -211,7 +178,7 @@ def process_vmlinux_post_ast(
|
|||||||
raise TypeError("Unsupported ctypes subclass")
|
raise TypeError("Unsupported ctypes subclass")
|
||||||
else:
|
else:
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
f"Unsupported module of {base_type}: {base_type_module}"
|
f"Unsupported module of {containing_type}"
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{containing_type} containing type of parent {elem_name} with {elem_type} and ctype {ctype_complex_type} and length {type_length}"
|
f"{containing_type} containing type of parent {elem_name} with {elem_type} and ctype {ctype_complex_type} and length {type_length}"
|
||||||
@ -224,16 +191,11 @@ def process_vmlinux_post_ast(
|
|||||||
elem_name, ctype_complex_type
|
elem_name, ctype_complex_type
|
||||||
)
|
)
|
||||||
new_dep_node.set_field_type(elem_name, elem_type)
|
new_dep_node.set_field_type(elem_name, elem_type)
|
||||||
|
if containing_type.__module__ == "vmlinux":
|
||||||
# Check the containing_type module to decide whether to recurse
|
|
||||||
containing_type_module = getattr(containing_type, "__module__", None)
|
|
||||||
if containing_type_module == "vmlinux":
|
|
||||||
# Also unwrap containing_type to get base type name
|
|
||||||
base_containing_type = unwrap_pointer_type(containing_type)
|
|
||||||
containing_type_name = (
|
containing_type_name = (
|
||||||
base_containing_type.__name__
|
containing_type.__name__
|
||||||
if hasattr(base_containing_type, "__name__")
|
if hasattr(containing_type, "__name__")
|
||||||
else str(base_containing_type)
|
else str(containing_type)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check for self-reference or already processed
|
# Check for self-reference or already processed
|
||||||
@ -250,21 +212,21 @@ def process_vmlinux_post_ast(
|
|||||||
)
|
)
|
||||||
new_dep_node.set_field_ready(elem_name, True)
|
new_dep_node.set_field_ready(elem_name, True)
|
||||||
else:
|
else:
|
||||||
# Process recursively - use base containing type, not the pointer wrapper
|
# Process recursively - THIS WAS MISSING
|
||||||
new_dep_node.add_dependent(containing_type_name)
|
new_dep_node.add_dependent(containing_type_name)
|
||||||
process_vmlinux_post_ast(
|
process_vmlinux_post_ast(
|
||||||
base_containing_type,
|
containing_type,
|
||||||
llvm_handler,
|
llvm_handler,
|
||||||
handler,
|
handler,
|
||||||
processing_stack,
|
processing_stack,
|
||||||
)
|
)
|
||||||
new_dep_node.set_field_ready(elem_name, True)
|
new_dep_node.set_field_ready(elem_name, True)
|
||||||
elif containing_type_module == ctypes.__name__ or containing_type_module is None:
|
elif containing_type.__module__ == ctypes.__name__:
|
||||||
logger.debug(f"Processing ctype internal{containing_type}")
|
logger.debug(f"Processing ctype internal{containing_type}")
|
||||||
new_dep_node.set_field_ready(elem_name, True)
|
new_dep_node.set_field_ready(elem_name, True)
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"Module not supported in recursive resolution: {containing_type_module}"
|
"Module not supported in recursive resolution"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
new_dep_node.add_dependent(
|
new_dep_node.add_dependent(
|
||||||
@ -283,12 +245,9 @@ def process_vmlinux_post_ast(
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"{elem_name} with type {elem_type} from module {module_name} not supported in recursive resolver"
|
f"{elem_name} with type {elem_type} from module {module_name} not supported in recursive resolver"
|
||||||
)
|
)
|
||||||
elif module_name == ctypes.__name__ or module_name is None:
|
|
||||||
# Handle ctypes types - these don't need processing, just return
|
|
||||||
logger.debug(f"Skipping ctypes type {current_symbol_name}")
|
|
||||||
return True
|
|
||||||
else:
|
else:
|
||||||
raise ImportError(f"UNSUPPORTED Module {module_name}")
|
raise ImportError("UNSUPPORTED Module")
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"{current_symbol_name} processed and handler readiness {handler.is_ready}"
|
f"{current_symbol_name} processed and handler readiness {handler.is_ready}"
|
||||||
|
|||||||
@ -11,9 +11,7 @@ from .class_handler import process_vmlinux_class
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def detect_import_statement(
|
def detect_import_statement(tree: ast.AST) -> list[tuple[str, ast.ImportFrom]]:
|
||||||
tree: ast.AST,
|
|
||||||
) -> list[tuple[str, ast.ImportFrom, str, str]]:
|
|
||||||
"""
|
"""
|
||||||
Parse AST and detect import statements from vmlinux.
|
Parse AST and detect import statements from vmlinux.
|
||||||
|
|
||||||
@ -27,7 +25,7 @@ def detect_import_statement(
|
|||||||
List of tuples containing (module_name, imported_item) for each vmlinux import
|
List of tuples containing (module_name, imported_item) for each vmlinux import
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
SyntaxError: If import * is used
|
SyntaxError: If multiple imports from vmlinux are attempted or import * is used
|
||||||
"""
|
"""
|
||||||
vmlinux_imports = []
|
vmlinux_imports = []
|
||||||
|
|
||||||
@ -42,19 +40,28 @@ def detect_import_statement(
|
|||||||
"Please import specific types explicitly."
|
"Please import specific types explicitly."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Check for multiple imports: from vmlinux import A, B, C
|
||||||
|
if len(node.names) > 1:
|
||||||
|
imported_names = [alias.name for alias in node.names]
|
||||||
|
raise SyntaxError(
|
||||||
|
f"Multiple imports from vmlinux are not supported. "
|
||||||
|
f"Found: {', '.join(imported_names)}. "
|
||||||
|
f"Please use separate import statements for each type."
|
||||||
|
)
|
||||||
|
|
||||||
# Check if no specific import is specified (should not happen with valid Python)
|
# Check if no specific import is specified (should not happen with valid Python)
|
||||||
if len(node.names) == 0:
|
if len(node.names) == 0:
|
||||||
raise SyntaxError(
|
raise SyntaxError(
|
||||||
"Import from vmlinux must specify at least one type."
|
"Import from vmlinux must specify at least one type."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Support multiple imports: from vmlinux import A, B, C
|
# Valid single import
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
import_name = alias.name
|
import_name = alias.name
|
||||||
# Use alias if provided, otherwise use the original name
|
# Use alias if provided, otherwise use the original name (commented)
|
||||||
as_name = alias.asname if alias.asname else alias.name
|
# as_name = alias.asname if alias.asname else alias.name
|
||||||
vmlinux_imports.append(("vmlinux", node, import_name, as_name))
|
vmlinux_imports.append(("vmlinux", node))
|
||||||
logger.info(f"Found vmlinux import: {import_name} as {as_name}")
|
logger.info(f"Found vmlinux import: {import_name}")
|
||||||
|
|
||||||
# Handle "import vmlinux" statements (not typical but should be rejected)
|
# Handle "import vmlinux" statements (not typical but should be rejected)
|
||||||
elif isinstance(node, ast.Import):
|
elif isinstance(node, ast.Import):
|
||||||
@ -66,7 +73,6 @@ def detect_import_statement(
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Total vmlinux imports detected: {len(vmlinux_imports)}")
|
logger.info(f"Total vmlinux imports detected: {len(vmlinux_imports)}")
|
||||||
# print(f"\n**************\n{vmlinux_imports}\n**************\n")
|
|
||||||
return vmlinux_imports
|
return vmlinux_imports
|
||||||
|
|
||||||
|
|
||||||
@ -80,54 +86,57 @@ def vmlinux_proc(tree: ast.AST, module):
|
|||||||
|
|
||||||
if not import_statements:
|
if not import_statements:
|
||||||
logger.info("No vmlinux imports found")
|
logger.info("No vmlinux imports found")
|
||||||
return None
|
return
|
||||||
|
|
||||||
# Import vmlinux module directly
|
# Import vmlinux module directly
|
||||||
try:
|
try:
|
||||||
vmlinux_mod = importlib.import_module("vmlinux")
|
vmlinux_mod = importlib.import_module("vmlinux")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.warning("Could not import vmlinux module")
|
logger.warning("Could not import vmlinux module")
|
||||||
return None
|
return
|
||||||
|
|
||||||
source_file = inspect.getsourcefile(vmlinux_mod)
|
source_file = inspect.getsourcefile(vmlinux_mod)
|
||||||
if source_file is None:
|
if source_file is None:
|
||||||
logger.warning("Cannot find source for vmlinux module")
|
logger.warning("Cannot find source for vmlinux module")
|
||||||
return None
|
return
|
||||||
|
|
||||||
with open(source_file, "r") as f:
|
with open(source_file, "r") as f:
|
||||||
mod_ast = ast.parse(f.read(), filename=source_file)
|
mod_ast = ast.parse(f.read(), filename=source_file)
|
||||||
|
|
||||||
for import_mod, import_node, imported_name, as_name in import_statements:
|
for import_mod, import_node in import_statements:
|
||||||
found = False
|
for alias in import_node.names:
|
||||||
for mod_node in mod_ast.body:
|
imported_name = alias.name
|
||||||
if isinstance(mod_node, ast.ClassDef) and mod_node.name == imported_name:
|
found = False
|
||||||
process_vmlinux_class(mod_node, module, handler)
|
for mod_node in mod_ast.body:
|
||||||
found = True
|
if (
|
||||||
break
|
isinstance(mod_node, ast.ClassDef)
|
||||||
if isinstance(mod_node, ast.Assign):
|
and mod_node.name == imported_name
|
||||||
for target in mod_node.targets:
|
):
|
||||||
if isinstance(target, ast.Name) and target.id == imported_name:
|
process_vmlinux_class(mod_node, module, handler)
|
||||||
process_vmlinux_assign(mod_node, module, assignments, as_name)
|
found = True
|
||||||
found = True
|
break
|
||||||
break
|
if isinstance(mod_node, ast.Assign):
|
||||||
if found:
|
for target in mod_node.targets:
|
||||||
break
|
if isinstance(target, ast.Name) and target.id == imported_name:
|
||||||
if not found:
|
process_vmlinux_assign(mod_node, module, assignments)
|
||||||
logger.info(f"{imported_name} not found as ClassDef or Assign in vmlinux")
|
found = True
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
logger.info(
|
||||||
|
f"{imported_name} not found as ClassDef or Assign in vmlinux"
|
||||||
|
)
|
||||||
|
|
||||||
IRGenerator(module, handler, assignments)
|
IRGenerator(module, handler, assignments)
|
||||||
return assignments
|
return assignments
|
||||||
|
|
||||||
|
|
||||||
def process_vmlinux_assign(
|
def process_vmlinux_assign(node, module, assignments: dict[str, AssignmentInfo]):
|
||||||
node, module, assignments: dict[str, AssignmentInfo], target_name=None
|
|
||||||
):
|
|
||||||
"""Process assignments from vmlinux module."""
|
"""Process assignments from vmlinux module."""
|
||||||
# Only handle single-target assignments
|
# Only handle single-target assignments
|
||||||
if len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
|
if len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
|
||||||
# Use provided target_name (for aliased imports) or fall back to original name
|
target_name = node.targets[0].id
|
||||||
if target_name is None:
|
|
||||||
target_name = node.targets[0].id
|
|
||||||
|
|
||||||
# Handle constant value assignments
|
# Handle constant value assignments
|
||||||
if isinstance(node.value, ast.Constant):
|
if isinstance(node.value, ast.Constant):
|
||||||
@ -139,7 +148,6 @@ def process_vmlinux_assign(
|
|||||||
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)}"
|
||||||
|
|||||||
@ -46,14 +46,13 @@ def debug_info_generation(
|
|||||||
|
|
||||||
if struct.name.startswith("struct_"):
|
if struct.name.startswith("struct_"):
|
||||||
struct_name = struct.name.removeprefix("struct_")
|
struct_name = struct.name.removeprefix("struct_")
|
||||||
# Create struct type with all members
|
|
||||||
struct_type = generator.create_struct_type_with_name(
|
|
||||||
struct_name, members, struct.__sizeof__() * 8, is_distinct=True
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
logger.warning("Blindly handling Unions present in vmlinux dependencies")
|
raise ValueError("Unions are not supported in the current version")
|
||||||
struct_type = None
|
# Create struct type with all members
|
||||||
# raise ValueError("Unions are not supported in the current version")
|
struct_type = generator.create_struct_type_with_name(
|
||||||
|
struct_name, members, struct.__sizeof__() * 8, is_distinct=True
|
||||||
|
)
|
||||||
|
|
||||||
return struct_type
|
return struct_type
|
||||||
|
|
||||||
|
|
||||||
@ -63,7 +62,7 @@ def _get_field_debug_type(
|
|||||||
generator: DebugInfoGenerator,
|
generator: DebugInfoGenerator,
|
||||||
parent_struct: DependencyNode,
|
parent_struct: DependencyNode,
|
||||||
generated_debug_info: List[Tuple[DependencyNode, Any]],
|
generated_debug_info: List[Tuple[DependencyNode, Any]],
|
||||||
) -> tuple[Any, int] | None:
|
) -> tuple[Any, int]:
|
||||||
"""
|
"""
|
||||||
Determine the appropriate debug type for a field based on its Python/ctypes type.
|
Determine the appropriate debug type for a field based on its Python/ctypes type.
|
||||||
|
|
||||||
@ -79,11 +78,7 @@ def _get_field_debug_type(
|
|||||||
"""
|
"""
|
||||||
# Handle complex types (arrays, pointers)
|
# Handle complex types (arrays, pointers)
|
||||||
if field.ctype_complex_type is not None:
|
if field.ctype_complex_type is not None:
|
||||||
#TODO: Check if this is a CFUNCTYPE (function pointer), but sadly it just checks callable for now
|
if issubclass(field.ctype_complex_type, ctypes.Array):
|
||||||
if callable(field.ctype_complex_type):
|
|
||||||
# Handle function pointer types, create a void pointer as a placeholder
|
|
||||||
return generator.create_pointer_type(None), 64
|
|
||||||
elif issubclass(field.ctype_complex_type, ctypes.Array):
|
|
||||||
# Handle array types
|
# Handle array types
|
||||||
element_type, base_type_size = _get_basic_debug_type(
|
element_type, base_type_size = _get_basic_debug_type(
|
||||||
field.containing_type, generator
|
field.containing_type, generator
|
||||||
|
|||||||
@ -11,9 +11,6 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class IRGenerator:
|
class IRGenerator:
|
||||||
# This field keeps track of the non_struct names to avoid duplicate name errors.
|
|
||||||
type_number = 0
|
|
||||||
unprocessed_store = []
|
|
||||||
# get the assignments dict and add this stuff to it.
|
# get the assignments dict and add this stuff to it.
|
||||||
def __init__(self, llvm_module, handler: DependencyHandler, assignments):
|
def __init__(self, llvm_module, handler: DependencyHandler, assignments):
|
||||||
self.llvm_module = llvm_module
|
self.llvm_module = llvm_module
|
||||||
@ -71,14 +68,14 @@ class IRGenerator:
|
|||||||
dep_node_from_dependency, processing_stack
|
dep_node_from_dependency, processing_stack
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
print(struct)
|
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"Warning: Dependency {dependency} not found in handler"
|
f"Warning: Dependency {dependency} not found in handler"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate IR first to populate field names
|
# Generate IR first to populate field names
|
||||||
struct_debug_info = self.gen_ir(struct, self.generated_debug_info)
|
self.generated_debug_info.append(
|
||||||
self.generated_debug_info.append((struct, struct_debug_info))
|
(struct, self.gen_ir(struct, self.generated_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:
|
||||||
@ -86,7 +83,6 @@ class IRGenerator:
|
|||||||
members_dict = {}
|
members_dict = {}
|
||||||
for field_name, field in struct.fields.items():
|
for field_name, field in struct.fields.items():
|
||||||
# Get the generated field name from our dictionary, or use field_name if not found
|
# Get the generated field name from our dictionary, or use field_name if not found
|
||||||
print(f"DEBUG: {struct.name}, {field_name}")
|
|
||||||
if (
|
if (
|
||||||
struct.name in self.generated_field_names
|
struct.name in self.generated_field_names
|
||||||
and field_name in self.generated_field_names[struct.name]
|
and field_name in self.generated_field_names[struct.name]
|
||||||
@ -109,7 +105,6 @@ 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}")
|
||||||
|
|
||||||
@ -134,20 +129,7 @@ class IRGenerator:
|
|||||||
|
|
||||||
for field_name, field in struct.fields.items():
|
for field_name, field in struct.fields.items():
|
||||||
# does not take arrays and similar types into consideration yet.
|
# does not take arrays and similar types into consideration yet.
|
||||||
if callable(field.ctype_complex_type):
|
if field.ctype_complex_type is not None and issubclass(
|
||||||
# Function pointer case - generate a simple field accessor
|
|
||||||
field_co_re_name, returned = self._struct_name_generator(
|
|
||||||
struct, field, field_index
|
|
||||||
)
|
|
||||||
print(field_co_re_name)
|
|
||||||
field_index += 1
|
|
||||||
globvar = ir.GlobalVariable(
|
|
||||||
self.llvm_module, ir.IntType(64), name=field_co_re_name
|
|
||||||
)
|
|
||||||
globvar.linkage = "external"
|
|
||||||
globvar.set_metadata("llvm.preserve.access.index", debug_info)
|
|
||||||
self.generated_field_names[struct.name][field_name] = globvar
|
|
||||||
elif field.ctype_complex_type is not None and issubclass(
|
|
||||||
field.ctype_complex_type, ctypes.Array
|
field.ctype_complex_type, ctypes.Array
|
||||||
):
|
):
|
||||||
array_size = field.type_size
|
array_size = field.type_size
|
||||||
@ -155,7 +137,7 @@ class IRGenerator:
|
|||||||
if containing_type.__module__ == ctypes.__name__:
|
if containing_type.__module__ == ctypes.__name__:
|
||||||
containing_type_size = ctypes.sizeof(containing_type)
|
containing_type_size = ctypes.sizeof(containing_type)
|
||||||
if array_size == 0:
|
if array_size == 0:
|
||||||
field_co_re_name, returned = self._struct_name_generator(
|
field_co_re_name = self._struct_name_generator(
|
||||||
struct, field, field_index, True, 0, containing_type_size
|
struct, field, field_index, True, 0, containing_type_size
|
||||||
)
|
)
|
||||||
globvar = ir.GlobalVariable(
|
globvar = ir.GlobalVariable(
|
||||||
@ -167,7 +149,7 @@ class IRGenerator:
|
|||||||
field_index += 1
|
field_index += 1
|
||||||
continue
|
continue
|
||||||
for i in range(0, array_size):
|
for i in range(0, array_size):
|
||||||
field_co_re_name, returned = self._struct_name_generator(
|
field_co_re_name = self._struct_name_generator(
|
||||||
struct, field, field_index, True, i, containing_type_size
|
struct, field, field_index, True, i, containing_type_size
|
||||||
)
|
)
|
||||||
globvar = ir.GlobalVariable(
|
globvar = ir.GlobalVariable(
|
||||||
@ -181,26 +163,12 @@ class IRGenerator:
|
|||||||
array_size = field.type_size
|
array_size = field.type_size
|
||||||
containing_type = field.containing_type
|
containing_type = field.containing_type
|
||||||
if containing_type.__module__ == "vmlinux":
|
if containing_type.__module__ == "vmlinux":
|
||||||
print(struct)
|
containing_type_size = self.handler[
|
||||||
# Unwrap all pointer layers to get the base struct type
|
containing_type.__name__
|
||||||
base_containing_type = containing_type
|
].current_offset
|
||||||
while hasattr(base_containing_type, "_type_"):
|
for i in range(0, array_size):
|
||||||
next_type = base_containing_type._type_
|
field_co_re_name = self._struct_name_generator(
|
||||||
# Stop if _type_ is a string (like 'c' for c_char)
|
struct, field, field_index, True, i, containing_type_size
|
||||||
#TODO: stacked pointers not handl;ing ctypes check here as well
|
|
||||||
if isinstance(next_type, str):
|
|
||||||
break
|
|
||||||
base_containing_type = next_type
|
|
||||||
|
|
||||||
# Get the base struct name
|
|
||||||
base_struct_name = base_containing_type.__name__ if hasattr(base_containing_type, "__name__") else str(base_containing_type)
|
|
||||||
|
|
||||||
# Look up the size using the base struct name
|
|
||||||
containing_type_size = self.handler[base_struct_name].current_offset
|
|
||||||
print(f"GAY: {array_size}, {struct.name}, {field_name}")
|
|
||||||
if array_size == 0:
|
|
||||||
field_co_re_name, returned = self._struct_name_generator(
|
|
||||||
struct, field, field_index, True, 0, containing_type_size
|
|
||||||
)
|
)
|
||||||
globvar = ir.GlobalVariable(
|
globvar = ir.GlobalVariable(
|
||||||
self.llvm_module, ir.IntType(64), name=field_co_re_name
|
self.llvm_module, ir.IntType(64), name=field_co_re_name
|
||||||
@ -208,21 +176,9 @@ class IRGenerator:
|
|||||||
globvar.linkage = "external"
|
globvar.linkage = "external"
|
||||||
globvar.set_metadata("llvm.preserve.access.index", debug_info)
|
globvar.set_metadata("llvm.preserve.access.index", debug_info)
|
||||||
self.generated_field_names[struct.name][field_name] = globvar
|
self.generated_field_names[struct.name][field_name] = globvar
|
||||||
field_index += 1
|
field_index += 1
|
||||||
else:
|
|
||||||
for i in range(0, array_size):
|
|
||||||
field_co_re_name, returned = self._struct_name_generator(
|
|
||||||
struct, field, field_index, True, i, containing_type_size
|
|
||||||
)
|
|
||||||
globvar = ir.GlobalVariable(
|
|
||||||
self.llvm_module, ir.IntType(64), name=field_co_re_name
|
|
||||||
)
|
|
||||||
globvar.linkage = "external"
|
|
||||||
globvar.set_metadata("llvm.preserve.access.index", debug_info)
|
|
||||||
self.generated_field_names[struct.name][field_name] = globvar
|
|
||||||
field_index += 1
|
|
||||||
else:
|
else:
|
||||||
field_co_re_name, returned = self._struct_name_generator(
|
field_co_re_name = self._struct_name_generator(
|
||||||
struct, field, field_index
|
struct, field, field_index
|
||||||
)
|
)
|
||||||
field_index += 1
|
field_index += 1
|
||||||
@ -242,7 +198,7 @@ class IRGenerator:
|
|||||||
is_indexed: bool = False,
|
is_indexed: bool = False,
|
||||||
index: int = 0,
|
index: int = 0,
|
||||||
containing_type_size: int = 0,
|
containing_type_size: int = 0,
|
||||||
) -> tuple[str, bool]:
|
) -> str:
|
||||||
# TODO: Does not support Unions as well as recursive pointer and array type naming
|
# TODO: Does not support Unions as well as recursive pointer and array type naming
|
||||||
if is_indexed:
|
if is_indexed:
|
||||||
name = (
|
name = (
|
||||||
@ -252,7 +208,7 @@ class IRGenerator:
|
|||||||
+ "$"
|
+ "$"
|
||||||
+ f"0:{field_index}:{index}"
|
+ f"0:{field_index}:{index}"
|
||||||
)
|
)
|
||||||
return name, True
|
return name
|
||||||
elif struct.name.startswith("struct_"):
|
elif struct.name.startswith("struct_"):
|
||||||
name = (
|
name = (
|
||||||
"llvm."
|
"llvm."
|
||||||
@ -261,18 +217,9 @@ class IRGenerator:
|
|||||||
+ "$"
|
+ "$"
|
||||||
+ f"0:{field_index}"
|
+ f"0:{field_index}"
|
||||||
)
|
)
|
||||||
return name, True
|
return name
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
print(self.handler[struct.name])
|
||||||
"Blindly handling non-struct type to avoid type errors in vmlinux IR generation. Possibly a union."
|
raise TypeError(
|
||||||
|
"Name generation cannot occur due to type name not starting with struct"
|
||||||
)
|
)
|
||||||
self.type_number += 1
|
|
||||||
unprocessed_type = "unprocessed_type_" + str(self.handler[struct.name].name)
|
|
||||||
if self.unprocessed_store.__contains__(unprocessed_type):
|
|
||||||
return unprocessed_type + "_" + str(self.type_number), False
|
|
||||||
else:
|
|
||||||
self.unprocessed_store.append(unprocessed_type)
|
|
||||||
return unprocessed_type, False
|
|
||||||
# raise TypeError(
|
|
||||||
# "Name generation cannot occur due to type name not starting with struct"
|
|
||||||
# )
|
|
||||||
|
|||||||
@ -1,9 +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.vmlinux_parser.assignment_info import AssignmentType
|
from pythonbpf.vmlinux_parser.assignment_info import AssignmentType
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -39,39 +36,20 @@ class VmlinuxHandler:
|
|||||||
"""Check if name is a vmlinux enum constant"""
|
"""Check if name is a vmlinux enum constant"""
|
||||||
return (
|
return (
|
||||||
name in self.vmlinux_symtab
|
name in self.vmlinux_symtab
|
||||||
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):
|
|
||||||
"""Check if name is a vmlinux struct type"""
|
|
||||||
if (
|
|
||||||
name in self.vmlinux_symtab
|
|
||||||
and self.vmlinux_symtab[name].value_type == AssignmentType.STRUCT
|
|
||||||
):
|
|
||||||
return self.vmlinux_symtab[name].python_type
|
|
||||||
else:
|
|
||||||
raise ValueError(f"{name} is not a vmlinux struct type")
|
|
||||||
|
|
||||||
def is_vmlinux_struct(self, name):
|
def is_vmlinux_struct(self, name):
|
||||||
"""Check if name is a vmlinux struct"""
|
"""Check if name is a vmlinux struct"""
|
||||||
return (
|
return (
|
||||||
name in self.vmlinux_symtab
|
name in self.vmlinux_symtab
|
||||||
and self.vmlinux_symtab[name].value_type == AssignmentType.STRUCT
|
and self.vmlinux_symtab[name]["value_type"] == AssignmentType.STRUCT
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_vmlinux_enum(self, name):
|
def handle_vmlinux_enum(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):
|
if self.is_vmlinux_enum(name):
|
||||||
value = self.vmlinux_symtab[name].value
|
value = self.vmlinux_symtab[name]["value"]
|
||||||
logger.info(f"Resolving vmlinux enum {name} = {value}")
|
logger.info(f"Resolving vmlinux enum {name} = {value}")
|
||||||
return ir.Constant(ir.IntType(64), value), ir.IntType(64)
|
return ir.Constant(ir.IntType(64), value), ir.IntType(64)
|
||||||
return None
|
return None
|
||||||
@ -79,131 +57,34 @@ class VmlinuxHandler:
|
|||||||
def get_vmlinux_enum_value(self, name):
|
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):
|
if self.is_vmlinux_enum(name):
|
||||||
value = self.vmlinux_symtab[name].value
|
value = self.vmlinux_symtab[name]["value"]
|
||||||
logger.info(f"The value of vmlinux enum {name} = {value}")
|
logger.info(f"The value of vmlinux enum {name} = {value}")
|
||||||
return value
|
return value
|
||||||
return None
|
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(
|
def handle_vmlinux_struct_field(
|
||||||
self, struct_var_name, field_name, module, builder, local_sym_tab
|
self, struct_var_name, field_name, module, builder, local_sym_tab
|
||||||
):
|
):
|
||||||
"""Handle access to vmlinux struct fields"""
|
"""Handle access to vmlinux struct fields"""
|
||||||
|
# Check if it's a variable of vmlinux struct type
|
||||||
if struct_var_name in local_sym_tab:
|
if struct_var_name in local_sym_tab:
|
||||||
var_info: LocalSymbol = local_sym_tab[struct_var_name]
|
var_info = local_sym_tab[struct_var_name] # noqa: F841
|
||||||
|
# Need to check if this variable is a vmlinux struct
|
||||||
|
# This will depend on how you track vmlinux struct types in your symbol table
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Attempting to access field {field_name} of possible vmlinux struct {struct_var_name}"
|
f"Attempting to access field {field_name} of possible vmlinux struct {struct_var_name}"
|
||||||
)
|
)
|
||||||
python_type: type = var_info.metadata
|
|
||||||
globvar_ir, field_data = self.get_field_type(
|
|
||||||
python_type.__name__, field_name
|
|
||||||
)
|
|
||||||
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 pointer to field and field type
|
||||||
return field_ptr, field_data
|
return None
|
||||||
else:
|
return None
|
||||||
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_ptr_type = ir.PointerType()
|
|
||||||
|
|
||||||
# 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], tail=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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):
|
|
||||||
python_type = self.vmlinux_symtab[struct_name].python_type
|
|
||||||
return hasattr(python_type, field_name)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_field_type(self, vmlinux_struct_name, field_name):
|
|
||||||
"""Get the type of a field in a vmlinux struct"""
|
|
||||||
if self.is_vmlinux_struct(vmlinux_struct_name):
|
|
||||||
python_type = self.vmlinux_symtab[vmlinux_struct_name].python_type
|
|
||||||
if hasattr(python_type, field_name):
|
|
||||||
return self.vmlinux_symtab[vmlinux_struct_name].members[field_name]
|
|
||||||
else:
|
|
||||||
raise ValueError(
|
|
||||||
f"Field {field_name} not found in vmlinux struct {vmlinux_struct_name}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"{vmlinux_struct_name} is not a vmlinux struct")
|
|
||||||
|
|
||||||
def get_field_index(self, vmlinux_struct_name, field_name):
|
|
||||||
"""Get the type of a field in a vmlinux struct"""
|
|
||||||
if self.is_vmlinux_struct(vmlinux_struct_name):
|
|
||||||
python_type = self.vmlinux_symtab[vmlinux_struct_name].python_type
|
|
||||||
if hasattr(python_type, field_name):
|
|
||||||
return list(
|
|
||||||
self.vmlinux_symtab[vmlinux_struct_name].members.keys()
|
|
||||||
).index(field_name)
|
|
||||||
else:
|
|
||||||
raise ValueError(
|
|
||||||
f"Field {field_name} not found in vmlinux struct {vmlinux_struct_name}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"{vmlinux_struct_name} is not a vmlinux struct")
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
BPF_CLANG := clang
|
BPF_CLANG := clang
|
||||||
CFLAGS := -O0 -emit-llvm -target bpf -c
|
CFLAGS := -O2 -emit-llvm -target bpf -c
|
||||||
|
|
||||||
SRC := $(wildcard *.bpf.c)
|
SRC := $(wildcard *.bpf.c)
|
||||||
LL := $(SRC:.bpf.c=.bpf.ll)
|
LL := $(SRC:.bpf.c=.bpf.ll)
|
||||||
|
|||||||
25
tests/c-form/ex5.bpf.c
Normal file
25
tests/c-form/ex5.bpf.c
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#define __TARGET_ARCH_arm64
|
||||||
|
|
||||||
|
#include "vmlinux.h"
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
#include <bpf/bpf_tracing.h>
|
||||||
|
#include <bpf/bpf_core_read.h>
|
||||||
|
|
||||||
|
// Map: key = struct request*, value = u64 timestamp
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_HASH);
|
||||||
|
__type(key, struct request *);
|
||||||
|
__type(value, u64);
|
||||||
|
__uint(max_entries, 1024);
|
||||||
|
} start SEC(".maps");
|
||||||
|
|
||||||
|
// Attach to kprobe for blk_start_request
|
||||||
|
SEC("kprobe/blk_start_request")
|
||||||
|
int BPF_KPROBE(trace_start, struct request *req)
|
||||||
|
{
|
||||||
|
u64 ts = bpf_ktime_get_ns();
|
||||||
|
bpf_map_update_elem(&start, &req, &ts, BPF_ANY);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char LICENSE[] SEC("license") = "GPL";
|
||||||
@ -2,75 +2,18 @@
|
|||||||
#include <bpf/bpf_helpers.h>
|
#include <bpf/bpf_helpers.h>
|
||||||
#include <bpf/bpf_tracing.h>
|
#include <bpf/bpf_tracing.h>
|
||||||
|
|
||||||
char LICENSE[] SEC("license") = "GPL";
|
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||||
|
|
||||||
SEC("kprobe/do_unlinkat")
|
SEC("kprobe/do_unlinkat")
|
||||||
int kprobe_execve(struct pt_regs *ctx)
|
int kprobe_execve(struct pt_regs *ctx)
|
||||||
{
|
{
|
||||||
bpf_printk("unlinkat created");
|
bpf_printk("unlinkat created");
|
||||||
|
return 0;
|
||||||
unsigned long r15 = ctx->r15;
|
}
|
||||||
bpf_printk("r15: %lld", r15);
|
|
||||||
|
SEC("kretprobe/do_unlinkat")
|
||||||
unsigned long r14 = ctx->r14;
|
int kretprobe_execve(struct pt_regs *ctx)
|
||||||
bpf_printk("r14: %lld", r14);
|
{
|
||||||
|
bpf_printk("unlinkat returned\n");
|
||||||
unsigned long r13 = ctx->r13;
|
|
||||||
bpf_printk("r13: %lld", r13);
|
|
||||||
|
|
||||||
unsigned long r12 = ctx->r12;
|
|
||||||
bpf_printk("r12: %lld", r12);
|
|
||||||
|
|
||||||
unsigned long bp = ctx->bp;
|
|
||||||
bpf_printk("rbp: %lld", bp);
|
|
||||||
|
|
||||||
unsigned long bx = ctx->bx;
|
|
||||||
bpf_printk("rbx: %lld", bx);
|
|
||||||
|
|
||||||
unsigned long r11 = ctx->r11;
|
|
||||||
bpf_printk("r11: %lld", r11);
|
|
||||||
|
|
||||||
unsigned long r10 = ctx->r10;
|
|
||||||
bpf_printk("r10: %lld", r10);
|
|
||||||
|
|
||||||
unsigned long r9 = ctx->r9;
|
|
||||||
bpf_printk("r9: %lld", r9);
|
|
||||||
|
|
||||||
unsigned long r8 = ctx->r8;
|
|
||||||
bpf_printk("r8: %lld", r8);
|
|
||||||
|
|
||||||
unsigned long ax = ctx->ax;
|
|
||||||
bpf_printk("rax: %lld", ax);
|
|
||||||
|
|
||||||
unsigned long cx = ctx->cx;
|
|
||||||
bpf_printk("rcx: %lld", cx);
|
|
||||||
|
|
||||||
unsigned long dx = ctx->dx;
|
|
||||||
bpf_printk("rdx: %lld", dx);
|
|
||||||
|
|
||||||
unsigned long si = ctx->si;
|
|
||||||
bpf_printk("rsi: %lld", si);
|
|
||||||
|
|
||||||
unsigned long di = ctx->di;
|
|
||||||
bpf_printk("rdi: %lld", di);
|
|
||||||
|
|
||||||
unsigned long orig_ax = ctx->orig_ax;
|
|
||||||
bpf_printk("orig_rax: %lld", orig_ax);
|
|
||||||
|
|
||||||
unsigned long ip = ctx->ip;
|
|
||||||
bpf_printk("rip: %lld", ip);
|
|
||||||
|
|
||||||
unsigned long cs = ctx->cs;
|
|
||||||
bpf_printk("cs: %lld", cs);
|
|
||||||
|
|
||||||
unsigned long flags = ctx->flags;
|
|
||||||
bpf_printk("eflags: %lld", flags);
|
|
||||||
|
|
||||||
unsigned long sp = ctx->sp;
|
|
||||||
bpf_printk("rsp: %lld", sp);
|
|
||||||
|
|
||||||
unsigned long ss = ctx->ss;
|
|
||||||
bpf_printk("ss: %lld", ss);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0
|
|
||||||
|
|
||||||
#include "vmlinux.h"
|
|
||||||
#include <bpf/bpf_helpers.h>
|
|
||||||
#include <bpf/bpf_tracing.h>
|
|
||||||
|
|
||||||
/*
|
|
||||||
Information gained from reversing this (multiple kernel versions):
|
|
||||||
There is no point of
|
|
||||||
```llvm
|
|
||||||
tail call void @llvm.dbg.value(metadata ptr %0, metadata !60, metadata !DIExpression()), !dbg !70
|
|
||||||
```
|
|
||||||
and the first argument of passthrough is fucking useless. It just needs to be a distinct integer:
|
|
||||||
```llvm
|
|
||||||
%9 = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 3, ptr %8)
|
|
||||||
```
|
|
||||||
*/
|
|
||||||
|
|
||||||
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 id = ctx->id;
|
|
||||||
bpf_printk("This is context field %d", id);
|
|
||||||
/*
|
|
||||||
* the IR to aim for is
|
|
||||||
* %2 = alloca ptr, align 8
|
|
||||||
* store ptr %0, ptr %2, align 8
|
|
||||||
* Above, %0 is the arg pointer
|
|
||||||
* %5 = load ptr, ptr %2, align 8
|
|
||||||
* %6 = getelementptr inbounds %struct.trace_event_raw_sys_enter, ptr %5, i32 0, i32 2
|
|
||||||
* %7 = load i64, ptr @"llvm.trace_event_raw_sys_enter:0:16$0:2:0", align 8
|
|
||||||
* %8 = bitcast ptr %5 to ptr
|
|
||||||
* %9 = getelementptr i8, ptr %8, i64 %7
|
|
||||||
* %10 = bitcast ptr %9 to ptr
|
|
||||||
* %11 = call ptr @llvm.bpf.passthrough.p0.p0(i32 0, ptr %10)
|
|
||||||
* %12 = load i64, ptr %11, align 8, !dbg !101
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char LICENSE[] SEC("license") = "GPL";
|
|
||||||
121617
tests/c-form/vmlinux.h
vendored
Normal file
121617
tests/c-form/vmlinux.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,21 +0,0 @@
|
|||||||
// xdp_rewrite.c
|
|
||||||
#include <linux/bpf.h>
|
|
||||||
#include <bpf/bpf_helpers.h>
|
|
||||||
#include <linux/if_ether.h>
|
|
||||||
|
|
||||||
SEC("xdp")
|
|
||||||
int xdp_rewrite_mac(struct xdp_md *ctx)
|
|
||||||
{
|
|
||||||
void *data_end = (void *)(long)ctx->data_end;
|
|
||||||
void *data = (void *)(long)ctx->data;
|
|
||||||
|
|
||||||
struct ethhdr *eth = data;
|
|
||||||
if ((void*)(eth + 1) > data_end)
|
|
||||||
return XDP_PASS;
|
|
||||||
__u8 new_src[ETH_ALEN] = {0x02,0x00,0x00,0x00,0x00,0x02};
|
|
||||||
for (int i = 0; i < ETH_ALEN; i++) eth->h_source[i] = new_src[i];
|
|
||||||
|
|
||||||
return XDP_PASS;
|
|
||||||
}
|
|
||||||
|
|
||||||
char _license[] SEC("license") = "GPL";
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
from vmlinux import struct_request, struct_pt_regs, XDP_PASS
|
|
||||||
from pythonbpf import bpf, section, bpfglobal, compile_to_ir
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
@bpf
|
|
||||||
@section("kprobe/blk_mq_start_request")
|
|
||||||
def example(ctx: struct_pt_regs):
|
|
||||||
req = struct_request(ctx.di)
|
|
||||||
c = req.__data_len
|
|
||||||
d = XDP_PASS
|
|
||||||
print(f"data length {c} and test {d}")
|
|
||||||
|
|
||||||
|
|
||||||
@bpf
|
|
||||||
@bpfglobal
|
|
||||||
def LICENSE() -> str:
|
|
||||||
return "GPL"
|
|
||||||
|
|
||||||
|
|
||||||
compile_to_ir("requests.py", "requests.ll", loglevel=logging.INFO)
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
from vmlinux import struct_kobj_type
|
|
||||||
from pythonbpf import bpf, section, bpfglobal, compile_to_ir
|
|
||||||
import logging
|
|
||||||
from ctypes import c_void_p
|
|
||||||
|
|
||||||
|
|
||||||
@bpf
|
|
||||||
@section("kprobe/blk_mq_start_request")
|
|
||||||
def example(ctx: c_void_p):
|
|
||||||
print(f"data lengt")
|
|
||||||
|
|
||||||
|
|
||||||
@bpf
|
|
||||||
@bpfglobal
|
|
||||||
def LICENSE() -> str:
|
|
||||||
return "GPL"
|
|
||||||
|
|
||||||
|
|
||||||
compile_to_ir("requests.py", "requests.ll", loglevel=logging.INFO)
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
from pythonbpf import bpf, map, section, bpfglobal, compile, struct, compile_to_ir
|
from pythonbpf import bpf, map, section, bpfglobal, compile, struct
|
||||||
from ctypes import c_void_p, c_int64, c_int32, c_uint64
|
from ctypes import c_void_p, c_int64, c_int32, c_uint64
|
||||||
from pythonbpf.maps import HashMap
|
from pythonbpf.maps import HashMap
|
||||||
from pythonbpf.helper import ktime
|
from pythonbpf.helper import ktime
|
||||||
@ -71,5 +71,4 @@ def LICENSE() -> str:
|
|||||||
return "GPL"
|
return "GPL"
|
||||||
|
|
||||||
|
|
||||||
compile_to_ir("comprehensive.py", "comprehensive.ll")
|
|
||||||
compile()
|
compile()
|
||||||
|
|||||||
29
tests/passing_tests/helpers/bpf_probe_read.py
Normal file
29
tests/passing_tests/helpers/bpf_probe_read.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from pythonbpf import bpf, section, bpfglobal, compile, struct
|
||||||
|
from ctypes import c_void_p, c_int64, c_uint64, c_uint32
|
||||||
|
from pythonbpf.helper import probe_read
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@struct
|
||||||
|
class data_t:
|
||||||
|
pid: c_uint32
|
||||||
|
value: c_uint64
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@section("tracepoint/syscalls/sys_enter_execve")
|
||||||
|
def test_probe_read(ctx: c_void_p) -> c_int64:
|
||||||
|
"""Test bpf_probe_read helper function"""
|
||||||
|
data = data_t()
|
||||||
|
probe_read(data.value, 8, ctx)
|
||||||
|
probe_read(data.pid, 4, ctx)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@bpfglobal
|
||||||
|
def LICENSE() -> str:
|
||||||
|
return "GPL"
|
||||||
|
|
||||||
|
|
||||||
|
compile()
|
||||||
25
tests/passing_tests/helpers/prandom.py
Normal file
25
tests/passing_tests/helpers/prandom.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from pythonbpf import bpf, bpfglobal, section, BPF, trace_pipe
|
||||||
|
from ctypes import c_void_p, c_int64
|
||||||
|
from pythonbpf.helper import random
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@section("tracepoint/syscalls/sys_enter_clone")
|
||||||
|
def hello_world(ctx: c_void_p) -> c_int64:
|
||||||
|
r = random()
|
||||||
|
print(f"Hello, World!, {r}")
|
||||||
|
return 0 # type: ignore [return-value]
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@bpfglobal
|
||||||
|
def LICENSE() -> str:
|
||||||
|
return "GPL"
|
||||||
|
|
||||||
|
|
||||||
|
# Compile and load
|
||||||
|
b = BPF()
|
||||||
|
b.load()
|
||||||
|
b.attach_all()
|
||||||
|
|
||||||
|
trace_pipe()
|
||||||
40
tests/passing_tests/helpers/smp_processor_id.py
Normal file
40
tests/passing_tests/helpers/smp_processor_id.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from pythonbpf import bpf, section, bpfglobal, compile, struct
|
||||||
|
from ctypes import c_void_p, c_int64, c_uint32, c_uint64
|
||||||
|
from pythonbpf.helper import smp_processor_id, ktime
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@struct
|
||||||
|
class cpu_event_t:
|
||||||
|
cpu_id: c_uint32
|
||||||
|
timestamp: c_uint64
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@section("tracepoint/syscalls/sys_enter_execve")
|
||||||
|
def trace_with_cpu(ctx: c_void_p) -> c_int64:
|
||||||
|
"""Test bpf_get_smp_processor_id helper function"""
|
||||||
|
|
||||||
|
# Get the current CPU ID
|
||||||
|
cpu = smp_processor_id()
|
||||||
|
|
||||||
|
# Print it
|
||||||
|
print(f"Running on CPU {cpu}")
|
||||||
|
|
||||||
|
# Use it in a struct
|
||||||
|
event = cpu_event_t()
|
||||||
|
event.cpu_id = smp_processor_id()
|
||||||
|
event.timestamp = ktime()
|
||||||
|
|
||||||
|
print(f"Event on CPU {event.cpu_id} at time {event.timestamp}")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@bpfglobal
|
||||||
|
def LICENSE() -> str:
|
||||||
|
return "GPL"
|
||||||
|
|
||||||
|
|
||||||
|
compile()
|
||||||
31
tests/passing_tests/helpers/uid_gid.py
Normal file
31
tests/passing_tests/helpers/uid_gid.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from pythonbpf import bpf, section, bpfglobal, compile
|
||||||
|
from ctypes import c_void_p, c_int64
|
||||||
|
from pythonbpf.helper import uid, pid
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@section("tracepoint/syscalls/sys_enter_execve")
|
||||||
|
def filter_by_user(ctx: c_void_p) -> c_int64:
|
||||||
|
"""Filter events by specific user ID"""
|
||||||
|
|
||||||
|
current_uid = uid()
|
||||||
|
|
||||||
|
# Only trace root user (UID 0)
|
||||||
|
if current_uid == 0:
|
||||||
|
process_id = pid()
|
||||||
|
print(f"Root process {process_id} executed")
|
||||||
|
|
||||||
|
# Or trace specific user (e.g., UID 1000)
|
||||||
|
if current_uid == 1002:
|
||||||
|
print("User 1002 executed something")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@bpfglobal
|
||||||
|
def LICENSE() -> str:
|
||||||
|
return "GPL"
|
||||||
|
|
||||||
|
|
||||||
|
compile()
|
||||||
@ -1,53 +0,0 @@
|
|||||||
from pythonbpf import bpf, section, bpfglobal, BPF, trace_pipe
|
|
||||||
from pythonbpf import compile # noqa: F401
|
|
||||||
from vmlinux import struct_pt_regs
|
|
||||||
from ctypes import c_int64, c_int32, c_void_p # noqa: F401
|
|
||||||
|
|
||||||
|
|
||||||
@bpf
|
|
||||||
@section("kprobe/do_unlinkat")
|
|
||||||
def kprobe_execve(ctx: struct_pt_regs) -> c_int64:
|
|
||||||
r15 = ctx.r15
|
|
||||||
r14 = ctx.r14
|
|
||||||
r13 = ctx.r13
|
|
||||||
r12 = ctx.r12
|
|
||||||
bp = ctx.bp
|
|
||||||
bx = ctx.bx
|
|
||||||
r11 = ctx.r11
|
|
||||||
r10 = ctx.r10
|
|
||||||
r9 = ctx.r9
|
|
||||||
r8 = ctx.r8
|
|
||||||
ax = ctx.ax
|
|
||||||
cx = ctx.cx
|
|
||||||
dx = ctx.dx
|
|
||||||
si = ctx.si
|
|
||||||
di = ctx.di
|
|
||||||
orig_ax = ctx.orig_ax
|
|
||||||
ip = ctx.ip
|
|
||||||
cs = ctx.cs
|
|
||||||
flags = ctx.flags
|
|
||||||
sp = ctx.sp
|
|
||||||
ss = ctx.ss
|
|
||||||
|
|
||||||
print(f"r15={r15} r14={r14} r13={r13}")
|
|
||||||
print(f"r12={r12} rbp={bp} rbx={bx}")
|
|
||||||
print(f"r11={r11} r10={r10} r9={r9}")
|
|
||||||
print(f"r8={r8} rax={ax} rcx={cx}")
|
|
||||||
print(f"rdx={dx} rsi={si} rdi={di}")
|
|
||||||
print(f"orig_rax={orig_ax} rip={ip} cs={cs}")
|
|
||||||
print(f"eflags={flags} rsp={sp} ss={ss}")
|
|
||||||
|
|
||||||
return c_int64(0)
|
|
||||||
|
|
||||||
|
|
||||||
@bpf
|
|
||||||
@bpfglobal
|
|
||||||
def LICENSE() -> str:
|
|
||||||
return "GPL"
|
|
||||||
|
|
||||||
|
|
||||||
b = BPF()
|
|
||||||
b.load()
|
|
||||||
b.attach_all()
|
|
||||||
|
|
||||||
trace_pipe()
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
from pythonbpf import bpf, section, bpfglobal, compile_to_ir
|
|
||||||
from pythonbpf import compile # noqa: F401
|
|
||||||
from vmlinux import TASK_COMM_LEN # noqa: F401
|
|
||||||
from vmlinux import struct_trace_event_raw_sys_enter # noqa: F401
|
|
||||||
from ctypes import c_int64, c_int32, c_void_p # noqa: F401
|
|
||||||
|
|
||||||
|
|
||||||
# from vmlinux import struct_uinput_device
|
|
||||||
# from vmlinux import struct_blk_integrity_iter
|
|
||||||
|
|
||||||
|
|
||||||
@bpf
|
|
||||||
@section("tracepoint/syscalls/sys_enter_execve")
|
|
||||||
def hello_world(ctx: struct_trace_event_raw_sys_enter) -> c_int64:
|
|
||||||
b = ctx.id
|
|
||||||
print(f"This is context field {b}")
|
|
||||||
return c_int64(0)
|
|
||||||
|
|
||||||
|
|
||||||
@bpf
|
|
||||||
@bpfglobal
|
|
||||||
def LICENSE() -> str:
|
|
||||||
return "GPL"
|
|
||||||
|
|
||||||
|
|
||||||
compile_to_ir("struct_field_access.py", "struct_field_access.ll", loglevel=logging.INFO)
|
|
||||||
compile()
|
|
||||||
Reference in New Issue
Block a user