Skip to content

Commit 377fa40

Browse files
add regular struct field access handling in vmlinux_registry.py
1 parent 99321c7 commit 377fa40

File tree

2 files changed

+112
-9
lines changed

2 files changed

+112
-9
lines changed

pythonbpf/vmlinux_parser/vmlinux_exports_handler.py

Lines changed: 110 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,119 @@ def handle_vmlinux_struct_field(
9494
f"Attempting to access field {field_name} of possible vmlinux struct {struct_var_name}"
9595
)
9696
python_type: type = var_info.metadata
97-
struct_name = python_type.__name__
98-
globvar_ir, field_data = self.get_field_type(struct_name, field_name)
99-
builder.function.args[0].type = ir.PointerType(ir.IntType(8))
100-
field_ptr = self.load_ctx_field(
101-
builder, builder.function.args[0], globvar_ir, field_data, struct_name
102-
)
103-
# Return pointer to field and field type
104-
return field_ptr, field_data
97+
# Check if this is a context field (ctx) or a cast struct
98+
is_context_field = var_info.var is None
99+
100+
if is_context_field:
101+
# Handle context field access (original behavior)
102+
struct_name = python_type.__name__
103+
globvar_ir, field_data = self.get_field_type(struct_name, field_name)
104+
builder.function.args[0].type = ir.PointerType(ir.IntType(8))
105+
field_ptr = self.load_ctx_field(
106+
builder, builder.function.args[0], globvar_ir, field_data, struct_name
107+
)
108+
return field_ptr, field_data
109+
else:
110+
# Handle cast struct field access
111+
struct_name = python_type.__name__
112+
globvar_ir, field_data = self.get_field_type(struct_name, field_name)
113+
114+
# Handle cast struct field access (use standard GEP)
115+
# Load the struct pointer from the local variable
116+
struct_ptr = builder.load(var_info.var)
117+
118+
# Use standard GEP for non-context struct field access
119+
field_value = self.load_struct_field(
120+
builder, struct_ptr, globvar_ir, field_data, struct_name
121+
)
122+
# Return field value and field type
123+
return field_value, field_data
105124
else:
106125
raise RuntimeError("Variable accessed not found in symbol table")
107126

127+
@staticmethod
128+
def load_struct_field(builder, struct_ptr_int, offset_global, field_data, struct_name=None):
129+
"""
130+
Generate LLVM IR to load a field from a regular (non-context) struct using standard GEP.
131+
132+
Args:
133+
builder: llvmlite IRBuilder instance
134+
struct_ptr_int: The struct pointer as an i64 value (already loaded from alloca)
135+
offset_global: Global variable containing the field offset (i64)
136+
field_data: contains data about the field
137+
struct_name: Name of the struct being accessed (optional)
138+
Returns:
139+
The loaded value
140+
"""
141+
142+
# Load the offset value
143+
offset = builder.load(offset_global)
144+
145+
# Convert i64 to pointer type (BPF stores pointers as i64)
146+
i8_ptr_type = ir.PointerType(ir.IntType(8))
147+
struct_ptr = builder.inttoptr(struct_ptr_int, i8_ptr_type)
148+
149+
# GEP with offset to get field pointer
150+
field_ptr = builder.gep(
151+
struct_ptr,
152+
[offset],
153+
inbounds=False,
154+
)
155+
156+
# Determine the appropriate IR type based on field information
157+
int_width = 64 # Default to 64-bit
158+
needs_zext = False
159+
160+
if field_data is not None:
161+
# Try to determine the size from field metadata
162+
if field_data.type.__module__ == ctypes.__name__:
163+
try:
164+
field_size_bytes = ctypes.sizeof(field_data.type)
165+
field_size_bits = field_size_bytes * 8
166+
167+
if field_size_bits in [8, 16, 32, 64]:
168+
int_width = field_size_bits
169+
logger.info(f"Determined field size: {int_width} bits")
170+
171+
# Special handling for struct_xdp_md i32 fields
172+
if struct_name == "struct_xdp_md" and int_width == 32:
173+
needs_zext = True
174+
logger.info(
175+
"struct_xdp_md i32 field detected, will zero-extend to i64"
176+
)
177+
else:
178+
logger.warning(
179+
f"Unusual field size {field_size_bits} bits, using default 64"
180+
)
181+
except Exception as e:
182+
logger.warning(
183+
f"Could not determine field size: {e}, using default 64"
184+
)
185+
186+
elif field_data.type.__module__ == "vmlinux":
187+
# For pointers to structs or complex vmlinux types
188+
if field_data.ctype_complex_type is not None and issubclass(
189+
field_data.ctype_complex_type, ctypes._Pointer
190+
):
191+
int_width = 64 # Pointers are always 64-bit
192+
logger.info("Field is a pointer type, using 64 bits")
193+
else:
194+
logger.warning("Complex vmlinux field type, using default 64 bits")
195+
196+
# Bitcast to appropriate pointer type based on determined width
197+
ptr_type = ir.PointerType(ir.IntType(int_width))
198+
typed_ptr = builder.bitcast(field_ptr, ptr_type)
199+
200+
# Load the value
201+
value = builder.load(typed_ptr)
202+
203+
# Zero-extend i32 to i64 if needed
204+
if needs_zext:
205+
value = builder.zext(value, ir.IntType(64))
206+
logger.info("Zero-extended i32 value to i64")
207+
208+
return value
209+
108210
@staticmethod
109211
def load_ctx_field(builder, ctx_arg, offset_global, field_data, struct_name=None):
110212
"""

tests/failing_tests/vmlinux/requests.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ def example(ctx: struct_pt_regs) -> c_int64:
1010
a = ctx.r15
1111
req = struct_request(ctx.di)
1212
d = req.__data_len
13+
b = ctx.r12
1314
c = req.timeout
14-
print(f"data length {d} and {c} and {a}")
15+
print(f"data length {d} and {c} and {a} and {b}")
1516
return c_int64(0)
1617

1718

0 commit comments

Comments
 (0)