Skip to content

Commit 1118e4f

Browse files
add naive unpythonic return type inference to function parsing
1 parent c055963 commit 1118e4f

3 files changed

Lines changed: 97 additions & 115 deletions

File tree

examples/execve2.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
from pythonbpf.decorators import bpf, section
2-
from ctypes import c_void_p, c_int32
2+
from ctypes import c_void_p, c_int64, c_int32
33

44

55
@bpf
66
@section("tracepoint/syscalls/sys_enter_execve")
77
def hello(ctx: c_void_p) -> c_int32:
8-
print("Hello, World!")
8+
print("entered")
99
return c_int32(0)
1010

11+
@bpf
12+
@section("tracepoint/syscalls/sys_exit_execve")
13+
def hello_again(ctx: c_void_p) -> c_int64:
14+
print("exited")
15+
return c_int64(0)
16+
1117
LICENSE = "GPL"

pythonbpf/functions_pass.py

Lines changed: 71 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,6 @@
11
from llvmlite import ir
22
import ast
3-
4-
5-
def emit_function(module: ir.Module, name: str):
6-
ret_type = ir.IntType(32)
7-
ptr_type = ir.PointerType()
8-
func_ty = ir.FunctionType(ret_type, [ptr_type])
9-
10-
func = ir.Function(module, func_ty, name)
11-
12-
param = func.args[0]
13-
param.add_attribute("nocapture")
14-
15-
func.attributes.add("nounwind")
16-
# func.attributes.add("\"frame-pointer\"=\"all\"")
17-
# func.attributes.add("no-trapping-math", "true")
18-
# func.attributes.add("stack-protector-buffer-size", "8")
19-
20-
block = func.append_basic_block(name="entry")
21-
builder = ir.IRBuilder(block)
22-
fmt_gvar = module.get_global("hello.____fmt")
23-
24-
if fmt_gvar is None:
25-
# If you haven't created the format string global yet
26-
print("Warning: Format string global not found")
27-
else:
28-
# Cast integer 6 to function pointer type
29-
fn_type = ir.FunctionType(ir.IntType(
30-
64), [ptr_type, ir.IntType(32)], var_arg=True)
31-
fn_ptr_type = ir.PointerType(fn_type)
32-
fn_addr = ir.Constant(ir.IntType(64), 6)
33-
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
34-
# Call the function
35-
builder.call(fn_ptr, [fmt_gvar, ir.Constant(ir.IntType(32), 14)])
36-
37-
builder.ret(ir.Constant(ret_type, 0))
38-
39-
func.return_value.add_attribute("noundef")
40-
func.linkage = "dso_local"
41-
func.section = "kprobe/sys_clone"
42-
print("function emitted:", name)
43-
return func
44-
3+
from .type_deducer import ctypes_to_ir
454

465
def get_probe_string(func_node):
476
"""Extract the probe string from the decorator of the function node."""
@@ -58,7 +17,7 @@ def get_probe_string(func_node):
5817
return "helper"
5918

6019

61-
def process_func_body(module, builder, func_node, func):
20+
def process_func_body(module, builder, func_node, func, ret_type):
6221
"""Process the body of a bpf function"""
6322
# TODO: A lot. We just have print -> bpf_trace_printk for now
6423
did_return = False
@@ -100,23 +59,28 @@ def process_func_body(module, builder, func_node, func):
10059
if stmt.value is None:
10160
builder.ret(ir.Constant(ir.IntType(32), 0))
10261
did_return = True
103-
elif isinstance(stmt.value, ast.Call) and isinstance(stmt.value.func, ast.Name) and stmt.value.func.id == "c_int32" and len(stmt.value.args) == 1 and isinstance(stmt.value.args[0], ast.Constant) and isinstance(stmt.value.args[0].value, int):
104-
builder.ret(ir.Constant(ir.IntType(
105-
32), stmt.value.args[0].value))
106-
did_return = True
62+
elif isinstance(stmt.value, ast.Call) and isinstance(stmt.value.func, ast.Name) and len(stmt.value.args) == 1 and isinstance(stmt.value.args[0], ast.Constant) and isinstance(stmt.value.args[0].value, int):
63+
call_type = stmt.value.func.id
64+
if ctypes_to_ir(call_type) != ret_type:
65+
raise ValueError(f"Return type mismatch: expected {ctypes_to_ir(call_type)}, got {call_type}")
66+
else:
67+
builder.ret(ir.Constant(ret_type, stmt.value.args[0].value))
68+
did_return = True
10769
else:
10870
print("Unsupported return value")
10971
if not did_return:
11072
builder.ret(ir.Constant(ir.IntType(32), 0))
11173

11274

113-
def process_bpf_chunk(func_node, module):
75+
def process_bpf_chunk(func_node, module, return_type):
11476
"""Process a single BPF chunk (function) and emit corresponding LLVM IR."""
11577

11678
func_name = func_node.name
11779

118-
# TODO: parse return type
119-
ret_type = ir.IntType(32)
80+
#TODO: The function actual arg retgurn type is parsed,
81+
# but the actual output is not. It's still very wrong. Try uncommenting the
82+
# code in execve2.py once
83+
ret_type = return_type
12084

12185
# TODO: parse parameters
12286
param_types = []
@@ -142,7 +106,7 @@ def process_bpf_chunk(func_node, module):
142106
block = func.append_basic_block(name="entry")
143107
builder = ir.IRBuilder(block)
144108

145-
process_func_body(module, builder, func_node, func)
109+
process_func_body(module, builder, func_node, func, ret_type)
146110

147111
print(func)
148112
print(module)
@@ -154,39 +118,59 @@ def func_proc(tree, module, chunks):
154118
func_type = get_probe_string(func_node)
155119
print(f"Found probe_string of {func_node.name}: {func_type}")
156120

157-
process_bpf_chunk(func_node, module)
158-
159-
160-
def functions_processing(tree, module):
161-
bpf_functions = []
162-
helper_functions = []
163-
for node in tree.body:
164-
section_name = ""
165-
if isinstance(node, ast.FunctionDef):
166-
if len(node.decorator_list) == 1:
167-
bpf_functions.append(node)
168-
node.end_lineno
169-
else:
170-
# IDK why this check is needed, but whatever
171-
if 'helper_functions' not in locals():
172-
helper_functions.append(node)
173-
174-
# TODO: implement helpers first
175-
176-
for func in bpf_functions:
177-
dec = func.decorator_list[0]
178-
if (
179-
isinstance(dec, ast.Call)
180-
and isinstance(dec.func, ast.Name)
181-
and dec.func.id == "section"
182-
and len(dec.args) == 1
183-
and isinstance(dec.args[0], ast.Constant)
184-
and isinstance(dec.args[0].value, str)
185-
):
186-
section_name = dec.args[0].value
187-
else:
188-
print(f"ERROR: Invalid decorator for function {func.name}")
189-
continue
190-
191-
# TODO: parse arguments and return type
192-
emit_function(module, func.name + "func")
121+
process_bpf_chunk(func_node, module, ctypes_to_ir(infer_return_type(func_node)))
122+
123+
def infer_return_type(func_node: ast.FunctionDef):
124+
if not isinstance(func_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
125+
raise TypeError("Expected ast.FunctionDef")
126+
if func_node.returns is not None:
127+
try:
128+
return ast.unparse(func_node.returns)
129+
except Exception:
130+
node = func_node.returns
131+
if isinstance(node, ast.Name):
132+
return node.id
133+
if isinstance(node, ast.Attribute):
134+
return getattr(node, "attr", type(node).__name__)
135+
try:
136+
return str(node)
137+
except Exception:
138+
return type(node).__name__
139+
found_type = None
140+
def _expr_type(e):
141+
if e is None:
142+
return "None"
143+
if isinstance(e, ast.Constant):
144+
return type(e.value).__name__
145+
if isinstance(e, ast.Name):
146+
return e.id
147+
if isinstance(e, ast.Call):
148+
f = e.func
149+
if isinstance(f, ast.Name):
150+
return f.id
151+
if isinstance(f, ast.Attribute):
152+
try:
153+
return ast.unparse(f)
154+
except Exception:
155+
return getattr(f, "attr", type(f).__name__)
156+
try:
157+
return ast.unparse(f)
158+
except Exception:
159+
return type(f).__name__
160+
if isinstance(e, ast.Attribute):
161+
try:
162+
return ast.unparse(e)
163+
except Exception:
164+
return getattr(e, "attr", type(e).__name__)
165+
try:
166+
return ast.unparse(e)
167+
except Exception:
168+
return type(e).__name__
169+
for node in ast.walk(func_node):
170+
if isinstance(node, ast.Return):
171+
t = _expr_type(node.value)
172+
if found_type is None:
173+
found_type = t
174+
elif found_type != t:
175+
raise ValueError(f"Conflicting return types: {found_type} vs {t}")
176+
return found_type or "None"

pythonbpf/type_deducer.py

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,21 @@
1-
import ctypes
21
from llvmlite import ir
32

4-
def ctypes_to_ir(ctype):
5-
if ctype is ctypes.c_int32:
6-
return ir.IntType(32)
7-
if ctype is ctypes.c_int64:
8-
return ir.IntType(64)
9-
if ctype is ctypes.c_uint8:
10-
return ir.IntType(8)
11-
if ctype is ctypes.c_double:
12-
return ir.DoubleType()
13-
if ctype is ctypes.c_float:
14-
return ir.FloatType()
15-
16-
# pointers
17-
if hasattr(ctype, "_type_") and hasattr(ctype, "_length_"):
18-
# ctypes array
19-
return ir.ArrayType(ctypes_to_ir(ctype._type_), ctype._length_)
20-
21-
# if hasattr(ctype, "_type_") and issubclass(ctype, ctypes._Pointer):
22-
# return ir.PointerType(ctypes_to_ir(ctype._type_))
23-
24-
# structs
25-
if issubclass(ctype, ctypes.Structure):
26-
fields = [ctypes_to_ir(f[1]) for f in ctype._fields_]
27-
return ir.LiteralStructType(fields)
28-
3+
#TODO: THIS IS NOT SUPPOSED TO MATCH STRINGS :skull:
4+
def ctypes_to_ir(ctype: str):
5+
print("CTYPE", ctype)
6+
mapping = {
7+
"c_int8": ir.IntType(8),
8+
"c_uint8": ir.IntType(8),
9+
"c_int16": ir.IntType(16),
10+
"c_uint16": ir.IntType(16),
11+
"c_int32": ir.IntType(32),
12+
"c_uint32": ir.IntType(32),
13+
"c_int64": ir.IntType(64),
14+
"c_uint64": ir.IntType(64),
15+
"c_float": ir.FloatType(),
16+
"c_double": ir.DoubleType(),
17+
"c_void_p": ir.IntType(64),
18+
}
19+
if ctype in mapping:
20+
return mapping[ctype]
2921
raise NotImplementedError(f"No mapping for {ctype}")

0 commit comments

Comments
 (0)