mirror of
https://github.com/varun-r-mallya/Python-BPF.git
synced 2025-12-31 21:06:25 +00:00
add naive unpythonic return type inference to function parsing
This commit is contained in:
@ -1,11 +1,17 @@
|
|||||||
from pythonbpf.decorators import bpf, section
|
from pythonbpf.decorators import bpf, section
|
||||||
from ctypes import c_void_p, c_int32
|
from ctypes import c_void_p, c_int64, c_int32
|
||||||
|
|
||||||
|
|
||||||
@bpf
|
@bpf
|
||||||
@section("tracepoint/syscalls/sys_enter_execve")
|
@section("tracepoint/syscalls/sys_enter_execve")
|
||||||
def hello(ctx: c_void_p) -> c_int32:
|
def hello(ctx: c_void_p) -> c_int32:
|
||||||
print("Hello, World!")
|
print("entered")
|
||||||
return c_int32(0)
|
return c_int32(0)
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@section("tracepoint/syscalls/sys_exit_execve")
|
||||||
|
def hello_again(ctx: c_void_p) -> c_int64:
|
||||||
|
print("exited")
|
||||||
|
return c_int64(0)
|
||||||
|
|
||||||
LICENSE = "GPL"
|
LICENSE = "GPL"
|
||||||
|
|||||||
@ -1,47 +1,6 @@
|
|||||||
from llvmlite import ir
|
from llvmlite import ir
|
||||||
import ast
|
import ast
|
||||||
|
from .type_deducer import ctypes_to_ir
|
||||||
|
|
||||||
def emit_function(module: ir.Module, name: str):
|
|
||||||
ret_type = ir.IntType(32)
|
|
||||||
ptr_type = ir.PointerType()
|
|
||||||
func_ty = ir.FunctionType(ret_type, [ptr_type])
|
|
||||||
|
|
||||||
func = ir.Function(module, func_ty, name)
|
|
||||||
|
|
||||||
param = func.args[0]
|
|
||||||
param.add_attribute("nocapture")
|
|
||||||
|
|
||||||
func.attributes.add("nounwind")
|
|
||||||
# func.attributes.add("\"frame-pointer\"=\"all\"")
|
|
||||||
# func.attributes.add("no-trapping-math", "true")
|
|
||||||
# func.attributes.add("stack-protector-buffer-size", "8")
|
|
||||||
|
|
||||||
block = func.append_basic_block(name="entry")
|
|
||||||
builder = ir.IRBuilder(block)
|
|
||||||
fmt_gvar = module.get_global("hello.____fmt")
|
|
||||||
|
|
||||||
if fmt_gvar is None:
|
|
||||||
# If you haven't created the format string global yet
|
|
||||||
print("Warning: Format string global not found")
|
|
||||||
else:
|
|
||||||
# Cast integer 6 to function pointer type
|
|
||||||
fn_type = ir.FunctionType(ir.IntType(
|
|
||||||
64), [ptr_type, ir.IntType(32)], var_arg=True)
|
|
||||||
fn_ptr_type = ir.PointerType(fn_type)
|
|
||||||
fn_addr = ir.Constant(ir.IntType(64), 6)
|
|
||||||
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
|
|
||||||
# Call the function
|
|
||||||
builder.call(fn_ptr, [fmt_gvar, ir.Constant(ir.IntType(32), 14)])
|
|
||||||
|
|
||||||
builder.ret(ir.Constant(ret_type, 0))
|
|
||||||
|
|
||||||
func.return_value.add_attribute("noundef")
|
|
||||||
func.linkage = "dso_local"
|
|
||||||
func.section = "kprobe/sys_clone"
|
|
||||||
print("function emitted:", name)
|
|
||||||
return func
|
|
||||||
|
|
||||||
|
|
||||||
def get_probe_string(func_node):
|
def get_probe_string(func_node):
|
||||||
"""Extract the probe string from the decorator of the function node."""
|
"""Extract the probe string from the decorator of the function node."""
|
||||||
@ -58,7 +17,7 @@ def get_probe_string(func_node):
|
|||||||
return "helper"
|
return "helper"
|
||||||
|
|
||||||
|
|
||||||
def process_func_body(module, builder, func_node, func):
|
def process_func_body(module, builder, func_node, func, ret_type):
|
||||||
"""Process the body of a bpf function"""
|
"""Process the body of a bpf function"""
|
||||||
# TODO: A lot. We just have print -> bpf_trace_printk for now
|
# TODO: A lot. We just have print -> bpf_trace_printk for now
|
||||||
did_return = False
|
did_return = False
|
||||||
@ -100,23 +59,28 @@ def process_func_body(module, builder, func_node, func):
|
|||||||
if stmt.value is None:
|
if stmt.value is None:
|
||||||
builder.ret(ir.Constant(ir.IntType(32), 0))
|
builder.ret(ir.Constant(ir.IntType(32), 0))
|
||||||
did_return = True
|
did_return = True
|
||||||
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):
|
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):
|
||||||
builder.ret(ir.Constant(ir.IntType(
|
call_type = stmt.value.func.id
|
||||||
32), stmt.value.args[0].value))
|
if ctypes_to_ir(call_type) != ret_type:
|
||||||
did_return = True
|
raise ValueError(f"Return type mismatch: expected {ctypes_to_ir(call_type)}, got {call_type}")
|
||||||
|
else:
|
||||||
|
builder.ret(ir.Constant(ret_type, stmt.value.args[0].value))
|
||||||
|
did_return = True
|
||||||
else:
|
else:
|
||||||
print("Unsupported return value")
|
print("Unsupported return value")
|
||||||
if not did_return:
|
if not did_return:
|
||||||
builder.ret(ir.Constant(ir.IntType(32), 0))
|
builder.ret(ir.Constant(ir.IntType(32), 0))
|
||||||
|
|
||||||
|
|
||||||
def process_bpf_chunk(func_node, module):
|
def process_bpf_chunk(func_node, module, return_type):
|
||||||
"""Process a single BPF chunk (function) and emit corresponding LLVM IR."""
|
"""Process a single BPF chunk (function) and emit corresponding LLVM IR."""
|
||||||
|
|
||||||
func_name = func_node.name
|
func_name = func_node.name
|
||||||
|
|
||||||
# TODO: parse return type
|
#TODO: The function actual arg retgurn type is parsed,
|
||||||
ret_type = ir.IntType(32)
|
# but the actual output is not. It's still very wrong. Try uncommenting the
|
||||||
|
# code in execve2.py once
|
||||||
|
ret_type = return_type
|
||||||
|
|
||||||
# TODO: parse parameters
|
# TODO: parse parameters
|
||||||
param_types = []
|
param_types = []
|
||||||
@ -142,7 +106,7 @@ def process_bpf_chunk(func_node, module):
|
|||||||
block = func.append_basic_block(name="entry")
|
block = func.append_basic_block(name="entry")
|
||||||
builder = ir.IRBuilder(block)
|
builder = ir.IRBuilder(block)
|
||||||
|
|
||||||
process_func_body(module, builder, func_node, func)
|
process_func_body(module, builder, func_node, func, ret_type)
|
||||||
|
|
||||||
print(func)
|
print(func)
|
||||||
print(module)
|
print(module)
|
||||||
@ -154,39 +118,59 @@ def func_proc(tree, module, chunks):
|
|||||||
func_type = get_probe_string(func_node)
|
func_type = get_probe_string(func_node)
|
||||||
print(f"Found probe_string of {func_node.name}: {func_type}")
|
print(f"Found probe_string of {func_node.name}: {func_type}")
|
||||||
|
|
||||||
process_bpf_chunk(func_node, module)
|
process_bpf_chunk(func_node, module, ctypes_to_ir(infer_return_type(func_node)))
|
||||||
|
|
||||||
|
def infer_return_type(func_node: ast.FunctionDef):
|
||||||
def functions_processing(tree, module):
|
if not isinstance(func_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||||
bpf_functions = []
|
raise TypeError("Expected ast.FunctionDef")
|
||||||
helper_functions = []
|
if func_node.returns is not None:
|
||||||
for node in tree.body:
|
try:
|
||||||
section_name = ""
|
return ast.unparse(func_node.returns)
|
||||||
if isinstance(node, ast.FunctionDef):
|
except Exception:
|
||||||
if len(node.decorator_list) == 1:
|
node = func_node.returns
|
||||||
bpf_functions.append(node)
|
if isinstance(node, ast.Name):
|
||||||
node.end_lineno
|
return node.id
|
||||||
else:
|
if isinstance(node, ast.Attribute):
|
||||||
# IDK why this check is needed, but whatever
|
return getattr(node, "attr", type(node).__name__)
|
||||||
if 'helper_functions' not in locals():
|
try:
|
||||||
helper_functions.append(node)
|
return str(node)
|
||||||
|
except Exception:
|
||||||
# TODO: implement helpers first
|
return type(node).__name__
|
||||||
|
found_type = None
|
||||||
for func in bpf_functions:
|
def _expr_type(e):
|
||||||
dec = func.decorator_list[0]
|
if e is None:
|
||||||
if (
|
return "None"
|
||||||
isinstance(dec, ast.Call)
|
if isinstance(e, ast.Constant):
|
||||||
and isinstance(dec.func, ast.Name)
|
return type(e.value).__name__
|
||||||
and dec.func.id == "section"
|
if isinstance(e, ast.Name):
|
||||||
and len(dec.args) == 1
|
return e.id
|
||||||
and isinstance(dec.args[0], ast.Constant)
|
if isinstance(e, ast.Call):
|
||||||
and isinstance(dec.args[0].value, str)
|
f = e.func
|
||||||
):
|
if isinstance(f, ast.Name):
|
||||||
section_name = dec.args[0].value
|
return f.id
|
||||||
else:
|
if isinstance(f, ast.Attribute):
|
||||||
print(f"ERROR: Invalid decorator for function {func.name}")
|
try:
|
||||||
continue
|
return ast.unparse(f)
|
||||||
|
except Exception:
|
||||||
# TODO: parse arguments and return type
|
return getattr(f, "attr", type(f).__name__)
|
||||||
emit_function(module, func.name + "func")
|
try:
|
||||||
|
return ast.unparse(f)
|
||||||
|
except Exception:
|
||||||
|
return type(f).__name__
|
||||||
|
if isinstance(e, ast.Attribute):
|
||||||
|
try:
|
||||||
|
return ast.unparse(e)
|
||||||
|
except Exception:
|
||||||
|
return getattr(e, "attr", type(e).__name__)
|
||||||
|
try:
|
||||||
|
return ast.unparse(e)
|
||||||
|
except Exception:
|
||||||
|
return type(e).__name__
|
||||||
|
for node in ast.walk(func_node):
|
||||||
|
if isinstance(node, ast.Return):
|
||||||
|
t = _expr_type(node.value)
|
||||||
|
if found_type is None:
|
||||||
|
found_type = t
|
||||||
|
elif found_type != t:
|
||||||
|
raise ValueError(f"Conflicting return types: {found_type} vs {t}")
|
||||||
|
return found_type or "None"
|
||||||
|
|||||||
@ -1,29 +1,21 @@
|
|||||||
import ctypes
|
|
||||||
from llvmlite import ir
|
from llvmlite import ir
|
||||||
|
|
||||||
def ctypes_to_ir(ctype):
|
#TODO: THIS IS NOT SUPPOSED TO MATCH STRINGS :skull:
|
||||||
if ctype is ctypes.c_int32:
|
def ctypes_to_ir(ctype: str):
|
||||||
return ir.IntType(32)
|
print("CTYPE", ctype)
|
||||||
if ctype is ctypes.c_int64:
|
mapping = {
|
||||||
return ir.IntType(64)
|
"c_int8": ir.IntType(8),
|
||||||
if ctype is ctypes.c_uint8:
|
"c_uint8": ir.IntType(8),
|
||||||
return ir.IntType(8)
|
"c_int16": ir.IntType(16),
|
||||||
if ctype is ctypes.c_double:
|
"c_uint16": ir.IntType(16),
|
||||||
return ir.DoubleType()
|
"c_int32": ir.IntType(32),
|
||||||
if ctype is ctypes.c_float:
|
"c_uint32": ir.IntType(32),
|
||||||
return ir.FloatType()
|
"c_int64": ir.IntType(64),
|
||||||
|
"c_uint64": ir.IntType(64),
|
||||||
# pointers
|
"c_float": ir.FloatType(),
|
||||||
if hasattr(ctype, "_type_") and hasattr(ctype, "_length_"):
|
"c_double": ir.DoubleType(),
|
||||||
# ctypes array
|
"c_void_p": ir.IntType(64),
|
||||||
return ir.ArrayType(ctypes_to_ir(ctype._type_), ctype._length_)
|
}
|
||||||
|
if ctype in mapping:
|
||||||
# if hasattr(ctype, "_type_") and issubclass(ctype, ctypes._Pointer):
|
return mapping[ctype]
|
||||||
# return ir.PointerType(ctypes_to_ir(ctype._type_))
|
|
||||||
|
|
||||||
# structs
|
|
||||||
if issubclass(ctype, ctypes.Structure):
|
|
||||||
fields = [ctypes_to_ir(f[1]) for f in ctype._fields_]
|
|
||||||
return ir.LiteralStructType(fields)
|
|
||||||
|
|
||||||
raise NotImplementedError(f"No mapping for {ctype}")
|
raise NotImplementedError(f"No mapping for {ctype}")
|
||||||
|
|||||||
Reference in New Issue
Block a user