Skip to content

Commit a8595ff

Browse files
feat: allocate tmp variable for pointer to vmlinux struct field access.
1 parent d43d3ad commit a8595ff

File tree

3 files changed

+48
-16
lines changed

3 files changed

+48
-16
lines changed

pythonbpf/allocation_pass.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_
384384
f"Could not determine size for ctypes field {field_name}: {e}"
385385
)
386386
actual_ir_type = ir.IntType(64)
387+
field_size_bits = 64
387388

388389
# Check if it's a nested vmlinux struct or complex type
389390
elif field.type.__module__ == "vmlinux":
@@ -392,23 +393,34 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_
392393
field.ctype_complex_type, ctypes._Pointer
393394
):
394395
actual_ir_type = ir.IntType(64) # Pointer is always 64-bit
396+
field_size_bits = 64
395397
# For embedded structs, this is more complex - might need different handling
396398
else:
397399
logger.warning(
398400
f"Field {field_name} is a nested vmlinux struct, using i64 for now"
399401
)
400402
actual_ir_type = ir.IntType(64)
403+
field_size_bits = 64
401404
else:
402405
logger.warning(
403406
f"Unknown field type module {field.type.__module__} for {field_name}"
404407
)
405408
actual_ir_type = ir.IntType(64)
409+
field_size_bits = 64
410+
411+
# Pre-allocate the tmp storage used by load_struct_field (so we don't alloca inside handler)
412+
tmp_name = f"{struct_var}_{field_name}_tmp"
413+
tmp_ir_type = ir.IntType(field_size_bits)
414+
tmp_var = builder.alloca(tmp_ir_type, name=tmp_name)
415+
tmp_var.align = tmp_ir_type.width // 8
416+
local_sym_tab[tmp_name] = LocalSymbol(tmp_var, tmp_ir_type)
417+
logger.info(
418+
f"Pre-allocated temp {tmp_name} (i{field_size_bits}) for vmlinux field read {vmlinux_struct_name}.{field_name}"
419+
)
406420

407-
# Allocate with the actual IR type
421+
# Allocate with the actual IR type for the destination var
408422
var = _allocate_with_type(builder, var_name, actual_ir_type)
409-
local_sym_tab[var_name] = LocalSymbol(
410-
var, actual_ir_type, field
411-
) # <-- Store Field metadata
423+
local_sym_tab[var_name] = LocalSymbol(var, actual_ir_type, field)
412424

413425
logger.info(
414426
f"Pre-allocated {var_name} as {actual_ir_type} from vmlinux struct {vmlinux_struct_name}.{field_name}"

pythonbpf/vmlinux_parser/vmlinux_exports_handler.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def handle_vmlinux_enum(self, name):
7777
return None
7878

7979
def get_vmlinux_enum_value(self, name):
80-
"""Handle vmlinux enum constants by returning LLVM IR constants"""
80+
"""Handle vmlinux.enum constants by returning LLVM IR constants"""
8181
if self.is_vmlinux_enum(name):
8282
value = self.vmlinux_symtab[name].value
8383
logger.info(f"The value of vmlinux enum {name} = {value}")
@@ -119,6 +119,9 @@ def handle_vmlinux_struct_field(
119119
# Load the struct pointer from the local variable
120120
struct_ptr = builder.load(var_info.var)
121121

122+
# Determine the preallocated tmp name that assignment pass should have created
123+
tmp_name = f"{struct_var_name}_{field_name}_tmp"
124+
122125
# Use bpf_probe_read_kernel for non-context struct field access
123126
field_value = self.load_struct_field(
124127
builder,
@@ -127,6 +130,7 @@ def handle_vmlinux_struct_field(
127130
field_data,
128131
struct_name,
129132
local_sym_tab,
133+
tmp_name,
130134
)
131135
# Return field value and field type
132136
return field_value, field_data
@@ -141,6 +145,7 @@ def load_struct_field(
141145
field_data,
142146
struct_name=None,
143147
local_sym_tab=None,
148+
tmp_name: str = None,
144149
):
145150
"""
146151
Generate LLVM IR to load a field from a regular (non-context) struct using bpf_probe_read_kernel.
@@ -151,6 +156,8 @@ def load_struct_field(
151156
offset_global: Global variable containing the field offset (i64)
152157
field_data: contains data about the field
153158
struct_name: Name of the struct being accessed (optional)
159+
local_sym_tab: symbol table (optional) - used to locate preallocated tmp storage
160+
tmp_name: name of the preallocated temporary storage to use (preferred)
154161
Returns:
155162
The loaded value
156163
"""
@@ -213,10 +220,20 @@ def load_struct_field(
213220
else:
214221
logger.warning("Complex vmlinux field type, using default 64 bits")
215222

216-
# Allocate local storage for the field value
217-
# TODO: CRITICAL BUG. alloca cannot be used anywhere other than the basic block
218-
local_storage = builder.alloca(ir.IntType(int_width))
219-
local_storage_i8_ptr = builder.bitcast(local_storage, i8_ptr_type)
223+
# Use preallocated temporary storage if provided by allocation pass
224+
225+
local_storage_i8_ptr = None
226+
if tmp_name and local_sym_tab and tmp_name in local_sym_tab:
227+
# Expect the tmp to be an alloca created during allocation pass
228+
tmp_alloca = local_sym_tab[tmp_name].var
229+
local_storage_i8_ptr = builder.bitcast(tmp_alloca, i8_ptr_type)
230+
else:
231+
# Fallback: allocate inline (not ideal, but preserves behavior)
232+
local_storage = builder.alloca(ir.IntType(int_width))
233+
local_storage_i8_ptr = builder.bitcast(local_storage, i8_ptr_type)
234+
logger.warning(
235+
f"Temp storage '{tmp_name}' not found. Allocating inline"
236+
)
220237

221238
# Use bpf_probe_read_kernel to safely read the field
222239
# This generates:
@@ -230,7 +247,7 @@ def load_struct_field(
230247
)
231248

232249
# Load the value from local storage
233-
value = builder.load(local_storage)
250+
value = builder.load(builder.bitcast(local_storage_i8_ptr, ir.PointerType(ir.IntType(int_width))))
234251

235252
# Zero-extend i32 to i64 if needed
236253
if needs_zext:

tests/failing_tests/xdp/xdp_test_1.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,18 @@ class iphdr:
2626
def ip_detector(ctx: struct_xdp_md) -> c_int64:
2727
data = ctx.data
2828
data_end = ctx.data_end
29-
eth = struct_ethhdr(ctx.data)
30-
nh = ctx.data + 14
29+
if data + 14 > data_end:
30+
return c_int64(XDP_DROP)
31+
32+
eth = struct_ethhdr(data)
33+
nh = data + 14
3134
if nh + 20 > data_end:
3235
return c_int64(XDP_DROP)
36+
3337
iph = iphdr(nh)
34-
h_proto = eth.h_proto
35-
h_proto_ext = c_int64(h_proto)
36-
ipv4 = iph.saddr
37-
print(f"ipaddress: {ipv4}")
38+
39+
print(f"ipaddress: {iph.saddr}")
40+
3841
return c_int64(XDP_PASS)
3942

4043

0 commit comments

Comments
 (0)