Skip to content

Commit 0ca8350

Browse files
Merge pull request #74 from pythonbpf/fix-vmlinux-ir-gen
Fix some issues with vmlinux struct usage in syntax TODO: THIS DOES NOT FIX the XDP example. The issue with the XDP example here is that the range is not being tracked due to multiple loads from stack which is making the verifier lose track of the range on that value.
2 parents 127852e + de8c486 commit 0ca8350

File tree

11 files changed

+512
-22
lines changed

11 files changed

+512
-22
lines changed

BCC-Examples/disksnoop.ipynb

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"id": "c3520e58-e50f-4bc1-8f9d-a6fecbf6e9f0",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"from vmlinux import struct_request, struct_pt_regs\n",
11+
"from pythonbpf import bpf, section, bpfglobal, map, BPF\n",
12+
"from pythonbpf.helper import ktime\n",
13+
"from pythonbpf.maps import HashMap\n",
14+
"from ctypes import c_int64, c_uint64, c_int32\n",
15+
"\n",
16+
"REQ_WRITE = 1\n",
17+
"\n",
18+
"\n",
19+
"@bpf\n",
20+
"@map\n",
21+
"def start() -> HashMap:\n",
22+
" return HashMap(key=c_uint64, value=c_uint64, max_entries=10240)\n",
23+
"\n",
24+
"\n",
25+
"@bpf\n",
26+
"@section(\"kprobe/blk_mq_end_request\")\n",
27+
"def trace_completion(ctx: struct_pt_regs) -> c_int64:\n",
28+
" # Get request pointer from first argument\n",
29+
" req_ptr = ctx.di\n",
30+
" req = struct_request(ctx.di)\n",
31+
" # Print: data_len, cmd_flags, latency_us\n",
32+
" data_len = req.__data_len\n",
33+
" cmd_flags = req.cmd_flags\n",
34+
" # Lookup start timestamp\n",
35+
" req_tsp = start.lookup(req_ptr)\n",
36+
" if req_tsp:\n",
37+
" # Calculate delta in nanoseconds\n",
38+
" delta = ktime() - req_tsp\n",
39+
"\n",
40+
" # Convert to microseconds for printing\n",
41+
" delta_us = delta // 1000\n",
42+
"\n",
43+
" print(f\"{data_len} {cmd_flags:x} {delta_us}\\n\")\n",
44+
"\n",
45+
" # Delete the entry\n",
46+
" start.delete(req_ptr)\n",
47+
"\n",
48+
" return c_int64(0)\n",
49+
"\n",
50+
"\n",
51+
"@bpf\n",
52+
"@section(\"kprobe/blk_mq_start_request\")\n",
53+
"def trace_start(ctx1: struct_pt_regs) -> c_int32:\n",
54+
" req = ctx1.di\n",
55+
" ts = ktime()\n",
56+
" start.update(req, ts)\n",
57+
" return c_int32(0)\n",
58+
"\n",
59+
"\n",
60+
"@bpf\n",
61+
"@bpfglobal\n",
62+
"def LICENSE() -> str:\n",
63+
" return \"GPL\"\n",
64+
"\n",
65+
"\n",
66+
"b = BPF()"
67+
]
68+
},
69+
{
70+
"cell_type": "code",
71+
"execution_count": null,
72+
"id": "97040f73-98e0-4993-94c6-125d1b42d931",
73+
"metadata": {},
74+
"outputs": [],
75+
"source": [
76+
"b.load()\n",
77+
"b.attach_all()"
78+
]
79+
},
80+
{
81+
"cell_type": "code",
82+
"execution_count": null,
83+
"id": "b1bd4f51-fa25-42e1-877c-e48a2605189f",
84+
"metadata": {},
85+
"outputs": [],
86+
"source": [
87+
"from pythonbpf import trace_pipe"
88+
]
89+
},
90+
{
91+
"cell_type": "code",
92+
"execution_count": null,
93+
"id": "96b4b59b-b0db-4952-9534-7a714f685089",
94+
"metadata": {},
95+
"outputs": [],
96+
"source": [
97+
"trace_pipe()"
98+
]
99+
}
100+
],
101+
"metadata": {
102+
"kernelspec": {
103+
"display_name": "Python 3 (ipykernel)",
104+
"language": "python",
105+
"name": "python3"
106+
},
107+
"language_info": {
108+
"codemirror_mode": {
109+
"name": "ipython",
110+
"version": 3
111+
},
112+
"file_extension": ".py",
113+
"mimetype": "text/x-python",
114+
"name": "python",
115+
"nbconvert_exporter": "python",
116+
"pygments_lexer": "ipython3",
117+
"version": "3.12.3"
118+
}
119+
},
120+
"nbformat": 4,
121+
"nbformat_minor": 5
122+
}

BCC-Examples/disksnoop.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from vmlinux import struct_request, struct_pt_regs
2+
from pythonbpf import bpf, section, bpfglobal, compile_to_ir, compile, map
3+
from pythonbpf.helper import ktime
4+
from pythonbpf.maps import HashMap
5+
import logging
6+
from ctypes import c_int64, c_uint64, c_int32
7+
8+
# Constants
9+
REQ_WRITE = 1 # from include/linux/blk_types.h
10+
11+
12+
@bpf
13+
@map
14+
def start() -> HashMap:
15+
return HashMap(key=c_uint64, value=c_uint64, max_entries=10240)
16+
17+
18+
@bpf
19+
@section("kprobe/blk_mq_end_request")
20+
def trace_completion(ctx: struct_pt_regs) -> c_int64:
21+
# Get request pointer from first argument
22+
req_ptr = ctx.di
23+
req = struct_request(ctx.di)
24+
# Print: data_len, cmd_flags, latency_us
25+
data_len = req.__data_len
26+
cmd_flags = req.cmd_flags
27+
# Lookup start timestamp
28+
req_tsp = start.lookup(req_ptr)
29+
if req_tsp:
30+
# Calculate delta in nanoseconds
31+
delta = ktime() - req_tsp
32+
33+
# Convert to microseconds for printing
34+
delta_us = delta // 1000
35+
36+
print(f"{data_len} {cmd_flags:x} {delta_us}\n")
37+
38+
# Delete the entry
39+
start.delete(req_ptr)
40+
41+
return c_int64(0)
42+
43+
44+
@bpf
45+
@section("kprobe/blk_mq_start_request")
46+
def trace_start(ctx1: struct_pt_regs) -> c_int32:
47+
req = ctx1.di
48+
ts = ktime()
49+
start.update(req, ts)
50+
return c_int32(0)
51+
52+
53+
@bpf
54+
@bpfglobal
55+
def LICENSE() -> str:
56+
return "GPL"
57+
58+
59+
if __name__ == "__main__":
60+
compile_to_ir("disksnoop.py", "disksnoop.ll", loglevel=logging.INFO)
61+
compile()

pythonbpf/allocation_pass.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,22 @@ def _allocate_for_call(
114114
# Struct constructors
115115
elif call_type in structs_sym_tab:
116116
struct_info = structs_sym_tab[call_type]
117-
var = builder.alloca(struct_info.ir_type, name=var_name)
118-
local_sym_tab[var_name] = LocalSymbol(var, struct_info.ir_type, call_type)
119-
logger.info(f"Pre-allocated {var_name} for struct {call_type}")
117+
if len(rval.args) == 0:
118+
# Zero-arg constructor: allocate the struct itself
119+
var = builder.alloca(struct_info.ir_type, name=var_name)
120+
local_sym_tab[var_name] = LocalSymbol(
121+
var, struct_info.ir_type, call_type
122+
)
123+
logger.info(f"Pre-allocated {var_name} for struct {call_type}")
124+
else:
125+
# Pointer cast: allocate as pointer to struct
126+
ptr_type = ir.PointerType(struct_info.ir_type)
127+
var = builder.alloca(ptr_type, name=var_name)
128+
var.align = 8
129+
local_sym_tab[var_name] = LocalSymbol(var, ptr_type, call_type)
130+
logger.info(
131+
f"Pre-allocated {var_name} for struct pointer cast to {call_type}"
132+
)
120133

121134
elif VmlinuxHandlerRegistry.is_vmlinux_struct(call_type):
122135
# When calling struct_name(pointer), we're doing a cast, not construction
@@ -371,6 +384,7 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_
371384
f"Could not determine size for ctypes field {field_name}: {e}"
372385
)
373386
actual_ir_type = ir.IntType(64)
387+
field_size_bits = 64
374388

375389
# Check if it's a nested vmlinux struct or complex type
376390
elif field.type.__module__ == "vmlinux":
@@ -379,23 +393,34 @@ def _allocate_for_attribute(builder, var_name, rval, local_sym_tab, structs_sym_
379393
field.ctype_complex_type, ctypes._Pointer
380394
):
381395
actual_ir_type = ir.IntType(64) # Pointer is always 64-bit
396+
field_size_bits = 64
382397
# For embedded structs, this is more complex - might need different handling
383398
else:
384399
logger.warning(
385400
f"Field {field_name} is a nested vmlinux struct, using i64 for now"
386401
)
387402
actual_ir_type = ir.IntType(64)
403+
field_size_bits = 64
388404
else:
389405
logger.warning(
390406
f"Unknown field type module {field.type.__module__} for {field_name}"
391407
)
392408
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+
)
393420

394-
# Allocate with the actual IR type
421+
# Allocate with the actual IR type for the destination var
395422
var = _allocate_with_type(builder, var_name, actual_ir_type)
396-
local_sym_tab[var_name] = LocalSymbol(
397-
var, actual_ir_type, field
398-
) # <-- Store Field metadata
423+
local_sym_tab[var_name] = LocalSymbol(var, actual_ir_type, field)
399424

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

pythonbpf/assign_pass.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,23 @@ def handle_variable_assignment(
174174
f"Type mismatch: vmlinux struct pointer requires i64, got {var_type}"
175175
)
176176
return False
177+
# Handle user-defined struct pointer casts
178+
# val_type is a string (struct name), var_type is a pointer to the struct
179+
if isinstance(val_type, str) and val_type in structs_sym_tab:
180+
struct_info = structs_sym_tab[val_type]
181+
expected_ptr_type = ir.PointerType(struct_info.ir_type)
182+
183+
# Check if var_type matches the expected pointer type
184+
if isinstance(var_type, ir.PointerType) and var_type == expected_ptr_type:
185+
# val is already the correct pointer type from inttoptr/bitcast
186+
builder.store(val, var_ptr)
187+
logger.info(f"Assigned user-defined struct pointer cast to {var_name}")
188+
return True
189+
else:
190+
logger.error(
191+
f"Type mismatch: user-defined struct pointer cast requires pointer type, got {var_type}"
192+
)
193+
return False
177194
if isinstance(val_type, Field):
178195
logger.info("Handling assignment to struct field")
179196
# Special handling for struct_xdp_md i32 fields that are zero-extended to i64

0 commit comments

Comments
 (0)