Skip to content

Commit 009b11a

Browse files
committed
Implement bpf_probe_read_kernel_str helper, Allow i8* to i8 ArrayType conversion
1 parent 9fc3c85 commit 009b11a

File tree

5 files changed

+223
-11
lines changed

5 files changed

+223
-11
lines changed

pythonbpf/assign_pass.py

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
from llvmlite import ir
44
from pythonbpf.expr import eval_expr
5+
from pythonbpf.helper import emit_probe_read_kernel_str_call
56

67
logger = logging.getLogger(__name__)
78

@@ -27,27 +28,82 @@ def handle_struct_field_assignment(
2728

2829
# Get field pointer and evaluate value
2930
field_ptr = struct_info.gep(builder, local_sym_tab[var_name].var, field_name)
30-
val = eval_expr(
31+
field_type = struct_info.field_type(field_name)
32+
val_result = eval_expr(
3133
func, module, builder, rval, local_sym_tab, map_sym_tab, structs_sym_tab
3234
)
3335

34-
if val is None:
36+
if val_result is None:
3537
logger.error(f"Failed to evaluate value for {var_name}.{field_name}")
3638
return
3739

38-
# TODO: Handle string assignment to char array (not a priority)
39-
field_type = struct_info.field_type(field_name)
40-
if isinstance(field_type, ir.ArrayType) and val[1] == ir.PointerType(ir.IntType(8)):
41-
logger.warning(
42-
f"String to char array assignment not implemented for {var_name}.{field_name}"
40+
val, val_type = val_result
41+
42+
# Special case: i8* string to [N x i8] char array
43+
if _is_char_array(field_type) and _is_i8_ptr(val_type):
44+
_copy_string_to_char_array(
45+
func,
46+
module,
47+
builder,
48+
val,
49+
field_ptr,
50+
field_type,
51+
local_sym_tab,
52+
map_sym_tab,
53+
structs_sym_tab,
4354
)
55+
logger.info(f"Copied string to char array {var_name}.{field_name}")
4456
return
4557

46-
# Store the value
47-
builder.store(val[0], field_ptr)
58+
# Regular assignment
59+
builder.store(val, field_ptr)
4860
logger.info(f"Assigned to struct field {var_name}.{field_name}")
4961

5062

63+
def _copy_string_to_char_array(
64+
func,
65+
module,
66+
builder,
67+
src_ptr,
68+
dst_ptr,
69+
array_type,
70+
local_sym_tab,
71+
map_sym_tab,
72+
struct_sym_tab,
73+
):
74+
"""Copy string (i8*) to char array ([N x i8]) using bpf_probe_read_kernel_str"""
75+
76+
array_size = array_type.count
77+
78+
# Get pointer to first element: [N x i8]* -> i8*
79+
dst_i8_ptr = builder.gep(
80+
dst_ptr,
81+
[ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)],
82+
inbounds=True,
83+
)
84+
85+
# Use the shared emitter function
86+
emit_probe_read_kernel_str_call(builder, dst_i8_ptr, array_size, src_ptr)
87+
88+
89+
def _is_char_array(ir_type):
90+
"""Check if type is [N x i8]."""
91+
return (
92+
isinstance(ir_type, ir.ArrayType)
93+
and isinstance(ir_type.element, ir.IntType)
94+
and ir_type.element.width == 8
95+
)
96+
97+
98+
def _is_i8_ptr(ir_type):
99+
"""Check if type is i8*."""
100+
return (
101+
isinstance(ir_type, ir.PointerType)
102+
and isinstance(ir_type.pointee, ir.IntType)
103+
and ir_type.pointee.width == 8
104+
)
105+
106+
51107
def handle_variable_assignment(
52108
func, module, builder, var_name, rval, local_sym_tab, map_sym_tab, structs_sym_tab
53109
):

pythonbpf/helper/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .helper_registry import HelperHandlerRegistry
22
from .helper_utils import reset_scratch_pool
3-
from .bpf_helper_handler import handle_helper_call
4-
from .helpers import ktime, pid, deref, comm, XDP_DROP, XDP_PASS
3+
from .bpf_helper_handler import handle_helper_call, emit_probe_read_kernel_str_call
4+
from .helpers import ktime, pid, deref, comm, probe_read_str, XDP_DROP, XDP_PASS
55

66

77
# Register the helper handler with expr module
@@ -59,10 +59,12 @@ def helper_call_handler(
5959
"HelperHandlerRegistry",
6060
"reset_scratch_pool",
6161
"handle_helper_call",
62+
"emit_probe_read_kernel_str_call",
6263
"ktime",
6364
"pid",
6465
"deref",
6566
"comm",
67+
"probe_read_str",
6668
"XDP_DROP",
6769
"XDP_PASS",
6870
]

pythonbpf/helper/bpf_helper_handler.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
get_flags_val,
99
get_data_ptr_and_size,
1010
get_buffer_ptr_and_size,
11+
get_char_array_ptr_and_size,
12+
get_ptr_from_arg,
1113
)
1214
from .printk_formatter import simple_string_print, handle_fstring_print
1315

@@ -26,6 +28,7 @@ class BPFHelperID(Enum):
2628
BPF_GET_CURRENT_PID_TGID = 14
2729
BPF_GET_CURRENT_COMM = 16
2830
BPF_PERF_EVENT_OUTPUT = 25
31+
BPF_PROBE_READ_KERNEL_STR = 115
2932

3033

3134
@HelperHandlerRegistry.register("ktime")
@@ -368,6 +371,68 @@ def bpf_perf_event_output_handler(
368371
return result, None
369372

370373

374+
def emit_probe_read_kernel_str_call(builder, dst_ptr, dst_size, src_ptr):
375+
"""Emit LLVM IR call to bpf_probe_read_kernel_str"""
376+
377+
fn_type = ir.FunctionType(
378+
ir.IntType(64),
379+
[ir.PointerType(), ir.IntType(32), ir.PointerType()],
380+
var_arg=False,
381+
)
382+
fn_ptr = builder.inttoptr(
383+
ir.Constant(ir.IntType(64), BPFHelperID.BPF_PROBE_READ_KERNEL_STR.value),
384+
ir.PointerType(fn_type),
385+
)
386+
387+
result = builder.call(
388+
fn_ptr,
389+
[
390+
builder.bitcast(dst_ptr, ir.PointerType()),
391+
ir.Constant(ir.IntType(32), dst_size),
392+
builder.bitcast(src_ptr, ir.PointerType()),
393+
],
394+
tail=False,
395+
)
396+
397+
logger.info(f"Emitted bpf_probe_read_kernel_str (size={dst_size})")
398+
return result
399+
400+
401+
@HelperHandlerRegistry.register("probe_read_str")
402+
def bpf_probe_read_kernel_str_emitter(
403+
call,
404+
map_ptr,
405+
module,
406+
builder,
407+
func,
408+
local_sym_tab=None,
409+
struct_sym_tab=None,
410+
map_sym_tab=None,
411+
):
412+
"""Emit LLVM IR for bpf_probe_read_kernel_str helper."""
413+
414+
if len(call.args) != 2:
415+
raise ValueError(
416+
f"probe_read_str expects 2 args (dst, src), got {len(call.args)}"
417+
)
418+
419+
# Get destination buffer (char array -> i8*)
420+
dst_ptr, dst_size = get_char_array_ptr_and_size(
421+
call.args[0], builder, local_sym_tab, struct_sym_tab
422+
)
423+
424+
# Get source pointer (evaluate expression)
425+
src_ptr, src_type = get_ptr_from_arg(
426+
call.args[1], func, module, builder, local_sym_tab, map_sym_tab, struct_sym_tab
427+
)
428+
429+
# Emit the helper call
430+
result = emit_probe_read_kernel_str_call(builder, dst_ptr, dst_size, src_ptr)
431+
432+
logger.info(f"Emitted bpf_probe_read_kernel_str (size={dst_size})")
433+
return result, ir.IntType(64)
434+
435+
371436
def handle_helper_call(
372437
call,
373438
module,

pythonbpf/helper/helper_utils.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from llvmlite import ir
55
from pythonbpf.expr import (
66
get_operand_value,
7+
eval_expr,
78
)
89

910
logger = logging.getLogger(__name__)
@@ -190,3 +191,86 @@ def get_buffer_ptr_and_size(buf_arg, builder, local_sym_tab, struct_sym_tab):
190191
raise ValueError(
191192
"comm expects either a struct field (obj.field) or variable name"
192193
)
194+
195+
196+
def get_char_array_ptr_and_size(buf_arg, builder, local_sym_tab, struct_sym_tab):
197+
"""Get pointer to char array and its size."""
198+
199+
# Struct field: obj.field
200+
if isinstance(buf_arg, ast.Attribute) and isinstance(buf_arg.value, ast.Name):
201+
var_name = buf_arg.value.id
202+
field_name = buf_arg.attr
203+
204+
if not (local_sym_tab and var_name in local_sym_tab):
205+
raise ValueError(f"Variable '{var_name}' not found")
206+
207+
struct_type = local_sym_tab[var_name].metadata
208+
if not (struct_sym_tab and struct_type in struct_sym_tab):
209+
raise ValueError(f"Struct type '{struct_type}' not found")
210+
211+
struct_info = struct_sym_tab[struct_type]
212+
if field_name not in struct_info.fields:
213+
raise ValueError(f"Field '{field_name}' not found")
214+
215+
field_type = struct_info.field_type(field_name)
216+
if not _is_char_array(field_type):
217+
raise ValueError("Expected char array field")
218+
219+
struct_ptr = local_sym_tab[var_name].var
220+
field_ptr = struct_info.gep(builder, struct_ptr, field_name)
221+
222+
# GEP to first element: [N x i8]* -> i8*
223+
buf_ptr = builder.gep(
224+
field_ptr,
225+
[ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)],
226+
inbounds=True,
227+
)
228+
return buf_ptr, field_type.count
229+
230+
elif isinstance(buf_arg, ast.Name):
231+
# NOTE: We shouldn't be doing this as we can't get size info
232+
var_name = buf_arg.id
233+
if not (local_sym_tab and var_name in local_sym_tab):
234+
raise ValueError(f"Variable '{var_name}' not found")
235+
236+
var_ptr = local_sym_tab[var_name].var
237+
var_type = local_sym_tab[var_name].ir_type
238+
239+
if not isinstance(var_type, ir.PointerType) and not isinstance(
240+
var_type.pointee, ir.IntType(8)
241+
):
242+
raise ValueError("Expected str ptr variable")
243+
244+
return var_ptr, 256 # Size unknown for str ptr, using 256 as default
245+
246+
else:
247+
raise ValueError("Expected struct field or variable name")
248+
249+
250+
def _is_char_array(ir_type):
251+
"""Check if IR type is [N x i8]."""
252+
return (
253+
isinstance(ir_type, ir.ArrayType)
254+
and isinstance(ir_type.element, ir.IntType)
255+
and ir_type.element.width == 8
256+
)
257+
258+
259+
def get_ptr_from_arg(
260+
arg, func, module, builder, local_sym_tab, map_sym_tab, struct_sym_tab
261+
):
262+
"""Evaluate argument and return pointer value"""
263+
264+
result = eval_expr(
265+
func, module, builder, arg, local_sym_tab, map_sym_tab, struct_sym_tab
266+
)
267+
268+
if not result:
269+
raise ValueError("Failed to evaluate argument")
270+
271+
val, val_type = result
272+
273+
if not isinstance(val_type, ir.PointerType):
274+
raise ValueError(f"Expected pointer type, got {val_type}")
275+
276+
return val, val_type

pythonbpf/helper/helpers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ def comm(buf):
2222
return ctypes.c_int64(0)
2323

2424

25+
def probe_read_str(dst, src):
26+
"""Safely read a null-terminated string from kernel memory"""
27+
return ctypes.c_int64(0)
28+
29+
2530
XDP_ABORTED = ctypes.c_int64(0)
2631
XDP_DROP = ctypes.c_int64(1)
2732
XDP_PASS = ctypes.c_int64(2)

0 commit comments

Comments
 (0)