Skip to content

Commit 4cb5f01

Browse files
r41k0uclaude
andcommitted
Merge origin/master (#105) into fix/packet-pointer-arith
Four keep-both conflicts: PktPtrTy and byte_size added at the same spot in type_deducer, the allocation pass's type_deducer import, and neighbouring entries in test_config.toml and test_signedness_ir.py. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2 parents db89408 + 4662fe6 commit 4cb5f01

66 files changed

Lines changed: 2000 additions & 155 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎docs/getting-started/quickstart.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def trace_open(ctx: c_void_p) -> c_int64:
188188
For network packet processing:
189189

190190
```python
191-
from pythonbpf.helper import XDP_PASS
191+
from vmlinux import XDP_PASS
192192

193193
@section("xdp")
194194
def xdp_pass(ctx: c_void_p) -> c_int64:

‎docs/user-guide/decorators.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def trace_open_return(ctx):
108108
For network packet processing at the earliest point:
109109

110110
```python
111-
from pythonbpf.helper import XDP_PASS
111+
from vmlinux import XDP_PASS
112112
from ctypes import c_void_p, c_int64
113113

114114
@section("xdp")

‎docs/user-guide/structs.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,8 @@ class MyStruct:
290290
```python
291291
from pythonbpf import bpf, struct, map, section
292292
from pythonbpf.maps import RingBuffer
293-
from pythonbpf.helper import ktime, XDP_PASS
293+
from pythonbpf.helper import ktime
294+
from vmlinux import XDP_PASS
294295
from ctypes import c_void_p, c_int64, c_uint8, c_uint16, c_uint32, c_uint64
295296

296297
@bpf

‎examples/xdp_pass.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from pythonbpf import bpf, map, section, bpfglobal, compile, compile_to_ir
2-
from pythonbpf.helper import XDP_PASS
2+
from vmlinux import XDP_PASS
33
from pythonbpf.maps import HashMap
44
from ctypes import c_int64, c_void_p
55

‎pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ python_files = ["test_*.py"]
6363
markers = [
6464
"verifier: requires sudo/root for kernel verifier tests (not run by default)",
6565
"vmlinux: requires vmlinux.py for current kernel",
66+
"verifier_match: substring a verifier-level xfail expects in the rejection",
6667
]
6768
log_cli = false
6869

‎pythonbpf/allocation_pass.py‎

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66
from pythonbpf.helper import HelperHandlerRegistry
77
from pythonbpf.vmlinux_parser.dependency_node import Field
88
from .expr import VmlinuxHandlerRegistry
9-
from pythonbpf.type_deducer import ctypes_to_ir, IntTy, signedness, PktPtrTy
9+
from pythonbpf.type_deducer import (
10+
ctypes_to_ir,
11+
is_ctypes,
12+
IntTy,
13+
signedness,
14+
byte_size,
15+
PktPtrTy,
16+
)
1017
from pythonbpf.expr.packet_pointer import packet_type
1118
from pythonbpf.expr.type_inference import infer_int_type
1219
from pythonbpf.maps import BPFMapType
@@ -111,10 +118,12 @@ def _allocate_for_call(builder, var_name, rval, local_sym_tab, compilation_conte
111118
call_type = rval.func.id
112119

113120
# C type constructors
114-
if call_type in ("c_int32", "c_int64", "c_uint32", "c_uint64", "c_void_p"):
121+
if is_ctypes(call_type) and isinstance(ctypes_to_ir(call_type), ir.IntType):
122+
# Any integer ctypes constructor, c_uint16 included, declares a
123+
# slot of that width; the value is converted into it at the store.
115124
ir_type = ctypes_to_ir(call_type)
116125
var = builder.alloca(ir_type, name=var_name)
117-
var.align = ir_type.width // 8
126+
var.align = byte_size(ir_type)
118127
local_sym_tab[var_name] = LocalSymbol(var, ir_type)
119128
logger.info(f"Pre-allocated {var_name} as {call_type}")
120129

@@ -207,7 +216,7 @@ def _allocate_for_map_method(
207216
return
208217

209218
map_params = map_sym_tab[map_name].params
210-
if map_params["type"] != BPFMapType.HASH:
219+
if map_params["type"] not in (BPFMapType.HASH, BPFMapType.ARRAY):
211220
logger.warning(
212221
"Map method lookup used on non-hash map, using fallback allocation"
213222
)
@@ -481,7 +490,7 @@ def _allocate_for_attribute(
481490
tmp_name = f"{struct_var}_{field_name}_tmp"
482491
tmp_ir_type = ir.IntType(field_size_bits)
483492
tmp_var = builder.alloca(tmp_ir_type, name=tmp_name)
484-
tmp_var.align = tmp_ir_type.width // 8
493+
tmp_var.align = byte_size(tmp_ir_type)
485494
local_sym_tab[tmp_name] = LocalSymbol(tmp_var, tmp_ir_type)
486495
logger.info(
487496
f"Pre-allocated temp {tmp_name} (i{field_size_bits}) for vmlinux field read {vmlinux_struct_name}.{field_name}"
@@ -536,8 +545,8 @@ def _allocate_with_type(builder, var_name, ir_type):
536545
def _get_alignment(ir_type):
537546
"""Get appropriate alignment for IR type."""
538547
if isinstance(ir_type, ir.IntType):
539-
return ir_type.width // 8
548+
return byte_size(ir_type)
540549
elif isinstance(ir_type, ir.ArrayType) and isinstance(ir_type.element, ir.IntType):
541-
return ir_type.element.width // 8
550+
return byte_size(ir_type.element)
542551
else:
543552
return 8 # Default: pointer size

‎pythonbpf/assign_pass.py‎

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from llvmlite import ir
66
from pythonbpf.expr import eval_expr, convert
77
from pythonbpf.helper import emit_probe_read_kernel_str_call
8-
from pythonbpf.type_deducer import ctypes_to_ir
8+
from pythonbpf.type_deducer import field_int_type
99
from pythonbpf.vmlinux_parser.dependency_node import Field
1010

1111
logger = logging.getLogger(__name__)
@@ -40,6 +40,10 @@ def handle_struct_field_assignment(
4040
return
4141

4242
val, val_type = val_result
43+
if isinstance(val_type, Field):
44+
field_ty = field_int_type(val_type)
45+
if field_ty is not None:
46+
val_type = field_ty
4347

4448
# Special case: i8* string to [N x i8] char array
4549
if _is_char_array(field_type) and _is_i8_ptr(val_type):
@@ -150,6 +154,13 @@ def handle_variable_assignment(
150154
logger.info(
151155
f"Evaluated value for {var_name}: {val} of type {val_type}, expected {var_type}"
152156
)
157+
# An integer vmlinux field is, for conversion purposes, its declared IntTy
158+
# (width and sign from the ctype), so it takes the same convert() path as
159+
# every other integer below instead of a special case.
160+
if isinstance(val_type, Field):
161+
field_ty = field_int_type(val_type)
162+
if field_ty is not None:
163+
val_type = field_ty
153164

154165
if isinstance(val_type, ir.IntType) and isinstance(var_type, ir.IntType):
155166
# The descriptor may be narrower than the constant carrying the value
@@ -194,33 +205,13 @@ def handle_variable_assignment(
194205
)
195206
return False
196207
if isinstance(val_type, Field):
197-
logger.info("Handling assignment to struct field")
198-
field_ir_type = ctypes_to_ir(val_type.type.__name__)
199-
# Sub-register-width context fields are zero-extended to i64 by
200-
# load_ctx_field, so val is already i64 even though the field type
201-
# says otherwise (c_uint for xdp_md, c_ushort for pt_regs.cs/ss).
202-
if (
203-
isinstance(field_ir_type, ir.IntType)
204-
and field_ir_type.width < 64
205-
and isinstance(var_type, ir.IntType)
206-
and var_type.width == 64
207-
):
208-
builder.store(val, var_ptr)
209-
logger.info(
210-
f"Assigned zero-extended i{field_ir_type.width} context field "
211-
f"to {var_name} (i64)"
212-
)
213-
return True
214-
# TODO: handling only ctype struct fields for now. Handle other stuff too later.
215-
elif var_type == field_ir_type:
216-
builder.store(val, var_ptr)
217-
logger.info(f"Assigned ctype struct field to {var_name}")
218-
return True
219-
else:
220-
logger.error(
221-
f"Failed to assign ctype struct field to {var_name}: {val_type} != {var_type}"
222-
)
223-
return False
208+
# Integer fields were normalised to their IntTy above; what is
209+
# left is a pointer, array or struct field, which has no path
210+
# into this slot.
211+
logger.error(
212+
f"Failed to assign ctype struct field to {var_name}: {val_type} != {var_type}"
213+
)
214+
return False
224215
elif isinstance(val_type, ir.IntType) and isinstance(var_type, ir.PointerType):
225216
# NOTE: This is assignment to a PTR_TO_MAP_VALUE_OR_NULL
226217
logger.info(

‎pythonbpf/debuginfo/debug_info_generator.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@ def get_basic_type(self, name: str, size: int, encoding: int) -> Any:
4949
)
5050
return self._type_cache[key]
5151

52+
def get_int_type(self, ty) -> Any:
53+
"""Debug type for an integer IR type, by width and sign (read from the
54+
IntTy descriptor, signed for a plain ir.IntType). A 1-bit type is C's
55+
_Bool, which occupies one byte. BTF, and so a map's key and value
56+
sizes, come from this, so the width must be the real one."""
57+
from pythonbpf.type_deducer import signedness
58+
59+
width, signed = ty.width, signedness(ty)
60+
if width == 1:
61+
return self.get_basic_type("_Bool", 8, dc.DW_ATE_boolean)
62+
base = {8: "char", 16: "short", 32: "int", 64: "long long"}.get(width)
63+
if base is None:
64+
raise ValueError(f"no debug type for a {width}-bit integer")
65+
if width == 8:
66+
encoding = dc.DW_ATE_signed_char if signed else dc.DW_ATE_unsigned_char
67+
else:
68+
encoding = dc.DW_ATE_signed if signed else dc.DW_ATE_unsigned
69+
return self.get_basic_type(
70+
base if signed else f"unsigned {base}", width, encoding
71+
)
72+
5273
def get_uint8_type(self) -> Any:
5374
"""Get debug info for signed 8-bit integer"""
5475
return self.get_basic_type("char", 8, dc.DW_ATE_unsigned)

‎pythonbpf/expr/expr_pass.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ def get_typed_operand(func, compilation_context, operand, builder, local_sym_tab
263263
vmlinux_result = VmlinuxHandlerRegistry.handle_name(operand.id)
264264
if vmlinux_result is not None:
265265
return vmlinux_result # (i64 constant, its C rank)
266+
raise SyntaxError(f"Undefined variable {operand.id}")
266267
elif isinstance(operand, ast.Constant):
267268
if isinstance(operand.value, (int, bool)):
268269
v = int(operand.value)

‎pythonbpf/functions/functions_pass.py‎

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
LocalSymbol,
3131
)
3232
from .function_debug_info import generate_function_debug_info
33-
from .return_utils import handle_none_return, handle_xdp_return, is_xdp_name
33+
from .return_utils import handle_none_return
3434
from .function_metadata import get_probe_string, is_global_function, infer_return_type
3535

3636

@@ -341,20 +341,6 @@ def handle_return(
341341
logger.info(f"Handling return statement: {ast.dump(stmt)}")
342342
if stmt.value is None:
343343
return handle_none_return(builder)
344-
elif (
345-
isinstance(stmt.value, ast.Name)
346-
and is_xdp_name(stmt.value.id)
347-
and stmt.value.id not in local_sym_tab
348-
and (
349-
compilation_context is None
350-
or stmt.value.id not in compilation_context.bpf_globals
351-
)
352-
):
353-
# The XDP fast path resolves names like XDP_PASS from the helper
354-
# constant table, but only as a fallback: a local or @bpfglobal of the
355-
# same name shadows it, mirroring C (a local shadows an enum constant)
356-
# and the resolution order everywhere else in the compiler.
357-
return handle_xdp_return(stmt, builder, ret_type)
358344
else:
359345
# Fallback for now if ctx not passed, but caller should pass it
360346
if compilation_context is None:

0 commit comments

Comments
 (0)