Skip to content

Commit 1888681

Browse files
Merge pull request #68 from pythonbpf/request-struct
Support enough machinery to make request struct work
2 parents 9def969 + a2de15f commit 1888681

File tree

16 files changed

+729
-164
lines changed

16 files changed

+729
-164
lines changed

pythonbpf/allocation_pass.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,18 @@ def _allocate_for_call(
118118
local_sym_tab[var_name] = LocalSymbol(var, struct_info.ir_type, call_type)
119119
logger.info(f"Pre-allocated {var_name} for struct {call_type}")
120120

121+
elif VmlinuxHandlerRegistry.is_vmlinux_struct(call_type):
122+
# When calling struct_name(pointer), we're doing a cast, not construction
123+
# So we allocate as a pointer (i64) not as the actual struct
124+
var = builder.alloca(ir.IntType(64), name=var_name)
125+
var.align = 8
126+
local_sym_tab[var_name] = LocalSymbol(
127+
var, ir.IntType(64), VmlinuxHandlerRegistry.get_struct_type(call_type)
128+
)
129+
logger.info(
130+
f"Pre-allocated {var_name} for vmlinux struct pointer cast to {call_type}"
131+
)
132+
121133
else:
122134
logger.warning(f"Unknown call type for allocation: {call_type}")
123135

@@ -325,13 +337,6 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_
325337
VmlinuxHandlerRegistry.get_field_type(vmlinux_struct_name, field_name)
326338
)
327339
field_ir, field = field_type
328-
# TODO: For now, we only support integer type allocations.
329-
# This always assumes first argument of function to be the context struct
330-
base_ptr = builder.function.args[0]
331-
local_sym_tab[
332-
struct_var
333-
].var = base_ptr # This is repurposing of var to store the pointer of the base type
334-
local_sym_tab[struct_var].ir_type = field_ir
335340

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

389-
# Allocate with the actual IR type, not the GlobalVariable
394+
# Allocate with the actual IR type
390395
var = _allocate_with_type(builder, var_name, actual_ir_type)
391-
local_sym_tab[var_name] = LocalSymbol(var, actual_ir_type, field)
396+
local_sym_tab[var_name] = LocalSymbol(
397+
var, actual_ir_type, field
398+
) # <-- Store Field metadata
392399

393400
logger.info(
394-
f"Pre-allocated {var_name} from vmlinux struct {vmlinux_struct_name}.{field_name}"
401+
f"Pre-allocated {var_name} as {actual_ir_type} from vmlinux struct {vmlinux_struct_name}.{field_name}"
395402
)
396403
return
397404
else:

pythonbpf/assign_pass.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import ast
22
import logging
3+
from inspect import isclass
4+
35
from llvmlite import ir
46
from pythonbpf.expr import eval_expr
57
from pythonbpf.helper import emit_probe_read_kernel_str_call
@@ -148,8 +150,30 @@ def handle_variable_assignment(
148150
return False
149151

150152
val, val_type = val_result
151-
logger.info(f"Evaluated value for {var_name}: {val} of type {val_type}, {var_type}")
153+
logger.info(
154+
f"Evaluated value for {var_name}: {val} of type {val_type}, expected {var_type}"
155+
)
156+
152157
if val_type != var_type:
158+
# Handle vmlinux struct pointers - they're represented as Python classes but are i64 pointers
159+
if isclass(val_type) and (val_type.__module__ == "vmlinux"):
160+
logger.info("Handling vmlinux struct pointer assignment")
161+
# vmlinux struct pointers: val is a pointer, need to convert to i64
162+
if isinstance(var_type, ir.IntType) and var_type.width == 64:
163+
# Convert pointer to i64 using ptrtoint
164+
if isinstance(val.type, ir.PointerType):
165+
val = builder.ptrtoint(val, ir.IntType(64))
166+
logger.info(
167+
"Converted vmlinux struct pointer to i64 using ptrtoint"
168+
)
169+
builder.store(val, var_ptr)
170+
logger.info(f"Assigned vmlinux struct pointer to {var_name} (i64)")
171+
return True
172+
else:
173+
logger.error(
174+
f"Type mismatch: vmlinux struct pointer requires i64, got {var_type}"
175+
)
176+
return False
153177
if isinstance(val_type, Field):
154178
logger.info("Handling assignment to struct field")
155179
# Special handling for struct_xdp_md i32 fields that are zero-extended to i64

pythonbpf/expr/expr_pass.py

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
get_base_type_and_depth,
1313
deref_to_depth,
1414
)
15-
from pythonbpf.vmlinux_parser.assignment_info import Field
1615
from .vmlinux_registry import VmlinuxHandlerRegistry
16+
from ..vmlinux_parser.dependency_node import Field
1717

1818
logger: Logger = logging.getLogger(__name__)
1919

@@ -173,8 +173,16 @@ def _handle_attribute_expr(
173173
return vmlinux_result
174174
else:
175175
raise RuntimeError("Vmlinux struct did not process successfully")
176-
metadata = structs_sym_tab[var_metadata]
177-
if attr_name in metadata.fields:
176+
177+
elif isinstance(var_metadata, Field):
178+
logger.error(
179+
f"Cannot access field '{attr_name}' on already-loaded field value '{var_name}'"
180+
)
181+
return None
182+
183+
# Regular user-defined struct
184+
metadata = structs_sym_tab.get(var_metadata)
185+
if metadata and attr_name in metadata.fields:
178186
gep = metadata.gep(builder, var_ptr, attr_name)
179187
val = builder.load(gep)
180188
field_type = metadata.field_type(attr_name)
@@ -609,6 +617,66 @@ def _handle_boolean_op(
609617
return None
610618

611619

620+
# ============================================================================
621+
# VMLinux casting
622+
# ============================================================================
623+
624+
625+
def _handle_vmlinux_cast(
626+
func,
627+
module,
628+
builder,
629+
expr,
630+
local_sym_tab,
631+
map_sym_tab,
632+
structs_sym_tab=None,
633+
):
634+
# handle expressions such as struct_request(ctx.di) where struct_request is a vmlinux
635+
# struct and ctx.di is a pointer to a struct but is actually represented as a c_uint64
636+
# which needs to be cast to a pointer. This is also a field of another vmlinux struct
637+
"""Handle vmlinux struct cast expressions like struct_request(ctx.di)."""
638+
if len(expr.args) != 1:
639+
logger.info("vmlinux struct cast takes exactly one argument")
640+
return None
641+
642+
# Get the struct name
643+
struct_name = expr.func.id
644+
645+
# Evaluate the argument (e.g., ctx.di which is a c_uint64)
646+
arg_result = eval_expr(
647+
func,
648+
module,
649+
builder,
650+
expr.args[0],
651+
local_sym_tab,
652+
map_sym_tab,
653+
structs_sym_tab,
654+
)
655+
656+
if arg_result is None:
657+
logger.info("Failed to evaluate argument to vmlinux struct cast")
658+
return None
659+
660+
arg_val, arg_type = arg_result
661+
# Get the vmlinux struct type
662+
vmlinux_struct_type = VmlinuxHandlerRegistry.get_struct_type(struct_name)
663+
if vmlinux_struct_type is None:
664+
logger.error(f"Failed to get vmlinux struct type for {struct_name}")
665+
return None
666+
# Cast the integer/value to a pointer to the struct
667+
# If arg_val is an integer type, we need to inttoptr it
668+
ptr_type = ir.PointerType()
669+
# TODO: add a integer check here later
670+
if ctypes_to_ir(arg_type.type.__name__):
671+
# Cast integer to pointer
672+
casted_ptr = builder.inttoptr(arg_val, ptr_type)
673+
else:
674+
logger.error(f"Unsupported type for vmlinux cast: {arg_type}")
675+
return None
676+
677+
return casted_ptr, vmlinux_struct_type
678+
679+
612680
# ============================================================================
613681
# Expression Dispatcher
614682
# ============================================================================
@@ -629,6 +697,18 @@ def eval_expr(
629697
elif isinstance(expr, ast.Constant):
630698
return _handle_constant_expr(module, builder, expr)
631699
elif isinstance(expr, ast.Call):
700+
if isinstance(expr.func, ast.Name) and VmlinuxHandlerRegistry.is_vmlinux_struct(
701+
expr.func.id
702+
):
703+
return _handle_vmlinux_cast(
704+
func,
705+
module,
706+
builder,
707+
expr,
708+
local_sym_tab,
709+
map_sym_tab,
710+
structs_sym_tab,
711+
)
632712
if isinstance(expr.func, ast.Name) and expr.func.id == "deref":
633713
return _handle_deref_call(expr, local_sym_tab, builder)
634714

pythonbpf/helper/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
from .helper_registry import HelperHandlerRegistry
22
from .helper_utils import reset_scratch_pool
3-
from .bpf_helper_handler import handle_helper_call, emit_probe_read_kernel_str_call
3+
from .bpf_helper_handler import (
4+
handle_helper_call,
5+
emit_probe_read_kernel_str_call,
6+
emit_probe_read_kernel_call,
7+
)
48
from .helpers import (
59
ktime,
610
pid,
@@ -74,6 +78,7 @@ def helper_call_handler(
7478
"reset_scratch_pool",
7579
"handle_helper_call",
7680
"emit_probe_read_kernel_str_call",
81+
"emit_probe_read_kernel_call",
7782
"ktime",
7883
"pid",
7984
"deref",

pythonbpf/helper/bpf_helper_handler.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class BPFHelperID(Enum):
3434
BPF_PERF_EVENT_OUTPUT = 25
3535
BPF_GET_STACK = 67
3636
BPF_PROBE_READ_KERNEL_STR = 115
37+
BPF_PROBE_READ_KERNEL = 113
3738
BPF_RINGBUF_OUTPUT = 130
3839
BPF_RINGBUF_RESERVE = 131
3940
BPF_RINGBUF_SUBMIT = 132
@@ -574,6 +575,75 @@ def bpf_probe_read_kernel_str_emitter(
574575
return result, ir.IntType(64)
575576

576577

578+
def emit_probe_read_kernel_call(builder, dst_ptr, dst_size, src_ptr):
579+
"""Emit LLVM IR call to bpf_probe_read_kernel"""
580+
581+
fn_type = ir.FunctionType(
582+
ir.IntType(64),
583+
[ir.PointerType(), ir.IntType(32), ir.PointerType()],
584+
var_arg=False,
585+
)
586+
fn_ptr = builder.inttoptr(
587+
ir.Constant(ir.IntType(64), BPFHelperID.BPF_PROBE_READ_KERNEL.value),
588+
ir.PointerType(fn_type),
589+
)
590+
591+
result = builder.call(
592+
fn_ptr,
593+
[
594+
builder.bitcast(dst_ptr, ir.PointerType()),
595+
ir.Constant(ir.IntType(32), dst_size),
596+
builder.bitcast(src_ptr, ir.PointerType()),
597+
],
598+
tail=False,
599+
)
600+
601+
logger.info(f"Emitted bpf_probe_read_kernel (size={dst_size})")
602+
return result
603+
604+
605+
@HelperHandlerRegistry.register(
606+
"probe_read_kernel",
607+
param_types=[
608+
ir.PointerType(ir.IntType(8)),
609+
ir.PointerType(ir.IntType(8)),
610+
],
611+
return_type=ir.IntType(64),
612+
)
613+
def bpf_probe_read_kernel_emitter(
614+
call,
615+
map_ptr,
616+
module,
617+
builder,
618+
func,
619+
local_sym_tab=None,
620+
struct_sym_tab=None,
621+
map_sym_tab=None,
622+
):
623+
"""Emit LLVM IR for bpf_probe_read_kernel helper."""
624+
625+
if len(call.args) != 2:
626+
raise ValueError(
627+
f"probe_read_kernel expects 2 args (dst, src), got {len(call.args)}"
628+
)
629+
630+
# Get destination buffer (char array -> i8*)
631+
dst_ptr, dst_size = get_or_create_ptr_from_arg(
632+
func, module, call.args[0], builder, local_sym_tab, map_sym_tab, struct_sym_tab
633+
)
634+
635+
# Get source pointer (evaluate expression)
636+
src_ptr, src_type = get_ptr_from_arg(
637+
call.args[1], func, module, builder, local_sym_tab, map_sym_tab, struct_sym_tab
638+
)
639+
640+
# Emit the helper call
641+
result = emit_probe_read_kernel_call(builder, dst_ptr, dst_size, src_ptr)
642+
643+
logger.info(f"Emitted bpf_probe_read_kernel (size={dst_size})")
644+
return result, ir.IntType(64)
645+
646+
577647
@HelperHandlerRegistry.register(
578648
"random",
579649
param_types=[],

0 commit comments

Comments
 (0)