From ea5a1ab2de9645f229ad882ec17cf241f8f13d32 Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Sat, 27 Sep 2025 12:23:29 +0530 Subject: [PATCH] add jupyter notebook support --- .gitignore | 3 +- demo/clone-matplotlib.ipynb | 397 ++++++++++++++++++++++++++++++++ pythonbpf/bpf_helper_handler.py | 4 +- pythonbpf/codegen.py | 24 +- pythonbpf/structs_pass.py | 1 - 5 files changed, 415 insertions(+), 14 deletions(-) create mode 100644 demo/clone-matplotlib.ipynb diff --git a/.gitignore b/.gitignore index 62321af..755f660 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ .vscode/ __pycache__/ *.ll -*.o \ No newline at end of file +*.o +.ipynb_checkpoints/ diff --git a/demo/clone-matplotlib.ipynb b/demo/clone-matplotlib.ipynb new file mode 100644 index 0000000..414dbc9 --- /dev/null +++ b/demo/clone-matplotlib.ipynb @@ -0,0 +1,397 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "22dd4e7b-2ea2-49cb-a8d5-1da108c10034", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "\n", + "from pythonbpf import bpf, map, section, bpfglobal, BPF\n", + "from pythonbpf.helpers import pid\n", + "from pythonbpf.maps import HashMap\n", + "from pylibbpf import *\n", + "from ctypes import c_void_p, c_int64, c_uint64, c_int32\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ac7a07bf-440f-41e2-bec8-95f520f9cd53", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Module(\n", + " body=[\n", + " FunctionDef(\n", + " name='hist',\n", + " args=arguments(\n", + " posonlyargs=[],\n", + " args=[],\n", + " kwonlyargs=[],\n", + " kw_defaults=[],\n", + " defaults=[]),\n", + " body=[\n", + " Return(\n", + " value=Call(\n", + " func=Name(id='HashMap', ctx=Load()),\n", + " args=[],\n", + " keywords=[\n", + " keyword(\n", + " arg='key',\n", + " value=Name(id='c_int32', ctx=Load())),\n", + " keyword(\n", + " arg='value',\n", + " value=Name(id='c_uint64', ctx=Load())),\n", + " keyword(\n", + " arg='max_entries',\n", + " value=Constant(value=4096))]))],\n", + " decorator_list=[\n", + " Name(id='bpf', ctx=Load()),\n", + " Name(id='map', ctx=Load())],\n", + " returns=Name(id='HashMap', ctx=Load()),\n", + " type_params=[]),\n", + " FunctionDef(\n", + " name='hello',\n", + " args=arguments(\n", + " posonlyargs=[],\n", + " args=[\n", + " arg(\n", + " arg='ctx',\n", + " annotation=Name(id='c_void_p', ctx=Load()))],\n", + " kwonlyargs=[],\n", + " kw_defaults=[],\n", + " defaults=[]),\n", + " body=[\n", + " Assign(\n", + " targets=[\n", + " Name(id='process_id', ctx=Store())],\n", + " value=Call(\n", + " func=Name(id='pid', ctx=Load()),\n", + " args=[],\n", + " keywords=[])),\n", + " Assign(\n", + " targets=[\n", + " Name(id='one', ctx=Store())],\n", + " value=Constant(value=1)),\n", + " Assign(\n", + " targets=[\n", + " Name(id='prev', ctx=Store())],\n", + " value=Call(\n", + " func=Attribute(\n", + " value=Call(\n", + " func=Name(id='hist', ctx=Load()),\n", + " args=[],\n", + " keywords=[]),\n", + " attr='lookup',\n", + " ctx=Load()),\n", + " args=[\n", + " Name(id='process_id', ctx=Load())],\n", + " keywords=[])),\n", + " If(\n", + " test=Name(id='prev', ctx=Load()),\n", + " body=[\n", + " Assign(\n", + " targets=[\n", + " Name(id='previous_value', ctx=Store())],\n", + " value=BinOp(\n", + " left=Name(id='prev', ctx=Load()),\n", + " op=Add(),\n", + " right=Constant(value=1))),\n", + " Expr(\n", + " value=Call(\n", + " func=Name(id='print', ctx=Load()),\n", + " args=[\n", + " JoinedStr(\n", + " values=[\n", + " Constant(value='count: '),\n", + " FormattedValue(\n", + " value=Name(id='previous_value', ctx=Load()),\n", + " conversion=-1),\n", + " Constant(value=' with '),\n", + " FormattedValue(\n", + " value=Name(id='process_id', ctx=Load()),\n", + " conversion=-1)])],\n", + " keywords=[])),\n", + " Expr(\n", + " value=Call(\n", + " func=Attribute(\n", + " value=Call(\n", + " func=Name(id='hist', ctx=Load()),\n", + " args=[],\n", + " keywords=[]),\n", + " attr='update',\n", + " ctx=Load()),\n", + " args=[\n", + " Name(id='process_id', ctx=Load()),\n", + " Name(id='previous_value', ctx=Load())],\n", + " keywords=[])),\n", + " Return(\n", + " value=Call(\n", + " func=Name(id='c_int64', ctx=Load()),\n", + " args=[\n", + " Constant(value=0)],\n", + " keywords=[]))],\n", + " orelse=[\n", + " Expr(\n", + " value=Call(\n", + " func=Attribute(\n", + " value=Call(\n", + " func=Name(id='hist', ctx=Load()),\n", + " args=[],\n", + " keywords=[]),\n", + " attr='update',\n", + " ctx=Load()),\n", + " args=[\n", + " Name(id='process_id', ctx=Load()),\n", + " Name(id='one', ctx=Load())],\n", + " keywords=[]))]),\n", + " Return(\n", + " value=Call(\n", + " func=Name(id='c_int64', ctx=Load()),\n", + " args=[\n", + " Constant(value=0)],\n", + " keywords=[]))],\n", + " decorator_list=[\n", + " Name(id='bpf', ctx=Load()),\n", + " Call(\n", + " func=Name(id='section', ctx=Load()),\n", + " args=[\n", + " Constant(value='tracepoint/syscalls/sys_enter_clone')],\n", + " keywords=[])],\n", + " returns=Name(id='c_int64', ctx=Load()),\n", + " type_params=[]),\n", + " FunctionDef(\n", + " name='LICENSE',\n", + " args=arguments(\n", + " posonlyargs=[],\n", + " args=[],\n", + " kwonlyargs=[],\n", + " kw_defaults=[],\n", + " defaults=[]),\n", + " body=[\n", + " Return(\n", + " value=Constant(value='GPL'))],\n", + " decorator_list=[\n", + " Name(id='bpf', ctx=Load()),\n", + " Name(id='bpfglobal', ctx=Load())],\n", + " returns=Name(id='str', ctx=Load()),\n", + " type_params=[]),\n", + " Assign(\n", + " targets=[\n", + " Name(id='b', ctx=Store())],\n", + " value=Call(\n", + " func=Name(id='BPF', ctx=Load()),\n", + " args=[],\n", + " keywords=[]))],\n", + " type_ignores=[])\n", + "Found BPF function/struct: hist\n", + "Found BPF function/struct: hello\n", + "Found BPF function/struct: LICENSE\n", + "Found BPF map: hist\n", + "Processing BPF map: hist\n", + "Creating HashMap map: hist\n", + "Map parameters: {'type': 'HASH', 'key': 'c_int32', 'value': 'c_uint64', 'max_entries': 4096}\n", + "Created BPF map: hist\n", + "Found probe_string of hello: tracepoint/syscalls/sys_enter_clone\n", + "Pre-allocated variable process_id for helper\n", + "Pre-allocated variable one of type c_int64\n", + "Pre-allocated variable prev for map\n", + "Pre-allocated variable previous_value of type c_int64\n", + "Local symbol table: dict_keys(['process_id', 'one', 'prev', 'previous_value'])\n", + "Processing statement: Assign(targets=[Name(id='process_id', ctx=Store())], value=Call(func=Name(id='pid', ctx=Load()), args=[], keywords=[]))\n", + "Handling assignment to Name(id='process_id', ctx=Store())\n", + "Assignment call type: pid\n", + "Assigned constant pid to process_id\n", + "Processing statement: Assign(targets=[Name(id='one', ctx=Store())], value=Constant(value=1))\n", + "Handling assignment to Name(id='one', ctx=Store())\n", + "Assigned constant 1 to one\n", + "Processing statement: Assign(targets=[Name(id='prev', ctx=Store())], value=Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='lookup', ctx=Load()), args=[Name(id='process_id', ctx=Load())], keywords=[]))\n", + "Handling assignment to Name(id='prev', ctx=Store())\n", + "Assignment call attribute: Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='lookup', ctx=Load())\n", + "{}\n", + "Processing statement: If(test=Name(id='prev', ctx=Load()), body=[Assign(targets=[Name(id='previous_value', ctx=Store())], value=BinOp(left=Name(id='prev', ctx=Load()), op=Add(), right=Constant(value=1))), Expr(value=Call(func=Name(id='print', ctx=Load()), args=[JoinedStr(values=[Constant(value='count: '), FormattedValue(value=Name(id='previous_value', ctx=Load()), conversion=-1), Constant(value=' with '), FormattedValue(value=Name(id='process_id', ctx=Load()), conversion=-1)])], keywords=[])), Expr(value=Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='previous_value', ctx=Load())], keywords=[])), Return(value=Call(func=Name(id='c_int64', ctx=Load()), args=[Constant(value=0)], keywords=[]))], orelse=[Expr(value=Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='one', ctx=Load())], keywords=[]))])\n", + "Handling if statement\n", + "Processing statement: Assign(targets=[Name(id='previous_value', ctx=Store())], value=BinOp(left=Name(id='prev', ctx=Load()), op=Add(), right=Constant(value=1)))\n", + "Handling assignment to Name(id='previous_value', ctx=Store())\n", + "; ModuleID = \"/tmp/tmp98sm2v54.py\"\n", + "target triple = \"bpf\"\n", + "target datalayout = \"e-m:e-p:64:64-i64:64-i128:128-n32:64-S128\"\n", + "\n", + "@\"hist\" = dso_local global {ptr, ptr, ptr, ptr} zeroinitializer, section \".maps\", align 8, !dbg !22\n", + "define dso_local i64 @\"hello\"(ptr nocapture %\".1\") noinline nounwind optnone section \"tracepoint/syscalls/sys_enter_clone\"\n", + "{\n", + "entry:\n", + " %\"process_id\" = alloca i64, align 8\n", + " %\"one\" = alloca i64, align 8\n", + " %\"prev\" = alloca i64*\n", + " %\"previous_value\" = alloca i64, align 8\n", + " %\".3\" = inttoptr i64 14 to i64 ()*\n", + " %\".4\" = call i64 %\".3\"()\n", + " %\".5\" = and i64 %\".4\", 4294967295\n", + " store i64 %\".5\", i64* %\"process_id\"\n", + " store i64 1, i64* %\"one\"\n", + " %\".8\" = inttoptr i64 1 to ptr (ptr, ptr)*\n", + " %\".9\" = call ptr %\".8\"({ptr, ptr, ptr, ptr}* @\"hist\", i64* %\"process_id\")\n", + " store ptr %\".9\", i64** %\"prev\"\n", + " %\".11\" = load i64*, i64** %\"prev\"\n", + " %\".12\" = icmp ne i64* %\".11\", null\n", + " br i1 %\".12\", label %\"if.then\", label %\"if.else\"\n", + "if.then:\n", + "if.end:\n", + "if.else:\n", + "}\n", + "\n", + "!llvm.dbg.cu = !{ !1 }\n", + "!0 = !DIFile(directory: \"/tmp\", filename: \"/tmp/tmp98sm2v54.py\")\n", + "!1 = distinct !DICompileUnit(emissionKind: 1, file: !0, isOptimized: true, language: 29, nameTableKind: 0, producer: \"PythonBPF DSL Compiler\", runtimeVersion: 0, splitDebugInlining: false)\n", + "!2 = !DIBasicType(encoding: 7, name: \"unsigned int\", size: 32)\n", + "!3 = !DIBasicType(encoding: 7, name: \"unsigned long long\", size: 64)\n", + "!4 = !DISubrange(count: 1)\n", + "!5 = !{ !4 }\n", + "!6 = !DICompositeType(baseType: !2, elements: !5, size: 32, tag: 1)\n", + "!7 = !DIDerivedType(baseType: !6, size: 64, tag: 15)\n", + "!8 = !DIDerivedType(baseType: !2, size: 64, tag: 15)\n", + "!9 = !DIDerivedType(baseType: !3, size: 64, tag: 15)\n", + "!10 = !DIDerivedType(baseType: !7, file: !0, name: \"type\", offset: 0, size: 64, tag: 13)\n", + "!11 = !DIDerivedType(baseType: !8, file: !0, name: \"key\", offset: 64, size: 64, tag: 13)\n", + "!12 = !DIDerivedType(baseType: !9, file: !0, name: \"value\", offset: 128, size: 64, tag: 13)\n", + "!13 = !DISubrange(count: 4096)\n", + "!14 = !{ !13 }\n", + "!15 = !DICompositeType(baseType: !2, elements: !14, size: 32, tag: 1)\n", + "!16 = !DIDerivedType(baseType: !15, size: 64, tag: 15)\n", + "!17 = !DIDerivedType(baseType: !16, file: !0, name: \"max_entries\", offset: 192, size: 64, tag: 13)\n", + "!18 = !{ !10, !11, !12, !17 }\n", + "!19 = distinct !DICompositeType(elements: !18, file: !0, size: 256, tag: 19)\n", + "!20 = distinct !DIGlobalVariable(file: !0, isDefinition: true, isLocal: false, name: \"hist\", scope: !1, type: !19)\n", + "!21 = !DIExpression()\n", + "!22 = !DIGlobalVariableExpression(expr: !21, var: !20)\n", + "left is %\".15\" = load i64, i64* %\".14\", right is i64 1, op is \n", + "Processing statement: Expr(value=Call(func=Name(id='print', ctx=Load()), args=[JoinedStr(values=[Constant(value='count: '), FormattedValue(value=Name(id='previous_value', ctx=Load()), conversion=-1), Constant(value=' with '), FormattedValue(value=Name(id='process_id', ctx=Load()), conversion=-1)])], keywords=[]))\n", + "Handling expression: Expr(value=Call(func=Name(id='print', ctx=Load()), args=[JoinedStr(values=[Constant(value='count: '), FormattedValue(value=Name(id='previous_value', ctx=Load()), conversion=-1), Constant(value=' with '), FormattedValue(value=Name(id='process_id', ctx=Load()), conversion=-1)])], keywords=[]))\n", + "Evaluating expression: Call(func=Name(id='print', ctx=Load()), args=[JoinedStr(values=[Constant(value='count: '), FormattedValue(value=Name(id='previous_value', ctx=Load()), conversion=-1), Constant(value=' with '), FormattedValue(value=Name(id='process_id', ctx=Load()), conversion=-1)])], keywords=[])\n", + "Value in f-string: Constant(value='count: ')\n", + "Value in f-string: FormattedValue(value=Name(id='previous_value', ctx=Load()), conversion=-1)\n", + "Formatted value: FormattedValue(value=Name(id='previous_value', ctx=Load()), conversion=-1)\n", + "Formatted value variable: %\"previous_value\" = alloca i64, align 8 i64\n", + "Value in f-string: Constant(value=' with ')\n", + "Value in f-string: FormattedValue(value=Name(id='process_id', ctx=Load()), conversion=-1)\n", + "Formatted value: FormattedValue(value=Name(id='process_id', ctx=Load()), conversion=-1)\n", + "Formatted value variable: %\"process_id\" = alloca i64, align 8 i64\n", + "Evaluating expression: Name(id='previous_value', ctx=Load())\n", + "Evaluating expression: Name(id='process_id', ctx=Load())\n", + "Processing statement: Expr(value=Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='previous_value', ctx=Load())], keywords=[]))\n", + "Handling expression: Expr(value=Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='previous_value', ctx=Load())], keywords=[]))\n", + "Evaluating expression: Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='previous_value', ctx=Load())], keywords=[])\n", + "Handling method call: Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load())\n", + "{}\n", + "Processing statement: Return(value=Call(func=Name(id='c_int64', ctx=Load()), args=[Constant(value=0)], keywords=[]))\n", + "Processing statement: Expr(value=Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='one', ctx=Load())], keywords=[]))\n", + "Handling expression: Expr(value=Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='one', ctx=Load())], keywords=[]))\n", + "Evaluating expression: Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='one', ctx=Load())], keywords=[])\n", + "Handling method call: Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load())\n", + "{}\n", + "Processing statement: Return(value=Call(func=Name(id='c_int64', ctx=Load()), args=[Constant(value=0)], keywords=[]))\n", + "IR written to /tmp/tmpx05plfu1.ll\n" + ] + } + ], + "source": [ + "@bpf\n", + "@map\n", + "def hist() -> HashMap:\n", + " return HashMap(key=c_int32, value=c_uint64, max_entries=4096)\n", + "\n", + "@bpf\n", + "@section(\"tracepoint/syscalls/sys_enter_clone\")\n", + "def hello(ctx: c_void_p) -> c_int64:\n", + " process_id = pid()\n", + " one = 1\n", + " prev = hist().lookup(process_id)\n", + " if prev:\n", + " previous_value = prev + 1\n", + " print(f\"count: {previous_value} with {process_id}\")\n", + " hist().update(process_id, previous_value)\n", + " return c_int64(0)\n", + " else:\n", + " hist().update(process_id, one)\n", + " return c_int64(0)\n", + "\n", + "\n", + "@bpf\n", + "@bpfglobal\n", + "def LICENSE() -> str:\n", + " return \"GPL\"\n", + "\n", + "b = BPF()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "93fae9f8-464e-48d6-b61e-57b9f93e508a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Recording\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAHHCAYAAABDUnkqAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAOxxJREFUeJzt3XlcFuX+//H3Dcoa4MoiEK64C65FllhipOQR66jZgnuLWpqlHU99XbKi8mhquZxOqXXMPbXSXMg1lTJLTM00V1IBdxBKVJjfH/68T3eAwi1ww/R6Ph7zeJy55rrm/szk8X57zcw9FsMwDAEAAJiEk6MLAAAAKE6EGwAAYCqEGwAAYCqEGwAAYCqEGwAAYCqEGwAAYCqEGwAAYCqEGwAAYCqEGwAAYCqEGwDFwmKxaOzYsdb1OXPmyGKx6OjRo7e87+LcFwDzI9wAZcTu3bv197//XSEhIXJzc1NgYKA6duyod99919GlwQS2bdumsWPH6sKFC44uBShxhBugDNi2bZtatWqlXbt2aeDAgXrvvfc0YMAAOTk5acqUKY4uDyawbds2jRs3jnCDv4QKji4AgPT666/Lx8dH3333nSpVqmSz7dSpU44pCgDKKWZugDLg0KFDaty4cZ5gI0m+vr7W/x0ZGamwsLB891G/fn1FR0db1xcsWKCWLVvKy8tL3t7eatq0aZ5ZoAsXLuj5559XzZo15erqqqCgIMXFxenMmTOSpMuXL2v06NFq2bKlfHx85OnpqXvuuUcbNmwohqP+n59//lk9evRQ9erV5e7urvr16+vll1++6bjp06ercePGcnV1VY0aNTR48OA8MxPt27dXkyZN9NNPP+nee++Vh4eHAgMD9fbbb+fZX3Z2tsaMGaO6devK1dVVwcHBGjlypLKzswt1HN9++606d+6sypUry9PTU82aNctzztevX6977rlHnp6eqlSpkrp27ap9+/bZ9OnTp49q1qyZZ/9jx46VxWKxabNYLBoyZIiWL1+uJk2ayNXVVY0bN9bq1attxo0YMUKSVKtWLVksFpt7mBISEnT33XerUqVKuu2221S/fn3985//LNQxA2URMzdAGRASEqLExETt2bNHTZo0KbDfE088oYEDB+bp99133+nAgQN65ZVXJF37surVq5c6dOigt956S5K0b98+bd26VUOHDpUkZWZm6p577tG+ffvUr18/tWjRQmfOnNHnn3+u48ePq1q1asrIyNAHH3ygXr16aeDAgbp48aI+/PBDRUdHa/v27QoPD7/lY//xxx91zz33qGLFinryySdVs2ZNHTp0SF988YVef/31AseNHTtW48aNU1RUlJ555hnt379fM2bM0HfffaetW7eqYsWK1r7nz5/XAw88oIceekg9evTQkiVL9NJLL6lp06bq1KmTJCk3N1d/+9vftGXLFj355JNq2LChdu/erXfeeUcHDhzQ8uXLb3gcCQkJevDBBxUQEKChQ4fK399f+/bt04oVK6zn/KuvvlKnTp1Uu3ZtjR07Vr///rveffddtW3bVj/88EO+gaYwtmzZoqVLl2rQoEHy8vLS1KlT9fDDDys5OVlVq1bVQw89pAMHDmj+/Pl65513VK1aNUlS9erVtXfvXj344INq1qyZXn31Vbm6uurgwYPaunWrXbUAZYIBwOHWrl1rODs7G87OzkZERIQxcuRIY82aNcbly5dt+l24cMFwc3MzXnrpJZv25557zvD09DQyMzMNwzCMoUOHGt7e3sbVq1cL/MzRo0cbkoylS5fm2Zabm2sYhmFcvXrVyM7Ottl2/vx5w8/Pz+jXr59NuyRjzJgx1vXZs2cbkowjR47c8NjbtWtneHl5GceOHcu3hvz2derUKcPFxcW4//77jZycHGu/9957z5BkzJo1y9oWGRlpSDI+/vhja1t2drbh7+9vPPzww9a2//73v4aTk5Px9ddf29Qxc+ZMQ5KxdevWAo/h6tWrRq1atYyQkBDj/PnzBR5HeHi44evra5w9e9batmvXLsPJycmIi4uztvXu3dsICQnJ8zljxowx/vzXtiTDxcXFOHjwoM0+JRnvvvuutW3ChAn5/vd45513DEnG6dOnCzw+oLzhshRQBnTs2FGJiYn629/+pl27duntt99WdHS0AgMD9fnnn1v7+fj4qGvXrpo/f74Mw5Ak5eTkaOHChYqNjZWnp6ckqVKlSsrKylJCQkKBn/npp58qLCxM3bp1y7Pt+qUPZ2dnubi4SLo2s3Hu3DldvXpVrVq10g8//HDLx3369Glt3rxZ/fr10+23355vDfn56quvdPnyZQ0bNkxOTv/7a2zgwIHy9vbWypUrbfrfdtttevzxx63rLi4uatOmjQ4fPmxtW7x4sRo2bKgGDRrozJkz1uW+++6TpBteitu5c6eOHDmiYcOG5bm0eP04UlJSlJSUpD59+qhKlSrW7c2aNVPHjh315ZdfFrj/m4mKilKdOnVs9unt7W1zfAW5Xu9nn32m3Nxcu2sAyhLCDVBGtG7dWkuXLtX58+e1fft2jRo1ShcvXtTf//53/fTTT9Z+cXFxSk5O1tdffy3p2hd9WlqannjiCWufQYMGKTQ0VJ06dVJQUJD69etncw+GdO0+nxtdArvuo48+UrNmzeTm5qaqVauqevXqWrlypdLT02/5mK9/+Ramjj86duyYpGv3Gf2Ri4uLateubd1+XVBQUJ6wVLlyZZ0/f966/ssvv2jv3r2qXr26zRIaGirpxjd2Hzp06KbHUVDNktSwYUOdOXNGWVlZBY6/kT8HQynv8RWkZ8+eatu2rQYMGCA/Pz898sgjWrRoEUEH5Rr33ABljIuLi1q3bq3WrVsrNDRUffv21eLFizVmzBhJUnR0tPz8/DR37ly1a9dOc+fOlb+/v6Kioqz78PX1VVJSktasWaNVq1Zp1apVmj17tuLi4vTRRx8Vupa5c+eqT58+io2N1YgRI+Tr6ytnZ2fFx8dbv9DLA2dn53zbr89+Sddmppo2bapJkybl2zc4OLhEastPQbNWOTk5+bYX5vgK4u7urs2bN2vDhg1auXKlVq9erYULF+q+++7T2rVrC9w3UJYxcwOUYa1atZJ07ZLGdc7Oznr00Ue1ZMkSnT9/XsuXL1evXr3yfAm5uLioS5cumj59ug4dOqSnnnpKH3/8sQ4ePChJqlOnjvbs2XPDz1+yZIlq166tpUuX6oknnlB0dLSioqJ06dKlYjm+2rVrS9JN6/izkJAQSdL+/ftt2i9fvqwjR45YtxdFnTp1dO7cOXXo0EFRUVF5lvxmXP44VrrxcRRUs3TtabFq1apZLytWrlw539+j+fOMVFHc6DKfk5OTOnTooEmTJumnn37S66+/rvXr1xf7U3FAaSHcAGXAhg0b8v1X9vX7MP78xfrEE0/o/Pnzeuqpp5SZmWlzP4kknT171mbdyclJzZo1kyTrY80PP/ywdu3apWXLluX53Ou1XA9Mf6zt22+/VWJiYpGOryDVq1dXu3btNGvWLCUnJ+dbQ36ioqLk4uKiqVOn2vT78MMPlZ6erpiYmCLX0qNHD504cUL/+c9/8mz7/fffb3jJqEWLFqpVq5YmT56cJ5Rcry8gIEDh4eH66KOPbPrs2bNHa9euVefOna1tderUUXp6un788UdrW0pKSr7/rQrrenD6c33nzp3L0/f6U3CFfQQeKGu4LAWUAc8++6x+++03devWTQ0aNNDly5e1bds2LVy4UDVr1lTfvn1t+jdv3lxNmjSx3gTbokULm+0DBgzQuXPndN999ykoKEjHjh3Tu+++q/DwcDVs2FCSNGLECC1ZskTdu3dXv3791LJlS507d06ff/65Zs6cqbCwMD344INaunSpunXrppiYGB05ckQzZ85Uo0aNlJmZWSzHPnXqVN19991q0aKFnnzySdWqVUtHjx7VypUrlZSUlO+Y6tWra9SoURo3bpweeOAB/e1vf9P+/fs1ffp0tW7dOk/YK4wnnnhCixYt0tNPP60NGzaobdu2ysnJ0c8//6xFixZpzZo11pm0P3NyctKMGTPUpUsXhYeHq2/fvgoICNDPP/+svXv3as2aNZKkCRMmqFOnToqIiFD//v2tj4L7+PjYvJfrkUce0UsvvaRu3brpueee02+//aYZM2YoNDTU7hu5W7ZsKUl6+eWX9cgjj6hixYrq0qWLXn31VW3evFkxMTEKCQnRqVOnNH36dAUFBenuu++267MAh3PYc1oArFatWmX069fPaNCggXHbbbcZLi4uRt26dY1nn33WSEtLy3fM22+/bUgy3njjjTzblixZYtx///2Gr6+v4eLiYtx+++3GU089ZaSkpNj0O3v2rDFkyBAjMDDQcHFxMYKCgozevXsbZ86cMQzj2mPMb7zxhhESEmK4uroazZs3N1asWJHvo8qy81FwwzCMPXv2GN26dTMqVapkuLm5GfXr1zf+7//+76b7eu+994wGDRoYFStWNPz8/Ixnnnkmz6PYkZGRRuPGjfN8Zn7HcPnyZeOtt94yGjdubLi6uhqVK1c2WrZsaYwbN85IT0+/6XFs2bLF6Nixo+Hl5WV4enoazZo1s3kc2zAM46uvvjLatm1ruLu7G97e3kaXLl2Mn376Kc++1q5dazRp0sRwcXEx6tevb8ydO7fAR8EHDx6cZ3xISIjRu3dvm7bx48cbgYGBhpOTk/V8rlu3zujatatRo0YNw8XFxahRo4bRq1cv48CBAzc9XqCsshhGIe44A1DmTJkyRc8//7yOHj2a79MyAPBXRbgByiHDMBQWFqaqVaty0ycA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AABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMhXADAABMxaHhJj4+Xq1bt5aXl5d8fX0VGxur/fv333Tc4sWL1aBBA7m5ualp06b68ssvS6FaAABQHjg03GzatEmDBw/WN998o4SEBF25ckX333+/srKyChyzbds29erVS/3799fOnTsVGxur2NhY7dmzpxQrBwAAZZXFMAzD0UVcd/r0afn6+mrTpk1q165dvn169uyprKwsrVixwtp25513Kjw8XDNnzrzpZ2RkZMjHx0fp6eny9vYuttoBACgJNf+x0tElFNnRN2OKfZ9F+f4uU/fcpKenS5KqVKlSYJ/ExERFRUXZtEVHRysxMTHf/tnZ2crIyLBZAACAeVVwdAHX5ebmatiwYWrbtq2aNGlSYL/U1FT5+fnZtPn5+Sk1NTXf/vHx8Ro3blyx1nojJGwAAByrzMzcDB48WHv27NGCBQuKdb+jRo1Senq6dfn111+Ldf8AAKBsKRMzN0OGDNGKFSu0efNmBQUF3bCvv7+/0tLSbNrS0tLk7++fb39XV1e5uroWW60AAKBsc+jMjWEYGjJkiJYtW6b169erVq1aNx0TERGhdevW2bQlJCQoIiKipMoEAADliENnbgYPHqx58+bps88+k5eXl/W+GR8fH7m7u0uS4uLiFBgYqPj4eEnS0KFDFRkZqYkTJyomJkYLFizQjh079P777zvsOAAAQNnh0JmbGTNmKD09Xe3bt1dAQIB1WbhwobVPcnKyUlJSrOt33XWX5s2bp/fff19hYWFasmSJli9ffsObkAEAwF+HQ2duCvMTOxs3bszT1r17d3Xv3r0EKgIAAOVdmXlaCgAAoDgQbgAAgKkQbgAAgKkQbgAAgKkQbgAAgKkQbgAAgKkQbgAAgKkQbgAAgKkQbgAAgKkQbgAAgKnYFW4OHz5c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AKWpgiM/vFq1anJ2dlZaWppNe1pamvz9/Qu1j4oVK6p58+Y6ePBgvttdXV3l6up6y7UCAIDywaEzNy4uLmrZsqXWrVtnbcvNzdW6desUERFRqH3k5ORo9+7dCggIKKkyAQBAOeLQmRtJGj58uHr37q1WrVqpTZs2mjx5srKystS3b19JUlxcnAIDAxUfHy9JevXVV3XnnXeqbt26unDhgiZMmKBjx45pwIABjjwMAABQRjg83PTs2VOnT5/W6NGjlZqaqvDwcK1evdp6k3FycrKcnP43wXT+/HkNHDhQqampqly5slq2bKlt27apUaNGjjoEAABQhlgMwzAcXURpysjIkI+Pj9LT0+Xt7V3s+6/5j5XFvs+SdvTNGEeXAAAoAN8r1xTl+7vcPS0FAABwI4QbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKoQbAABgKmUi3EybNk01a9aUm5ub7rjjDm3fvv2G/RcvXqwGDRrIzc1NTZs21ZdffllKlQIAgLLO4eFm4cKFGj58uMaMGaMffvhBYWFhio6O1qlTp/Ltv23bNvXq1Uv9+/fXzp07FRsbq9jYWO3Zs6eUKwcAAGWRw8PNpEmTNHDgQPXt21eNGjXSzJkz5eHhoVmzZuXbf8qUKXrggQc0YsQINWzYUOPHj1eLFi303nvvlXLlAACgLHJouLl8+bK+//57RUVFWducnJwUFRWlxMTEfMckJiba9Jek6OjoAvsDAIC/lgqO/PAzZ84oJydHfn5+Nu1+fn76+eef8x2Tmpqab//U1NR8+2dnZys7O9u6np6eLknKyMi4ldILlJv9W4nstySV1LkAANw6vlds92kYxk37OjTclIb4+HiNGzcuT3twcLADqimbfCY7ugIAgJmU5PfKxYsX5ePjc8M+Dg031apVk7Ozs9LS0mza09LS5O/vn+8Yf3//IvUfNWqUhg8fbl3Pzc3VuXPnVLVqVVkslls8AlsZGRkKDg7Wr7/+Km9v72LdN/6H81w6OM+lg/NcejjXpaOkzrNhGLp48aJq1Khx074ODTcuLi5q2bKl1q1bp9jYWEnXwse6des0ZMiQfMdERERo3bp1GjZsmLUtISFBERER+fZ3dXWVq6urTVulSpWKo/wCeXt783+cUsB5Lh2c59LBeS49nOvSURLn+WYzNtc5/LLU8OHD1bt3b7Vq1Upt2rTR5MmTlZWVpb59+0qS4uLiFBgYqPj4eEnS0KFDFRkZqYkTJyomJkYLFizQjh079P777zvyMAAAQBnh8HDTs2dPnT59WqNHj1ZqaqrCw8O1evVq603DycnJcnL630Ndd911l+bNm6dXXnlF//znP1WvXj0tX75cTZo0cdQhAACAMsTh4UaShgwZUuBlqI0bN+Zp6969u7p3717CVRWdq6urxowZk+cyGIoX57l0cJ5LB+e59HCuS0dZOM8WozDPVAEAAJQTDv+FYgAAgOJEuAEAAKZCuAEAAKZCuAEAAKZCuCmkzZs3q0uXLqpRo4YsFouWL19+0zEbN25UixYt5Orqqrp162rOnDklXqcZFPVcL126VB07dlT16tXl7e2tiIgIrVmzpnSKLcfs+TN93datW1WhQgWFh4eXWH1mYc95zs7O1ssvv6yQkBC5urqqZs2amjVrVskXW47Zc54/+eQThYWFycPDQwEBAerXr5/Onj1b8sWWY/Hx8WrdurW8vLzk6+ur2NhY7d+//6bjFi9erAYNGsjNzU1NmzbVl19+WaJ1Em4KKSsrS2FhYZo2bVqh+h85ckQxMTG69957lZSUpGHDhmnAgAF86RZCUc/15s2b1bFjR3355Zf6/vvvde+996pLly7auXNnCVdavhX1PF934cIFxcXFqUOHDiVUmbnYc5579OihdevW6cMPP9T+/fs1f/581a9fvwSrLP+Kep63bt2quLg49e/fX3v37tXixYu1fft2DRw4sIQrLd82bdqkwYMH65tvvlFCQoKuXLmi+++/X1lZWQWO2bZtm3r16qX+/ftr586dio2NVWxsrPbs2VNyhRooMknGsmXLbthn5MiRRuPGjW3aevbsaURHR5dgZeZTmHOdn0aNGhnjxo0r/oJMqijnuWfPnsYrr7xijBkzxggLCyvRusymMOd51apVho+Pj3H27NnSKcqECnOeJ0yYYNSuXdumberUqUZgYGAJVmY+p06dMiQZmzZtKrBPjx49jJiYGJu2O+64w3jqqadKrC5mbkpIYmKioqKibNqio6OVmJjooIr+OnJzc3Xx4kVVqVLF0aWYzuzZs3X48GGNGTPG0aWY1ueff65WrVrp7bffVmBgoEJDQ/Xiiy/q999/d3RpphIREaFff/1VX375pQzDUFpampYsWaLOnTs7urRyJT09XZJu+PetI74Py8QvFJtRamqq9RUS1/n5+SkjI0O///673N3dHVSZ+f3rX/9SZmamevTo4ehSTOWXX37RP/7xD3399deqUIG/OkrK4cOHtWXLFrm5uWnZsmU6c+aMBg0apLNnz2r27NmOLs802rZtq08++UQ9e/bUpUuXdPXqVXXp0qXIl2n/ynJzczVs2DC1bdv2hq9AKuj7MDU1tcRqY+YGpjJv3jyNGzdOixYtkq+vr6PLMY2cnBw9+uijGjdunEJDQx1djqnl5ubKYrHok08+UZs2bdS5c2dNmjRJH330EbM3xeinn37S0KFDNXr0aH3//fdavXq1jh49qqefftrRpZUbgwcP1p49e7RgwQJHl5IH//wqIf7+/kpLS7NpS0tLk7e3N7M2JWTBggUaMGCAFi9enGcKFLfm4sWL2rFjh3bu3Gl9D1xubq4Mw1CFChW0du1a3XfffQ6u0hwCAgIUGBgoHx8fa1vDhg1lGIaOHz+uevXqObA684iPj1fbtm01YsQISVKzZs3k6empe+65R6+99poCAgIcXGHZNmTIEK1YsUKbN29WUFDQDfsW9H3o7+9fYvUxc1NCIiIitG7dOpu2hIQERUREOKgic5s/f7769u2r+fPnKyYmxtHlmI63t7d2796tpKQk6/L000+rfv36SkpK0h133OHoEk2jbdu2OnnypDIzM61tBw4ckJOT002/RFB4v/32m5ycbL8CnZ2dJUkGr1wskGEYGjJkiJYtW6b169erVq1aNx3jiO9DZm4KKTMzUwcPHrSuHzlyRElJSapSpYpuv/12jRo1SidOnNDHH38sSXr66af13nvvaeTIkerXr5/Wr1+vRYsWaeXKlY46hHKjqOd63rx56t27t6ZMmaI7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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "b.load_and_attach()\n", + "hist = BpfMap(b, hist)\n", + "print(\"Recording\")\n", + "time.sleep(10)\n", + "\n", + "counts = list(hist.values())\n", + "\n", + "plt.hist(counts, bins=10)\n", + "plt.xlabel(\"Clone calls per PID\")\n", + "plt.ylabel(\"Frequency\")\n", + "plt.title(\"Syscall clone counts\")\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pythonbpf/bpf_helper_handler.py b/pythonbpf/bpf_helper_handler.py index 23eb6e8..59b5a86 100644 --- a/pythonbpf/bpf_helper_handler.py +++ b/pythonbpf/bpf_helper_handler.py @@ -151,12 +151,12 @@ def bpf_printk_emitter(call, map_ptr, module, builder, func, local_sym_tab=None, fmt_gvar = ir.GlobalVariable( module, ir.ArrayType(ir.IntType(8), len(fmt_str)), name=fmt_name) fmt_gvar.global_constant = True - fmt_gvar.initializer = ir.Constant( + fmt_gvar.initializer = ir.Constant( # type: ignore ir.ArrayType(ir.IntType(8), len(fmt_str)), bytearray(fmt_str.encode("utf8")) ) fmt_gvar.linkage = "internal" - fmt_gvar.align = 1 + fmt_gvar.align = 1 # type: ignore fmt_ptr = builder.bitcast(fmt_gvar, ir.PointerType()) diff --git a/pythonbpf/codegen.py b/pythonbpf/codegen.py index 52a8044..db5f05f 100644 --- a/pythonbpf/codegen.py +++ b/pythonbpf/codegen.py @@ -10,6 +10,7 @@ import subprocess import inspect from pathlib import Path from pylibbpf import BpfProgram +import tempfile def find_bpf_chunks(tree): @@ -122,14 +123,17 @@ def compile(): def BPF() -> BpfProgram: caller_frame = inspect.stack()[1] - caller_file = Path(caller_frame.filename).resolve() - ll_file = Path("/tmp") / caller_file.with_suffix(".ll").name - o_file = Path("/tmp") / caller_file.with_suffix(".o").name - compile_to_ir(str(caller_file), str(ll_file)) + src = inspect.getsource(caller_frame.frame) + with tempfile.NamedTemporaryFile(mode="w+", delete=True, suffix=".py") as f, \ + tempfile.NamedTemporaryFile(mode="w+", delete=True, suffix=".ll") as inter, \ + tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".o") as obj_file: + f.write(src) + f.flush() + source = f.name + compile_to_ir(source, str(inter.name)) + subprocess.run([ + "llc", "-march=bpf", "-filetype=obj", "-O2", + str(inter.name), "-o", str(obj_file.name) + ], check=True) - subprocess.run([ - "llc", "-march=bpf", "-filetype=obj", "-O2", - str(ll_file), "-o", str(o_file) - ], check=True) - - return BpfProgram(str(o_file)) + return BpfProgram(str(obj_file.name)) diff --git a/pythonbpf/structs_pass.py b/pythonbpf/structs_pass.py index 19ad3c9..84394e3 100644 --- a/pythonbpf/structs_pass.py +++ b/pythonbpf/structs_pass.py @@ -1,7 +1,6 @@ import ast from llvmlite import ir from .type_deducer import ctypes_to_ir -from . import dwarf_constants as dc structs_sym_tab = {}