Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c5bef26
add multi imports to single import line.
varun-r-mallya Nov 8, 2025
95a6240
fix type error
varun-r-mallya Nov 8, 2025
5031f90
fix stacked vmlinux struct parsing issue
varun-r-mallya Nov 10, 2025
f7dee32
fix nested pointers issue in array generation and also fix zero lengt…
varun-r-mallya Nov 10, 2025
73bbf00
add tests
varun-r-mallya Nov 13, 2025
4974059
format chore
varun-r-mallya Nov 13, 2025
c8801f4
nonetype not parsed
varun-r-mallya Nov 19, 2025
740eed4
add placeholder debug info to shut llvmlite up about NoneType
varun-r-mallya Nov 20, 2025
3065709
format chore
varun-r-mallya Nov 20, 2025
902a52a
remove debug print statements
varun-r-mallya Nov 20, 2025
144d9b0
change c-file test structure
varun-r-mallya Nov 20, 2025
42b8865
Merge branch 'master' into request-struct
varun-r-mallya Nov 20, 2025
fde8eab
allow allocation pass on vmlinux cast
varun-r-mallya Nov 21, 2025
2539405
allow casting
varun-r-mallya Nov 21, 2025
9ee821c
make pointer allocation feasible but subverting LLC
varun-r-mallya Nov 21, 2025
11850d1
field check in allocation pass
varun-r-mallya Nov 21, 2025
99321c7
add a failing C test
varun-r-mallya Nov 21, 2025
377fa40
add regular struct field access handling in vmlinux_registry.py
varun-r-mallya Nov 21, 2025
a42a751
format chore
varun-r-mallya Nov 21, 2025
84507b8
add btf probe read kernel helper
varun-r-mallya Nov 21, 2025
6f25c55
fix CO-RE read for cast structs
varun-r-mallya Nov 21, 2025
2b3635f
format chore
varun-r-mallya Nov 21, 2025
a91c315
sort fields in debug info by offset order
varun-r-mallya Nov 22, 2025
081ee5c
move requests.py to passing tests
varun-r-mallya Nov 22, 2025
a2de15f
add c_int to type_deducer.py
varun-r-mallya Nov 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions pythonbpf/allocation_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,18 @@ def _allocate_for_call(
local_sym_tab[var_name] = LocalSymbol(var, struct_info.ir_type, call_type)
logger.info(f"Pre-allocated {var_name} for struct {call_type}")

elif VmlinuxHandlerRegistry.is_vmlinux_struct(call_type):
# When calling struct_name(pointer), we're doing a cast, not construction
# So we allocate as a pointer (i64) not as the actual struct
var = builder.alloca(ir.IntType(64), name=var_name)
var.align = 8
local_sym_tab[var_name] = LocalSymbol(
var, ir.IntType(64), VmlinuxHandlerRegistry.get_struct_type(call_type)
)
logger.info(
f"Pre-allocated {var_name} for vmlinux struct pointer cast to {call_type}"
)

else:
logger.warning(f"Unknown call type for allocation: {call_type}")

Expand Down Expand Up @@ -325,13 +337,6 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_
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

# Determine the actual IR type based on the field's type
actual_ir_type = None
Expand Down Expand Up @@ -386,12 +391,14 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_
)
actual_ir_type = ir.IntType(64)

# Allocate with the actual IR type, not the GlobalVariable
# Allocate with the actual IR type
var = _allocate_with_type(builder, var_name, actual_ir_type)
local_sym_tab[var_name] = LocalSymbol(var, actual_ir_type, field)
local_sym_tab[var_name] = LocalSymbol(
var, actual_ir_type, field
) # <-- Store Field metadata

logger.info(
f"Pre-allocated {var_name} from vmlinux struct {vmlinux_struct_name}.{field_name}"
f"Pre-allocated {var_name} as {actual_ir_type} from vmlinux struct {vmlinux_struct_name}.{field_name}"
)
return
else:
Expand Down
26 changes: 25 additions & 1 deletion pythonbpf/assign_pass.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import ast
import logging
from inspect import isclass

from llvmlite import ir
from pythonbpf.expr import eval_expr
from pythonbpf.helper import emit_probe_read_kernel_str_call
Expand Down Expand Up @@ -148,8 +150,30 @@ def handle_variable_assignment(
return False

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}, expected {var_type}"
)

if val_type != var_type:
# Handle vmlinux struct pointers - they're represented as Python classes but are i64 pointers
if isclass(val_type) and (val_type.__module__ == "vmlinux"):
logger.info("Handling vmlinux struct pointer assignment")
# vmlinux struct pointers: val is a pointer, need to convert to i64
if isinstance(var_type, ir.IntType) and var_type.width == 64:
# Convert pointer to i64 using ptrtoint
if isinstance(val.type, ir.PointerType):
val = builder.ptrtoint(val, ir.IntType(64))
logger.info(
"Converted vmlinux struct pointer to i64 using ptrtoint"
)
builder.store(val, var_ptr)
logger.info(f"Assigned vmlinux struct pointer to {var_name} (i64)")
return True
else:
logger.error(
f"Type mismatch: vmlinux struct pointer requires i64, got {var_type}"
)
return False
if isinstance(val_type, Field):
logger.info("Handling assignment to struct field")
# Special handling for struct_xdp_md i32 fields that are zero-extended to i64
Expand Down
86 changes: 83 additions & 3 deletions pythonbpf/expr/expr_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
get_base_type_and_depth,
deref_to_depth,
)
from pythonbpf.vmlinux_parser.assignment_info import Field
from .vmlinux_registry import VmlinuxHandlerRegistry
from ..vmlinux_parser.dependency_node import Field

logger: Logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -89,8 +89,16 @@ def _handle_attribute_expr(
return vmlinux_result
else:
raise RuntimeError("Vmlinux struct did not process successfully")
metadata = structs_sym_tab[var_metadata]
if attr_name in metadata.fields:

elif isinstance(var_metadata, Field):
logger.error(
f"Cannot access field '{attr_name}' on already-loaded field value '{var_name}'"
)
return None

# Regular user-defined struct
metadata = structs_sym_tab.get(var_metadata)
if metadata and attr_name in metadata.fields:
gep = metadata.gep(builder, var_ptr, attr_name)
val = builder.load(gep)
field_type = metadata.field_type(attr_name)
Expand Down Expand Up @@ -525,6 +533,66 @@ def _handle_boolean_op(
return None


# ============================================================================
# VMLinux casting
# ============================================================================


def _handle_vmlinux_cast(
func,
module,
builder,
expr,
local_sym_tab,
map_sym_tab,
structs_sym_tab=None,
):
# handle expressions such as struct_request(ctx.di) where struct_request is a vmlinux
# struct and ctx.di is a pointer to a struct but is actually represented as a c_uint64
# which needs to be cast to a pointer. This is also a field of another vmlinux struct
"""Handle vmlinux struct cast expressions like struct_request(ctx.di)."""
if len(expr.args) != 1:
logger.info("vmlinux struct cast takes exactly one argument")
return None

# Get the struct name
struct_name = expr.func.id

# Evaluate the argument (e.g., ctx.di which is a c_uint64)
arg_result = eval_expr(
func,
module,
builder,
expr.args[0],
local_sym_tab,
map_sym_tab,
structs_sym_tab,
)

if arg_result is None:
logger.info("Failed to evaluate argument to vmlinux struct cast")
return None

arg_val, arg_type = arg_result
# Get the vmlinux struct type
vmlinux_struct_type = VmlinuxHandlerRegistry.get_struct_type(struct_name)
if vmlinux_struct_type is None:
logger.error(f"Failed to get vmlinux struct type for {struct_name}")
return None
# Cast the integer/value to a pointer to the struct
# If arg_val is an integer type, we need to inttoptr it
ptr_type = ir.PointerType()
# TODO: add a integer check here later
if ctypes_to_ir(arg_type.type.__name__):
# Cast integer to pointer
casted_ptr = builder.inttoptr(arg_val, ptr_type)
else:
logger.error(f"Unsupported type for vmlinux cast: {arg_type}")
return None

return casted_ptr, vmlinux_struct_type


# ============================================================================
# Expression Dispatcher
# ============================================================================
Expand All @@ -545,6 +613,18 @@ def eval_expr(
elif isinstance(expr, ast.Constant):
return _handle_constant_expr(module, builder, expr)
elif isinstance(expr, ast.Call):
if isinstance(expr.func, ast.Name) and VmlinuxHandlerRegistry.is_vmlinux_struct(
expr.func.id
):
return _handle_vmlinux_cast(
func,
module,
builder,
expr,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
)
if isinstance(expr.func, ast.Name) and expr.func.id == "deref":
return _handle_deref_call(expr, local_sym_tab, builder)

Expand Down
7 changes: 6 additions & 1 deletion pythonbpf/helper/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from .helper_registry import HelperHandlerRegistry
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,
emit_probe_read_kernel_call,
)
from .helpers import (
ktime,
pid,
Expand Down Expand Up @@ -74,6 +78,7 @@ def helper_call_handler(
"reset_scratch_pool",
"handle_helper_call",
"emit_probe_read_kernel_str_call",
"emit_probe_read_kernel_call",
"ktime",
"pid",
"deref",
Expand Down
70 changes: 70 additions & 0 deletions pythonbpf/helper/bpf_helper_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class BPFHelperID(Enum):
BPF_PERF_EVENT_OUTPUT = 25
BPF_GET_STACK = 67
BPF_PROBE_READ_KERNEL_STR = 115
BPF_PROBE_READ_KERNEL = 113
BPF_RINGBUF_OUTPUT = 130
BPF_RINGBUF_RESERVE = 131
BPF_RINGBUF_SUBMIT = 132
Expand Down Expand Up @@ -574,6 +575,75 @@ def bpf_probe_read_kernel_str_emitter(
return result, ir.IntType(64)


def emit_probe_read_kernel_call(builder, dst_ptr, dst_size, src_ptr):
"""Emit LLVM IR call to bpf_probe_read_kernel"""

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_KERNEL.value),
ir.PointerType(fn_type),
)

result = builder.call(
fn_ptr,
[
builder.bitcast(dst_ptr, ir.PointerType()),
ir.Constant(ir.IntType(32), dst_size),
builder.bitcast(src_ptr, ir.PointerType()),
],
tail=False,
)

logger.info(f"Emitted bpf_probe_read_kernel (size={dst_size})")
return result


@HelperHandlerRegistry.register(
"probe_read_kernel",
param_types=[
ir.PointerType(ir.IntType(8)),
ir.PointerType(ir.IntType(8)),
],
return_type=ir.IntType(64),
)
def bpf_probe_read_kernel_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_kernel helper."""

if len(call.args) != 2:
raise ValueError(
f"probe_read_kernel expects 2 args (dst, src), got {len(call.args)}"
)

# Get destination buffer (char array -> i8*)
dst_ptr, dst_size = get_or_create_ptr_from_arg(
func, module, call.args[0], builder, local_sym_tab, map_sym_tab, struct_sym_tab
)

# Get source pointer (evaluate expression)
src_ptr, src_type = get_ptr_from_arg(
call.args[1], func, module, builder, local_sym_tab, map_sym_tab, struct_sym_tab
)

# Emit the helper call
result = emit_probe_read_kernel_call(builder, dst_ptr, dst_size, src_ptr)

logger.info(f"Emitted bpf_probe_read_kernel (size={dst_size})")
return result, ir.IntType(64)


@HelperHandlerRegistry.register(
"random",
param_types=[],
Expand Down
Loading