167 Commits

Author SHA1 Message Date
9fdc6fa3ed Add compile to tests/failing_tests/conditionals/helper_cond.py 2025-10-08 07:26:41 +05:30
17004d58df Remove completed short term goal from TODO.md 2025-10-08 07:25:14 +05:30
6362a5e665 Fix expr imports 2025-10-08 07:24:14 +05:30
d38d73d5c6 Move handle_comparator to type_normalization 2025-10-08 07:20:04 +05:30
0a6571726a Move convert_to_bool to type_normalization 2025-10-08 07:14:42 +05:30
e62557bd1d Seperate type_normalization from expr_pass 2025-10-08 06:59:32 +05:30
ee90ee9392 Fix type_deducer import in expr 2025-10-08 06:50:53 +05:30
5f9eaff59c Fix expr imports 2025-10-08 06:49:34 +05:30
b86341ce7a Rework dir structure for expr 2025-10-08 06:45:52 +05:30
4857739eec cleanup handle_cond in functions_pass 2025-10-08 06:42:34 +05:30
3bb4b099c1 Add passing and.py test for conditionals 2025-10-08 06:28:03 +05:30
e7912a088f Add passing or.py test for conditionals 2025-10-08 06:27:18 +05:30
95d63d969e Add _handle_or_or in expr_pass 2025-10-08 06:24:57 +05:30
1f96bab944 Add _handle_and_op in expr_pass 2025-10-08 06:24:13 +05:30
f98491f3bd Add handle_and and handle_or handling stub in eval_expr 2025-10-08 06:14:32 +05:30
98f262ae22 Add BoolOp handling stub in eval_expr 2025-10-08 06:11:59 +05:30
d2ff53052c Add support for is and is not keywords 2025-10-08 06:04:29 +05:30
ecac24c1d2 Add explanation notes to failing conditionals tests 2025-10-08 05:57:17 +05:30
a764b095f8 Add helper_cond failing test for conditionals 2025-10-08 05:54:49 +05:30
95a196a91f Move map_comp test to passing 2025-10-08 05:53:52 +05:30
6b59980874 Add null checks for pointer derefs to avoid map_value_or_null verifier errors 2025-10-08 05:53:12 +05:30
3f9604a370 Add _deref_to_depth in expr_pass 2025-10-08 03:12:17 +05:30
480afd1341 Move _get_base_type to _get_base_type_and_depth 2025-10-08 03:02:31 +05:30
ab71275566 Add _get_base_type to expr_pass 2025-10-08 03:00:52 +05:30
2d850f457f Add _normalize_types to handle mismatched ints, move type_mismatch test to passing 2025-10-08 02:22:41 +05:30
9e1142bf05 Add type_mismatch failing test for conditionals 2025-10-07 14:02:09 +05:30
1843ca6c53 Add failing struct_ptr test for conditionals 2025-10-07 13:42:58 +05:30
caa5d92c32 Fix struct_access in eval_expr, move struct_access conditional test to passing 2025-10-07 13:35:31 +05:30
f41693bc6d Add 'and' and 'or' BoolOps as future deliverables 2025-10-07 05:27:31 +05:30
b7092fa362 Add failing test map_comp for conditionals 2025-10-07 05:20:43 +05:30
0e7dcafbab Add var_comp test for conditionals 2025-10-07 05:02:26 +05:30
a574527891 Add support for unary op 'not' in eval_expr, move not test to passing 2025-10-07 04:49:45 +05:30
176673017c Add failing tests struct and not for conditionals 2025-10-07 04:17:26 +05:30
1d6226d829 Add map test to conditionals 2025-10-07 04:06:16 +05:30
12b712c217 Add var_binop test for conditionals 2025-10-07 03:43:36 +05:30
2de280915a Add var test for conditionals 2025-10-07 03:37:13 +05:30
1cce49f5e0 Add const_binop test for conditionals 2025-10-07 03:24:11 +05:30
682a7e6566 Add const_int test for conditionals 2025-10-07 03:15:34 +05:30
fb63dbd698 Move conditional logic to eval_expr, add _conver_to_bool, add passing bool test 2025-10-07 03:11:23 +05:30
4f433d00cc Add Boolean return support 2025-10-06 23:04:45 +05:30
6cf5115ea9 Eval LHS and RHS in _handle_compare 2025-10-06 22:38:43 +05:30
f11a43010d Add _handle_cond to expr_pass 2025-10-06 22:33:03 +05:30
d1055e4d41 Reduce a condition from handle_cond 2025-10-06 22:20:54 +05:30
28ce14ce34 Merge pull request #24 from pythonbpf/func_refactor
Refactor handle_return
2025-10-06 05:12:20 +05:30
5066cd4cfe Use named args for eval_expr call in handle_return 2025-10-06 05:11:33 +05:30
0bfb3855b6 Remove dead code from _handle_ctypes_call 2025-10-06 05:10:22 +05:30
2f0dd20f1e Add false case for _handle_xdp in return_utils 2025-10-06 05:09:03 +05:30
abef68c274 Remove redundant TODO from return_utils 2025-10-06 05:04:06 +05:30
9aff614ff5 Remove unnecessary parts from return_utils 2025-10-06 05:02:02 +05:30
7b0e8a2fca Add xdp example for passing return type 2025-10-06 04:59:20 +05:30
3e68d6df4f Add passing test examples for return statements 2025-10-06 04:57:04 +05:30
b75dc82f90 Remove clutter from handle_return 2025-10-06 04:44:55 +05:30
f53ca3bd5b Add ctypes in eval_expr 2025-10-06 04:43:04 +05:30
02885af1ca Add binops to eval_expr 2025-10-06 03:36:44 +05:30
e6e2a69506 Add _is_xdp_name 2025-10-06 03:02:08 +05:30
e4e92710c0 Move XDP pass above general return handling 2025-10-06 02:58:57 +05:30
f08bc9976c Add _handle_wrapped_return 2025-10-06 02:22:43 +05:30
23183da2e1 Add _handle_variable_return 2025-10-06 00:05:23 +05:30
c6fef1693e Add _handle_binop_return 2025-10-06 00:03:34 +05:30
192e03aa98 Add _handle_typed_constant_return 2025-10-05 23:59:04 +05:30
6f02b61527 Add _handle_xdp_return 2025-10-05 23:54:06 +05:30
a21ff5633c Add _handle_none_return 2025-10-05 23:44:46 +05:30
f96a6b94dc Remove useless args from handle_return 2025-10-05 23:40:48 +05:30
e9f3aa25d2 Make handle_return (crude for now) 2025-10-05 23:19:06 +05:30
d0a8e96b70 Use getitem dunder for StatementHandlerRegistry 2025-10-05 20:10:07 +05:30
b09dc815fc Add StatementHandlerRegistry 2025-10-05 15:19:16 +05:30
ceaac78633 Janitorial: fix lint 2025-10-05 15:12:01 +05:30
dc7a127fa6 Restructure dir for functions 2025-10-05 15:09:39 +05:30
552cd352f2 Merge pull request #20 from pythonbpf/fix-failing-tests
Fix failing tests in tests/
2025-10-05 14:04:14 +05:30
c7f2955ee9 Fix typo in process_stmt 2025-10-05 14:03:19 +05:30
ef36ea1e03 Add nullcheck for var_name in handle_binary_ops 2025-10-05 14:02:08 +05:30
d341cb24c0 Update explanation for named_arg 2025-10-05 04:27:37 +05:30
2fabb67942 Add note for faling test named_arg 2025-10-05 03:15:17 +05:30
a0b0ad370e Merge pull request #23 from pythonbpf/formatter
update formatter and pre-commit
2025-10-05 01:15:01 +05:30
283b947fc5 Add named_arg failing test 2025-10-04 19:50:33 +05:30
bf78ac21fe Remove 'Static Typing' from short term tasks 2025-10-04 07:30:11 +05:30
ac49cd8b1c Fix hashmap access in direct_assign.py 2025-10-04 02:14:33 +05:30
af44bd063c Add explanation for direct_assign.py failing test 2025-10-04 02:13:46 +05:30
1239d1c35f Fix handle_binary_ops calls in functions_pass 2025-10-04 02:09:11 +05:30
f41a9ccf26 Remove unnecessary args from binary_ops 2025-10-04 02:07:31 +05:30
be05b5d102 Allow local symbols to be used within return 2025-10-03 19:50:56 +05:30
3f061750cf fix return value error 2025-10-03 19:11:11 +05:30
6d5d6345e2 Add var_rval failing test 2025-10-03 18:01:15 +05:30
6fea580693 Fix t/f/return.py, tweak handle_binary_ops 2025-10-03 17:56:21 +05:30
b35134625b Merge pull request #19 from pythonbpf/fix-expr
Refactor expr_pass
2025-10-03 17:36:31 +05:30
c3db609a90 Revert to using Warning loglevel as default 2025-10-03 17:35:57 +05:30
cc626c38f7 Move binops1 to tests/passing 2025-10-03 17:13:02 +05:30
a8b3f4f86c Fix recursive binops, move failing binops to passing 2025-10-03 17:08:41 +05:30
d593969408 Refactor ugly if-elif chain in handle_binary_op 2025-10-03 14:04:38 +05:30
6d5895ebc2 More fixes to recursive dereferencer, add get_operand value 2025-10-03 13:46:52 +05:30
c9ee6e4f17 Fix recursive_dereferencer in binops 2025-10-03 13:35:15 +05:30
a622c53e0f Add deref 2025-10-03 02:00:01 +05:30
a4f1363aed Add _handle_attribute_expr 2025-10-03 01:50:59 +05:30
3a819dcaee Add _handle_constant_expr 2025-10-02 22:54:38 +05:30
729270b34b Use _handle_name_expr in eval_expr 2025-10-02 22:50:21 +05:30
44cbcccb6c Create _handle_name_expr 2025-10-02 22:43:54 +05:30
86b9ec56d7 update formatter and pre-commit
Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>
2025-10-02 22:43:05 +05:30
253944afd2 Merge pull request #18 from pythonbpf/fix-maps
Fix map calling convention
2025-10-02 22:12:01 +05:30
54993ce5c2 Merge branch 'master' into fix-maps 2025-10-02 22:11:38 +05:30
05083bd513 janitorial nitpicks 2025-10-02 22:10:28 +05:30
6e4c340780 Allow non-call convention for maps 2025-10-02 22:07:28 +05:30
9dbca410c2 Remove calls from map in sys_sync 2025-10-02 21:24:15 +05:30
62ca3b5ffe format errors 2025-10-02 19:07:49 +05:30
f263c35156 move debug cu generation to debug module 2025-10-02 19:05:58 +05:30
0678d70309 bump version 2025-10-02 18:02:36 +05:30
96fa5687f8 Merge pull request #17 from pythonbpf/logging
add logging
2025-10-02 17:59:18 +05:30
4d0dd68d56 fix formatting 2025-10-02 17:58:24 +05:30
89b0a07419 add logging level control 2025-10-02 17:57:37 +05:30
469ca43eaa replace prints with logger.info 2025-10-02 17:46:27 +05:30
dc2b611cbc format errors
Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>
2025-10-02 05:17:02 +05:30
0c1acf1420 Fix local_sym_tab usage in binary_ops 2025-10-02 05:08:05 +05:30
71b97e3e20 Add iter to LocalSymbol 2025-10-02 04:56:34 +05:30
12ba3605e9 Fix local_sym_tab usage in helpers 2025-10-02 04:53:04 +05:30
d7427f306f Fix usage of local_sym_tab in expr_pass 2025-10-02 04:50:31 +05:30
0142381ce2 Remove local_var_metadata from expr_pass 2025-10-02 04:44:14 +05:30
9223d7b5c5 Remove local_var_metadata from helpers 2025-10-02 04:40:44 +05:30
3b74ade455 Remove occurences of local_var_metadata from functions_pass, use LocalSymbol.var 2025-10-02 04:35:10 +05:30
dadcb69f1c Store LocalSymbol in allocate_mem 2025-10-02 04:27:10 +05:30
2fd2a46838 Add LocalSymbol dataclass 2025-10-02 04:13:24 +05:30
1a66887f48 move helper annotations to helpers module 2025-10-02 01:55:32 +05:30
23f3cbcea7 add type annotations 2025-10-02 01:43:05 +05:30
429f51437f Merge pull request #15 from pythonbpf/static-type-checks
Static type checks
2025-10-02 01:38:46 +05:30
c92272dd35 workflow update 2025-10-02 01:37:36 +05:30
8792740eb0 workflow update 2025-10-02 01:36:14 +05:30
cf5faaad7f remove pointless type annotation
Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>
2025-10-02 01:27:03 +05:30
59b3d6514b fix ruff errors 2025-10-02 01:23:55 +05:30
3c956e671a add static type checking
Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>
2025-10-02 01:11:54 +05:30
8650297866 make type checks viable 2025-10-02 00:51:23 +05:30
6831f11179 Fix fstrings in examples, add alternate map attr access 2025-10-02 00:22:59 +05:30
d4e8e1bf73 Fix unterminated fstrings 2025-10-02 00:14:51 +05:30
08f2b283c9 Merge pull request #10 from pythonbpf/helper-refactor
bpf_helper_handler refactor
2025-10-02 00:08:59 +05:30
c38ecf6623 Merge branch 'master' into helper-refactor 2025-10-02 00:06:19 +05:30
81807ace34 Fix simple_string_print docstring 2025-10-01 23:59:07 +05:30
2f02f94b61 Merge pull request #14 from pythonbpf/ringbuf
make default map unspec
2025-10-01 23:58:54 +05:30
690ff7ffbc Remove unnecessary prints 2025-10-01 23:57:22 +05:30
bda88d3f8e make default map unspec 2025-10-01 23:57:00 +05:30
ba3e02052d Register output in HelperHandlerRegistry 2025-10-01 23:56:16 +05:30
9099b3eaec Replace logger.warn with logger.warning 2025-10-01 23:55:16 +05:30
03da7c5cfc Merge pull request #13 from pythonbpf/ringbuf
Add ringbuf support
2025-10-01 23:52:28 +05:30
cecf45061c Fix line length nitpicks 2025-10-01 23:51:25 +05:30
da9df2e6bf add ringbuf map type 2025-10-01 23:51:18 +05:30
929eef31ef Add has_handler to HelperHandlerRegistry 2025-10-01 23:38:38 +05:30
28cc0c5eec Refactor handle_helper_call 2025-10-01 23:32:27 +05:30
99d6c193f6 Fix calling of _simple_string_print 2025-10-01 22:53:19 +05:30
4f33db206c Refactor bpf_perf_event_output_emitter 2025-10-01 22:49:50 +05:30
6ccbab402f Complete printk refactor 2025-10-01 22:12:30 +05:30
7b01f1dde3 Complete helpers for fstrings in helper_utils 2025-10-01 21:43:11 +05:30
8ceb1d1ac3 add int32 type 2025-10-01 21:39:16 +05:30
668343532f add map types for completion 2025-10-01 21:22:44 +05:30
84ad58b775 Add ringbuf type hinting. 2025-10-01 21:14:20 +05:30
17f60d721b Add _process_*_in_fval to helper_utils 2025-10-01 20:26:18 +05:30
d18c69fae1 Add _handle_fstring_print scaffolding 2025-10-01 19:56:20 +05:30
9c58116c82 Use get_flags_val in bpf_map_update_elem_emitter 2025-10-01 18:38:18 +05:30
18f164bdec Add get_flags_val to helper_utils 2025-10-01 18:35:11 +05:30
8d9ff2df3b Fix import in sys_sync example 2025-10-01 18:28:40 +05:30
ffcd2de44d Replace usage of get_key_ptr with get_or_create_ptr_from_arg 2025-10-01 18:25:22 +05:30
8dd2746411 rename get_key_ptr to get_or_create_ptr_from_arg 2025-10-01 18:21:42 +05:30
7f6c318069 Use get_key_ptr in map_update helper 2025-10-01 18:14:32 +05:30
d2e0f17ca8 Use key_arg instead of call in get_key_ptr 2025-10-01 18:14:09 +05:30
4af6c4dcad Refactor bpf_map_delete_elem_emitter 2025-10-01 18:00:51 +05:30
244ea143d4 Refactor bpf_map_lookup_elem_emitter, add utils 2025-10-01 17:36:05 +05:30
168ab29be3 Format function definitions in bpf_helper_handler 2025-10-01 04:04:32 +05:30
61f6743f0a Use HelperHandleRegitry 2025-10-01 03:53:11 +05:30
6cd07498fe Create HelperProcessorRegistry 2025-10-01 03:07:36 +05:30
7e45864552 Move helper scripts to a new dir, make temp fixes to allow this 2025-09-30 23:57:31 +05:30
fa2ff0a242 Use BPFHelperID Enums in bpf_helper_handler 2025-09-30 23:51:05 +05:30
c1466a5bca Add BPFHelperID enum to bpf_helper_handler 2025-09-30 23:43:29 +05:30
70 changed files with 2570 additions and 1207 deletions

View File

@ -5,10 +5,7 @@ name: Format
on: on:
workflow_dispatch: workflow_dispatch:
pull_request:
push: push:
branches:
- master
jobs: jobs:
pre-commit: pre-commit:

View File

@ -21,7 +21,7 @@ ci:
repos: repos:
# Standard hooks # Standard hooks
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0 rev: v6.0.0
hooks: hooks:
- id: check-added-large-files - id: check-added-large-files
- id: check-case-conflict - id: check-case-conflict
@ -36,21 +36,20 @@ repos:
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.4.2" rev: "v0.13.2"
hooks: hooks:
- id: ruff - id: ruff
args: ["--fix", "--show-fixes"] args: ["--fix", "--show-fixes"]
- id: ruff-format - id: ruff-format
exclude: ^(docs) exclude: ^(docs)|^(tests)|^(examples)
## Checking static types # Checking static types
#- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
# rev: "v1.10.0" rev: "v1.18.2"
# hooks: hooks:
# - id: mypy - id: mypy
# files: "setup.py" exclude: ^(tests)|^(examples)
# args: [] additional_dependencies: [types-setuptools]
# additional_dependencies: [types-setuptools]
# Changes tabs to spaces # Changes tabs to spaces
- repo: https://github.com/Lucas-C/pre-commit-hooks - repo: https://github.com/Lucas-C/pre-commit-hooks

View File

@ -60,12 +60,13 @@ pip install pythonbpf pylibbpf
```python ```python
import time import time
from pythonbpf import bpf, map, section, bpfglobal, BPF from pythonbpf import bpf, map, section, bpfglobal, BPF
from pythonbpf.helpers import pid from pythonbpf.helper import pid
from pythonbpf.maps import HashMap from pythonbpf.maps import HashMap
from pylibbpf import * from pylibbpf import *
from ctypes import c_void_p, c_int64, c_uint64, c_int32 from ctypes import c_void_p, c_int64, c_uint64, c_int32
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
# This program attaches an eBPF tracepoint to sys_enter_clone, # This program attaches an eBPF tracepoint to sys_enter_clone,
# counts per-PID clone syscalls, stores them in a hash map, # counts per-PID clone syscalls, stores them in a hash map,
# and then plots the distribution as a histogram using matplotlib. # and then plots the distribution as a histogram using matplotlib.
@ -76,6 +77,7 @@ import matplotlib.pyplot as plt
def hist() -> HashMap: def hist() -> HashMap:
return HashMap(key=c_int32, value=c_uint64, max_entries=4096) return HashMap(key=c_int32, value=c_uint64, max_entries=4096)
@bpf @bpf
@section("tracepoint/syscalls/sys_enter_clone") @section("tracepoint/syscalls/sys_enter_clone")
def hello(ctx: c_void_p) -> c_int64: def hello(ctx: c_void_p) -> c_int64:

View File

@ -1,7 +1,6 @@
## Short term ## Short term
- Implement enough functionality to port the BCC tutorial examples in PythonBPF - Implement enough functionality to port the BCC tutorial examples in PythonBPF
- Static Typing
- Add all maps - Add all maps
- XDP support in pylibbpf - XDP support in pylibbpf
- ringbuf support - ringbuf support

View File

@ -1,5 +1,5 @@
from pythonbpf import bpf, map, section, bpfglobal, compile from pythonbpf import bpf, map, section, bpfglobal, compile
from pythonbpf.helpers import ktime from pythonbpf.helper import ktime
from pythonbpf.maps import HashMap from pythonbpf.maps import HashMap
from ctypes import c_void_p, c_int64, c_uint64 from ctypes import c_void_p, c_int64, c_uint64

View File

@ -1,5 +1,5 @@
from pythonbpf import bpf, map, section, bpfglobal, compile from pythonbpf import bpf, map, section, bpfglobal, compile
from pythonbpf.helpers import ktime from pythonbpf.helper import ktime
from pythonbpf.maps import HashMap from pythonbpf.maps import HashMap
from ctypes import c_void_p, c_int32, c_uint64 from ctypes import c_void_p, c_int32, c_uint64

View File

@ -10,9 +10,9 @@
"import time\n", "import time\n",
"\n", "\n",
"from pythonbpf import bpf, map, section, bpfglobal, BPF\n", "from pythonbpf import bpf, map, section, bpfglobal, BPF\n",
"from pythonbpf.helpers import pid\n", "from pythonbpf.helper import pid\n",
"from pythonbpf.maps import HashMap\n", "from pythonbpf.maps import HashMap\n",
"from pylibbpf import *\n", "from pylibbpf import BpfMap\n",
"from ctypes import c_void_p, c_int64, c_uint64, c_int32\n", "from ctypes import c_void_p, c_int64, c_uint64, c_int32\n",
"import matplotlib.pyplot as plt" "import matplotlib.pyplot as plt"
] ]

View File

@ -1,7 +1,7 @@
import time import time
from pythonbpf import bpf, map, section, bpfglobal, BPF from pythonbpf import bpf, map, section, bpfglobal, BPF
from pythonbpf.helpers import pid from pythonbpf.helper import pid
from pythonbpf.maps import HashMap from pythonbpf.maps import HashMap
from pylibbpf import BpfMap from pylibbpf import BpfMap
from ctypes import c_void_p, c_int64, c_uint64, c_int32 from ctypes import c_void_p, c_int64, c_uint64, c_int32

View File

@ -1,5 +1,5 @@
from pythonbpf import bpf, map, struct, section, bpfglobal, compile from pythonbpf import bpf, map, struct, section, bpfglobal, compile
from pythonbpf.helpers import ktime, pid from pythonbpf.helper import ktime, pid
from pythonbpf.maps import PerfEventArray from pythonbpf.maps import PerfEventArray
from ctypes import c_void_p, c_int32, c_uint64 from ctypes import c_void_p, c_int32, c_uint64
@ -23,14 +23,11 @@ def events() -> PerfEventArray:
@section("tracepoint/syscalls/sys_enter_clone") @section("tracepoint/syscalls/sys_enter_clone")
def hello(ctx: c_void_p) -> c_int32: def hello(ctx: c_void_p) -> c_int32:
dataobj = data_t() dataobj = data_t()
ts = ktime()
strobj = "hellohellohello" strobj = "hellohellohello"
dataobj.pid = pid() dataobj.pid = pid()
dataobj.ts = ktime() dataobj.ts = ktime()
# dataobj.comm = strobj # dataobj.comm = strobj
print( print(f"clone called at {dataobj.ts} by pid" f"{dataobj.pid}, comm {strobj}")
f"clone called at {dataobj.ts} by pid {dataobj.pid}, comm {strobj} at time {ts}"
)
events.output(dataobj) events.output(dataobj)
return c_int32(0) return c_int32(0)

View File

@ -1,5 +1,5 @@
from pythonbpf import bpf, map, section, bpfglobal, compile from pythonbpf import bpf, map, section, bpfglobal, compile
from pythonbpf.helpers import ktime from pythonbpf.helper import ktime
from pythonbpf.maps import HashMap from pythonbpf.maps import HashMap
from ctypes import c_void_p, c_int64, c_uint64 from ctypes import c_void_p, c_int64, c_uint64
@ -21,17 +21,17 @@ def last() -> HashMap:
@section("tracepoint/syscalls/sys_enter_sync") @section("tracepoint/syscalls/sys_enter_sync")
def do_trace(ctx: c_void_p) -> c_int64: def do_trace(ctx: c_void_p) -> c_int64:
key = 0 key = 0
tsp = last().lookup(key) tsp = last.lookup(key)
if tsp: if tsp:
kt = ktime() kt = ktime()
delta = kt - tsp delta = kt - tsp
if delta < 1000000000: if delta < 1000000000:
time_ms = delta // 1000000 time_ms = delta // 1000000
print(f"sync called within last second, last {time_ms} ms ago") print(f"sync called within last second, last {time_ms} ms ago")
last().delete(key) last.delete(key)
else: else:
kt = ktime() kt = ktime()
last().update(key, kt) last.update(key, kt)
return c_int64(0) return c_int64(0)

View File

@ -1,5 +1,5 @@
from pythonbpf import bpf, map, section, bpfglobal, compile from pythonbpf import bpf, map, section, bpfglobal, compile
from pythonbpf.helpers import XDP_PASS from pythonbpf.helper import XDP_PASS
from pythonbpf.maps import HashMap from pythonbpf.maps import HashMap
from ctypes import c_void_p, c_int64 from ctypes import c_void_p, c_int64

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "pythonbpf" name = "pythonbpf"
version = "0.1.3" version = "0.1.4"
description = "Reduced Python frontend for eBPF" description = "Reduced Python frontend for eBPF"
authors = [ authors = [
{ name = "r41k0u", email="pragyanshchaturvedi18@gmail.com" }, { name = "r41k0u", email="pragyanshchaturvedi18@gmail.com" },

View File

@ -1,71 +1,72 @@
import ast import ast
from llvmlite import ir from llvmlite import ir
from logging import Logger
import logging
logger: Logger = logging.getLogger(__name__)
def recursive_dereferencer(var, builder): def recursive_dereferencer(var, builder):
"""dereference until primitive type comes out""" """dereference until primitive type comes out"""
if var.type == ir.PointerType(ir.PointerType(ir.IntType(64))): # TODO: Not worrying about stack overflow for now
logger.info(f"Dereferencing {var}, type is {var.type}")
if isinstance(var.type, ir.PointerType):
a = builder.load(var) a = builder.load(var)
return recursive_dereferencer(a, builder) return recursive_dereferencer(a, builder)
elif var.type == ir.PointerType(ir.IntType(64)): elif isinstance(var.type, ir.IntType):
a = builder.load(var)
return recursive_dereferencer(a, builder)
elif var.type == ir.IntType(64):
return var return var
else: else:
raise TypeError(f"Unsupported type for dereferencing: {var.type}") raise TypeError(f"Unsupported type for dereferencing: {var.type}")
def handle_binary_op(rval, module, builder, var_name, local_sym_tab, map_sym_tab, func): def get_operand_value(operand, builder, local_sym_tab):
print(module) """Extract the value from an operand, handling variables and constants."""
left = rval.left if isinstance(operand, ast.Name):
right = rval.right if operand.id in local_sym_tab:
return recursive_dereferencer(local_sym_tab[operand.id].var, builder)
raise ValueError(f"Undefined variable: {operand.id}")
elif isinstance(operand, ast.Constant):
if isinstance(operand.value, int):
return ir.Constant(ir.IntType(64), operand.value)
raise TypeError(f"Unsupported constant type: {type(operand.value)}")
elif isinstance(operand, ast.BinOp):
return handle_binary_op_impl(operand, builder, local_sym_tab)
raise TypeError(f"Unsupported operand type: {type(operand)}")
def handle_binary_op_impl(rval, builder, local_sym_tab):
op = rval.op op = rval.op
left = get_operand_value(rval.left, builder, local_sym_tab)
right = get_operand_value(rval.right, builder, local_sym_tab)
logger.info(f"left is {left}, right is {right}, op is {op}")
# Handle left operand # Map AST operation nodes to LLVM IR builder methods
if isinstance(left, ast.Name): op_map = {
if left.id in local_sym_tab: ast.Add: builder.add,
left = recursive_dereferencer(local_sym_tab[left.id][0], builder) ast.Sub: builder.sub,
else: ast.Mult: builder.mul,
raise SyntaxError(f"Undefined variable: {left.id}") ast.Div: builder.sdiv,
elif isinstance(left, ast.Constant): ast.Mod: builder.srem,
left = ir.Constant(ir.IntType(64), left.value) ast.LShift: builder.shl,
else: ast.RShift: builder.lshr,
raise SyntaxError("Unsupported left operand type") ast.BitOr: builder.or_,
ast.BitXor: builder.xor,
ast.BitAnd: builder.and_,
ast.FloorDiv: builder.udiv,
}
if isinstance(right, ast.Name): if type(op) in op_map:
if right.id in local_sym_tab: result = op_map[type(op)](left, right)
right = recursive_dereferencer(local_sym_tab[right.id][0], builder) return result
else:
raise SyntaxError(f"Undefined variable: {right.id}")
elif isinstance(right, ast.Constant):
right = ir.Constant(ir.IntType(64), right.value)
else:
raise SyntaxError("Unsupported right operand type")
print(f"left is {left}, right is {right}, op is {op}")
if isinstance(op, ast.Add):
builder.store(builder.add(left, right), local_sym_tab[var_name][0])
elif isinstance(op, ast.Sub):
builder.store(builder.sub(left, right), local_sym_tab[var_name][0])
elif isinstance(op, ast.Mult):
builder.store(builder.mul(left, right), local_sym_tab[var_name][0])
elif isinstance(op, ast.Div):
builder.store(builder.sdiv(left, right), local_sym_tab[var_name][0])
elif isinstance(op, ast.Mod):
builder.store(builder.srem(left, right), local_sym_tab[var_name][0])
elif isinstance(op, ast.LShift):
builder.store(builder.shl(left, right), local_sym_tab[var_name][0])
elif isinstance(op, ast.RShift):
builder.store(builder.lshr(left, right), local_sym_tab[var_name][0])
elif isinstance(op, ast.BitOr):
builder.store(builder.or_(left, right), local_sym_tab[var_name][0])
elif isinstance(op, ast.BitXor):
builder.store(builder.xor(left, right), local_sym_tab[var_name][0])
elif isinstance(op, ast.BitAnd):
builder.store(builder.and_(left, right), local_sym_tab[var_name][0])
elif isinstance(op, ast.FloorDiv):
builder.store(builder.udiv(left, right), local_sym_tab[var_name][0])
else: else:
raise SyntaxError("Unsupported binary operation") raise SyntaxError("Unsupported binary operation")
def handle_binary_op(rval, builder, var_name, local_sym_tab):
result = handle_binary_op_impl(rval, builder, local_sym_tab)
if var_name and var_name in local_sym_tab:
logger.info(
f"Storing result {result} into variable {local_sym_tab[var_name].var}"
)
builder.store(result, local_sym_tab[var_name].var)
return result, result.type

View File

@ -1,654 +0,0 @@
import ast
from llvmlite import ir
from .expr_pass import eval_expr
def bpf_ktime_get_ns_emitter(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
local_var_metadata=None,
):
"""
Emit LLVM IR for bpf_ktime_get_ns helper function call.
"""
# func is an arg to just have a uniform signature with other emitters
helper_id = ir.Constant(ir.IntType(64), 5)
fn_type = ir.FunctionType(ir.IntType(64), [], var_arg=False)
fn_ptr_type = ir.PointerType(fn_type)
fn_ptr = builder.inttoptr(helper_id, fn_ptr_type)
result = builder.call(fn_ptr, [], tail=False)
return result, ir.IntType(64)
def bpf_map_lookup_elem_emitter(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
local_var_metadata=None,
):
"""
Emit LLVM IR for bpf_map_lookup_elem helper function call.
"""
if call.args and len(call.args) != 1:
raise ValueError(
"Map lookup expects exactly one argument, got " f"{len(call.args)}"
)
key_arg = call.args[0]
if isinstance(key_arg, ast.Name):
key_name = key_arg.id
if local_sym_tab and key_name in local_sym_tab:
key_ptr = local_sym_tab[key_name][0]
else:
raise ValueError(
f"Key variable {key_name} not found in local symbol table."
)
elif isinstance(key_arg, ast.Constant) and isinstance(key_arg.value, int):
# handle constant integer keys
key_val = key_arg.value
key_type = ir.IntType(64)
key_ptr = builder.alloca(key_type)
key_ptr.align = key_type // 8
builder.store(ir.Constant(key_type, key_val), key_ptr)
else:
raise NotImplementedError(
"Only simple variable names are supported as keys in map lookup."
)
if key_ptr is None:
raise ValueError("Key pointer is None.")
map_void_ptr = builder.bitcast(map_ptr, ir.PointerType())
fn_type = ir.FunctionType(
ir.PointerType(), # Return type: void*
[ir.PointerType(), ir.PointerType()], # Args: (void*, void*)
var_arg=False,
)
fn_ptr_type = ir.PointerType(fn_type)
# Helper ID 1 is bpf_map_lookup_elem
fn_addr = ir.Constant(ir.IntType(64), 1)
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
result = builder.call(fn_ptr, [map_void_ptr, key_ptr], tail=False)
return result, ir.PointerType()
def bpf_printk_emitter(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
local_var_metadata=None,
):
if not hasattr(func, "_fmt_counter"):
func._fmt_counter = 0
if not call.args:
raise ValueError("print expects at least one argument")
if isinstance(call.args[0], ast.JoinedStr):
fmt_parts = []
exprs = []
for value in call.args[0].values:
print("Value in f-string:", ast.dump(value))
if isinstance(value, ast.Constant):
if isinstance(value.value, str):
fmt_parts.append(value.value)
elif isinstance(value.value, int):
fmt_parts.append("%lld")
exprs.append(ir.Constant(ir.IntType(64), value.value))
else:
raise NotImplementedError(
"Only string and integer constants are supported in f-string."
)
elif isinstance(value, ast.FormattedValue):
print("Formatted value:", ast.dump(value))
# TODO: Dirty handling here, only checks for int or str
if isinstance(value.value, ast.Name):
if local_sym_tab and value.value.id in local_sym_tab:
var_ptr, var_type = local_sym_tab[value.value.id]
if isinstance(var_type, ir.IntType):
fmt_parts.append("%lld")
exprs.append(value.value)
elif var_type == ir.PointerType(ir.IntType(8)):
# Case with string
fmt_parts.append("%s")
exprs.append(value.value)
else:
raise NotImplementedError(
"Only integer and pointer types are supported in formatted values."
)
else:
raise ValueError(
f"Variable {value.value.id} not found in local symbol table."
)
elif isinstance(value.value, ast.Attribute):
# object field access from struct
if (
isinstance(value.value.value, ast.Name)
and local_sym_tab
and value.value.value.id in local_sym_tab
):
var_name = value.value.value.id
field_name = value.value.attr
if local_var_metadata and var_name in local_var_metadata:
var_type = local_var_metadata[var_name]
if var_type in struct_sym_tab:
struct_info = struct_sym_tab[var_type]
if field_name in struct_info.fields:
field_type = struct_info.field_type(field_name)
if isinstance(field_type, ir.IntType):
fmt_parts.append("%lld")
exprs.append(value.value)
elif field_type == ir.PointerType(ir.IntType(8)):
fmt_parts.append("%s")
exprs.append(value.value)
else:
raise NotImplementedError(
"Only integer and pointer types are supported in formatted values."
)
else:
raise ValueError(
f"Field {field_name} not found in struct {var_type}."
)
else:
raise ValueError(
f"Struct type {var_type} for variable {var_name} not found in struct symbol table."
)
else:
raise ValueError(
f"Metadata for variable {var_name} not found in local variable metadata."
)
else:
raise ValueError(
f"Variable {value.value.value.id} not found in local symbol table."
)
else:
raise NotImplementedError(
"Only simple variable names are supported in formatted values."
)
else:
raise NotImplementedError("Unsupported value type in f-string.")
fmt_str = "".join(fmt_parts) + "\n" + "\0"
fmt_name = f"{func.name}____fmt{func._fmt_counter}"
func._fmt_counter += 1
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( # type: ignore
ir.ArrayType(ir.IntType(8), len(fmt_str)), bytearray(fmt_str.encode("utf8"))
)
fmt_gvar.linkage = "internal"
fmt_gvar.align = 1 # type: ignore
fmt_ptr = builder.bitcast(fmt_gvar, ir.PointerType())
args = [fmt_ptr, ir.Constant(ir.IntType(32), len(fmt_str))]
# Only 3 args supported in bpf_printk
if len(exprs) > 3:
print(
"Warning: bpf_printk supports up to 3 arguments, extra arguments will be ignored."
)
for expr in exprs[:3]:
print(f"{ast.dump(expr)}")
val, _ = eval_expr(
func,
module,
builder,
expr,
local_sym_tab,
None,
struct_sym_tab,
local_var_metadata,
)
if val:
if isinstance(val.type, ir.PointerType):
val = builder.ptrtoint(val, ir.IntType(64))
elif isinstance(val.type, ir.IntType):
if val.type.width < 64:
val = builder.sext(val, ir.IntType(64))
else:
print(
"Warning: Only integer and pointer types are supported in bpf_printk arguments. Others will be converted to 0."
)
val = ir.Constant(ir.IntType(64), 0)
args.append(val)
else:
print(
"Warning: Failed to evaluate expression for bpf_printk argument. It will be converted to 0."
)
args.append(ir.Constant(ir.IntType(64), 0))
fn_type = ir.FunctionType(
ir.IntType(64), [ir.PointerType(), 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)
return builder.call(fn_ptr, args, tail=True)
for arg in call.args:
if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
fmt_str = arg.value + "\n" + "\0"
fmt_name = f"{func.name}____fmt{func._fmt_counter}"
func._fmt_counter += 1
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( # type: ignore
ir.ArrayType(ir.IntType(8), len(fmt_str)),
bytearray(fmt_str.encode("utf8")),
)
fmt_gvar.linkage = "internal"
fmt_gvar.align = 1 # type: ignore
fmt_ptr = builder.bitcast(fmt_gvar, ir.PointerType())
fn_type = ir.FunctionType(
ir.IntType(64), [ir.PointerType(), 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)
builder.call(
fn_ptr, [fmt_ptr, ir.Constant(ir.IntType(32), len(fmt_str))], tail=True
)
return None
def bpf_map_update_elem_emitter(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
local_var_metadata=None,
):
"""
Emit LLVM IR for bpf_map_update_elem helper function call.
Expected call signature: map.update(key, value, flags=0)
"""
if not call.args or len(call.args) < 2 or len(call.args) > 3:
raise ValueError(
"Map update expects 2 or 3 arguments (key, value, flags), got "
f"{len(call.args)}"
)
key_arg = call.args[0]
value_arg = call.args[1]
flags_arg = call.args[2] if len(call.args) > 2 else None
# Handle key
if isinstance(key_arg, ast.Name):
key_name = key_arg.id
if local_sym_tab and key_name in local_sym_tab:
key_ptr = local_sym_tab[key_name][0]
else:
raise ValueError(
f"Key variable {key_name} not found in local symbol table."
)
elif isinstance(key_arg, ast.Constant) and isinstance(key_arg.value, int):
# Handle constant integer keys
key_val = key_arg.value
key_type = ir.IntType(64)
key_ptr = builder.alloca(key_type)
key_ptr.align = key_type.width // 8
builder.store(ir.Constant(key_type, key_val), key_ptr)
else:
raise NotImplementedError(
"Only simple variable names and integer constants are supported as keys in map update."
)
# Handle value
if isinstance(value_arg, ast.Name):
value_name = value_arg.id
if local_sym_tab and value_name in local_sym_tab:
value_ptr = local_sym_tab[value_name][0]
else:
raise ValueError(
f"Value variable {value_name} not found in local symbol table."
)
elif isinstance(value_arg, ast.Constant) and isinstance(value_arg.value, int):
# Handle constant integers
value_val = value_arg.value
value_type = ir.IntType(64)
value_ptr = builder.alloca(value_type)
value_ptr.align = value_type.width // 8
builder.store(ir.Constant(value_type, value_val), value_ptr)
else:
raise NotImplementedError(
"Only simple variable names and integer constants are supported as values in map update."
)
# Handle flags argument (defaults to 0)
if flags_arg is not None:
if isinstance(flags_arg, ast.Constant) and isinstance(flags_arg.value, int):
flags_val = flags_arg.value
elif isinstance(flags_arg, ast.Name):
flags_name = flags_arg.id
if local_sym_tab and flags_name in local_sym_tab:
# Assume it's a stored integer value, load it
flags_ptr = local_sym_tab[flags_name][0]
flags_val = builder.load(flags_ptr)
else:
raise ValueError(
f"Flags variable {flags_name} not found in local symbol table."
)
else:
raise NotImplementedError(
"Only integer constants and simple variable names are supported as flags in map update."
)
else:
flags_val = 0
if key_ptr is None or value_ptr is None:
raise ValueError("Key pointer or value pointer is None.")
map_void_ptr = builder.bitcast(map_ptr, ir.PointerType())
fn_type = ir.FunctionType(
ir.IntType(64),
[ir.PointerType(), ir.PointerType(), ir.PointerType(), ir.IntType(64)],
var_arg=False,
)
fn_ptr_type = ir.PointerType(fn_type)
# helper id
fn_addr = ir.Constant(ir.IntType(64), 2)
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
if isinstance(flags_val, int):
flags_const = ir.Constant(ir.IntType(64), flags_val)
else:
flags_const = flags_val
result = builder.call(
fn_ptr, [map_void_ptr, key_ptr, value_ptr, flags_const], tail=False
)
return result, None
def bpf_map_delete_elem_emitter(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
local_var_metadata=None,
):
"""
Emit LLVM IR for bpf_map_delete_elem helper function call.
Expected call signature: map.delete(key)
"""
# Check for correct number of arguments
if not call.args or len(call.args) != 1:
raise ValueError(
"Map delete expects exactly 1 argument (key), got " f"{len(call.args)}"
)
key_arg = call.args[0]
# Handle key argument
if isinstance(key_arg, ast.Name):
key_name = key_arg.id
if local_sym_tab and key_name in local_sym_tab:
key_ptr = local_sym_tab[key_name][0]
else:
raise ValueError(
f"Key variable {key_name} not found in local symbol table."
)
elif isinstance(key_arg, ast.Constant) and isinstance(key_arg.value, int):
# Handle constant integer keys
key_val = key_arg.value
key_type = ir.IntType(64)
key_ptr = builder.alloca(key_type)
key_ptr.align = key_type.width // 8
builder.store(ir.Constant(key_type, key_val), key_ptr)
else:
raise NotImplementedError(
"Only simple variable names and integer constants are supported as keys in map delete."
)
if key_ptr is None:
raise ValueError("Key pointer is None.")
# Cast map pointer to void*
map_void_ptr = builder.bitcast(map_ptr, ir.PointerType())
# Define function type for bpf_map_delete_elem
fn_type = ir.FunctionType(
ir.IntType(64), # Return type: int64 (status code)
[ir.PointerType(), ir.PointerType()], # Args: (void*, void*)
var_arg=False,
)
fn_ptr_type = ir.PointerType(fn_type)
# Helper ID 3 is bpf_map_delete_elem
fn_addr = ir.Constant(ir.IntType(64), 3)
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
# Call the helper function
result = builder.call(fn_ptr, [map_void_ptr, key_ptr], tail=False)
return result, None
def bpf_get_current_pid_tgid_emitter(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
local_var_metadata=None,
):
"""
Emit LLVM IR for bpf_get_current_pid_tgid helper function call.
"""
# func is an arg to just have a uniform signature with other emitters
helper_id = ir.Constant(ir.IntType(64), 14)
fn_type = ir.FunctionType(ir.IntType(64), [], var_arg=False)
fn_ptr_type = ir.PointerType(fn_type)
fn_ptr = builder.inttoptr(helper_id, fn_ptr_type)
result = builder.call(fn_ptr, [], tail=False)
# Extract the lower 32 bits (PID) using bitwise AND with 0xFFFFFFFF
mask = ir.Constant(ir.IntType(64), 0xFFFFFFFF)
pid = builder.and_(result, mask)
return pid, ir.IntType(64)
def bpf_perf_event_output_handler(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
local_var_metadata=None,
):
if len(call.args) != 1:
raise ValueError(
"Perf event output expects exactly one argument (data), got "
f"{len(call.args)}"
)
data_arg = call.args[0]
ctx_ptr = func.args[0] # First argument to the function is ctx
if isinstance(data_arg, ast.Name):
data_name = data_arg.id
if local_sym_tab and data_name in local_sym_tab:
data_ptr = local_sym_tab[data_name][0]
else:
raise ValueError(
f"Data variable {data_name} not found in local symbol table."
)
# Check is data_name is a struct
if local_var_metadata and data_name in local_var_metadata:
data_type = local_var_metadata[data_name]
if data_type in struct_sym_tab:
struct_info = struct_sym_tab[data_type]
size_val = ir.Constant(ir.IntType(64), struct_info.size)
else:
raise ValueError(
f"Struct type {data_type} for variable {data_name} not found in struct symbol table."
)
else:
raise ValueError(
f"Metadata for variable {data_name} not found in local variable metadata."
)
# BPF_F_CURRENT_CPU is -1 in 32 bit
flags_val = ir.Constant(ir.IntType(64), 0xFFFFFFFF)
map_void_ptr = builder.bitcast(map_ptr, ir.PointerType())
data_void_ptr = builder.bitcast(data_ptr, ir.PointerType())
fn_type = ir.FunctionType(
ir.IntType(64),
[
ir.PointerType(ir.IntType(8)),
ir.PointerType(),
ir.IntType(64),
ir.PointerType(),
ir.IntType(64),
],
var_arg=False,
)
fn_ptr_type = ir.PointerType(fn_type)
# helper id
fn_addr = ir.Constant(ir.IntType(64), 25)
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
result = builder.call(
fn_ptr,
[ctx_ptr, map_void_ptr, flags_val, data_void_ptr, size_val],
tail=False,
)
return result, None
else:
raise NotImplementedError(
"Only simple object names are supported as data in perf event output."
)
helper_func_list = {
"lookup": bpf_map_lookup_elem_emitter,
"print": bpf_printk_emitter,
"ktime": bpf_ktime_get_ns_emitter,
"update": bpf_map_update_elem_emitter,
"delete": bpf_map_delete_elem_emitter,
"pid": bpf_get_current_pid_tgid_emitter,
"output": bpf_perf_event_output_handler,
}
def handle_helper_call(
call,
module,
builder,
func,
local_sym_tab=None,
map_sym_tab=None,
struct_sym_tab=None,
local_var_metadata=None,
):
print(local_var_metadata)
if isinstance(call.func, ast.Name):
func_name = call.func.id
if func_name in helper_func_list:
# it is not a map method call
return helper_func_list[func_name](
call,
None,
module,
builder,
func,
local_sym_tab,
struct_sym_tab,
local_var_metadata,
)
else:
raise NotImplementedError(
f"Function {func_name} is not implemented as a helper function."
)
elif isinstance(call.func, ast.Attribute):
# likely a map method call
if isinstance(call.func.value, ast.Call) and isinstance(
call.func.value.func, ast.Name
):
map_name = call.func.value.func.id
method_name = call.func.attr
if map_sym_tab and map_name in map_sym_tab:
map_ptr = map_sym_tab[map_name]
if method_name in helper_func_list:
print(local_var_metadata)
return helper_func_list[method_name](
call,
map_ptr,
module,
builder,
func,
local_sym_tab,
struct_sym_tab,
local_var_metadata,
)
else:
raise NotImplementedError(
f"Map method {method_name} is not implemented as a helper function."
)
else:
raise ValueError(f"Map variable {map_name} not found in symbol tables.")
elif isinstance(call.func.value, ast.Name):
obj_name = call.func.value.id
method_name = call.func.attr
if map_sym_tab and obj_name in map_sym_tab:
map_ptr = map_sym_tab[obj_name]
if method_name in helper_func_list:
return helper_func_list[method_name](
call,
map_ptr,
module,
builder,
func,
local_sym_tab,
struct_sym_tab,
local_var_metadata,
)
else:
raise NotImplementedError(
f"Map method {method_name} is not implemented as a helper function."
)
else:
raise ValueError(f"Map variable {obj_name} not found in symbol tables.")
else:
raise NotImplementedError("Attribute not supported for map method calls.")
return None

View File

@ -1,19 +1,23 @@
import ast import ast
from llvmlite import ir from llvmlite import ir
from .license_pass import license_processing from .license_pass import license_processing
from .functions_pass import func_proc from .functions import func_proc
from .maps import maps_proc from .maps import maps_proc
from .structs import structs_proc from .structs import structs_proc
from .globals_pass import globals_processing from .globals_pass import globals_processing
from .debuginfo import DW_LANG_C11, DwarfBehaviorEnum from .debuginfo import DW_LANG_C11, DwarfBehaviorEnum, DebugInfoGenerator
import os import os
import subprocess import subprocess
import inspect import inspect
from pathlib import Path from pathlib import Path
from pylibbpf import BpfProgram from pylibbpf import BpfProgram
import tempfile import tempfile
from logging import Logger
import logging
VERSION = "v0.1.3" logger: Logger = logging.getLogger(__name__)
VERSION = "v0.1.4"
def find_bpf_chunks(tree): def find_bpf_chunks(tree):
@ -30,11 +34,11 @@ def find_bpf_chunks(tree):
def processor(source_code, filename, module): def processor(source_code, filename, module):
tree = ast.parse(source_code, filename) tree = ast.parse(source_code, filename)
print(ast.dump(tree, indent=4)) logger.debug(ast.dump(tree, indent=4))
bpf_chunks = find_bpf_chunks(tree) bpf_chunks = find_bpf_chunks(tree)
for func_node in bpf_chunks: for func_node in bpf_chunks:
print(f"Found BPF function/struct: {func_node.name}") logger.info(f"Found BPF function/struct: {func_node.name}")
structs_sym_tab = structs_proc(tree, module, bpf_chunks) structs_sym_tab = structs_proc(tree, module, bpf_chunks)
map_sym_tab = maps_proc(tree, module, bpf_chunks) map_sym_tab = maps_proc(tree, module, bpf_chunks)
@ -44,7 +48,10 @@ def processor(source_code, filename, module):
globals_processing(tree, module) globals_processing(tree, module)
def compile_to_ir(filename: str, output: str): def compile_to_ir(filename: str, output: str, loglevel=logging.INFO):
logging.basicConfig(
level=loglevel, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
with open(filename) as f: with open(filename) as f:
source = f.read() source = f.read()
@ -53,33 +60,17 @@ def compile_to_ir(filename: str, output: str):
module.triple = "bpf" module.triple = "bpf"
if not hasattr(module, "_debug_compile_unit"): if not hasattr(module, "_debug_compile_unit"):
module._file_metadata = module.add_debug_info( debug_generator = DebugInfoGenerator(module)
"DIFile", debug_generator.generate_file_metadata(filename, os.path.dirname(filename))
{ # type: ignore debug_generator.generate_debug_cu(
"filename": filename, DW_LANG_C11,
"directory": os.path.dirname(filename), f"PythonBPF {VERSION}",
}, True, # TODO: This is probably not true
# TODO: add a global field here that keeps track of all the globals. Works without it, but I think it might
# be required for kprobes.
True,
) )
module._debug_compile_unit = module.add_debug_info(
"DICompileUnit",
{ # type: ignore
"language": DW_LANG_C11,
"file": module._file_metadata, # type: ignore
"producer": f"PythonBPF {VERSION}",
"isOptimized": True, # TODO: This is probably not true
# TODO: add a global field here that keeps track of all the globals. Works without it, but I think it might
# be required for kprobes.
"runtimeVersion": 0,
"emissionKind": 1,
"splitDebugInlining": False,
"nameTableKind": 0,
},
is_distinct=True,
)
module.add_named_metadata("llvm.dbg.cu", module._debug_compile_unit) # type: ignore
processor(source, filename, module) processor(source, filename, module)
wchar_size = module.add_metadata( wchar_size = module.add_metadata(
@ -121,7 +112,7 @@ def compile_to_ir(filename: str, output: str):
module.add_named_metadata("llvm.ident", [f"PythonBPF {VERSION}"]) module.add_named_metadata("llvm.ident", [f"PythonBPF {VERSION}"])
print(f"IR written to {output}") logger.info(f"IR written to {output}")
with open(output, "w") as f: with open(output, "w") as f:
f.write(f'source_filename = "{filename}"\n') f.write(f'source_filename = "{filename}"\n')
f.write(str(module)) f.write(str(module))
@ -130,7 +121,7 @@ def compile_to_ir(filename: str, output: str):
return output return output
def compile() -> bool: def compile(loglevel=logging.INFO) -> bool:
# Look one level up the stack to the caller of this function # Look one level up the stack to the caller of this function
caller_frame = inspect.stack()[1] caller_frame = inspect.stack()[1]
caller_file = Path(caller_frame.filename).resolve() caller_file = Path(caller_frame.filename).resolve()
@ -139,9 +130,11 @@ def compile() -> bool:
o_file = caller_file.with_suffix(".o") o_file = caller_file.with_suffix(".o")
success = True success = True
success = compile_to_ir(str(caller_file), str(ll_file)) and success
success = ( success = (
compile_to_ir(str(caller_file), str(ll_file), loglevel=loglevel) and success
)
success = bool(
subprocess.run( subprocess.run(
[ [
"llc", "llc",
@ -157,11 +150,11 @@ def compile() -> bool:
and success and success
) )
print(f"Object written to {o_file}") logger.info(f"Object written to {o_file}")
return success return success
def BPF() -> BpfProgram: def BPF(loglevel=logging.INFO) -> BpfProgram:
caller_frame = inspect.stack()[1] caller_frame = inspect.stack()[1]
src = inspect.getsource(caller_frame.frame) src = inspect.getsource(caller_frame.frame)
with tempfile.NamedTemporaryFile( with tempfile.NamedTemporaryFile(
@ -174,7 +167,7 @@ def BPF() -> BpfProgram:
f.write(src) f.write(src)
f.flush() f.flush()
source = f.name source = f.name
compile_to_ir(source, str(inter.name)) compile_to_ir(source, str(inter.name), loglevel=loglevel)
subprocess.run( subprocess.run(
[ [
"llc", "llc",

View File

@ -12,6 +12,34 @@ class DebugInfoGenerator:
self.module = module self.module = module
self._type_cache = {} # Cache for common debug types self._type_cache = {} # Cache for common debug types
def generate_file_metadata(self, filename, dirname):
self.module._file_metadata = self.module.add_debug_info(
"DIFile",
{ # type: ignore
"filename": filename,
"directory": dirname,
},
)
def generate_debug_cu(
self, language, producer: str, is_optimized: bool, is_distinct: bool
):
self.module._debug_compile_unit = self.module.add_debug_info(
"DICompileUnit",
{ # type: ignore
"language": language,
"file": self.module._file_metadata, # type: ignore
"producer": producer,
"isOptimized": is_optimized,
"runtimeVersion": 0,
"emissionKind": 1,
"splitDebugInlining": False,
"nameTableKind": 0,
},
is_distinct=is_distinct,
)
self.module.add_named_metadata("llvm.dbg.cu", self.module._debug_compile_unit) # type: ignore
def get_basic_type(self, name: str, size: int, encoding: int) -> Any: def get_basic_type(self, name: str, size: int, encoding: int) -> Any:
"""Get or create a basic type with caching""" """Get or create a basic type with caching"""
key = (name, size, encoding) key = (name, size, encoding)
@ -21,6 +49,10 @@ class DebugInfoGenerator:
) )
return self._type_cache[key] return self._type_cache[key]
def get_int32_type(self) -> Any:
"""Get debug info for signed 32-bit integer"""
return self.get_basic_type("int", 32, dc.DW_ATE_signed)
def get_uint32_type(self) -> Any: def get_uint32_type(self) -> Any:
"""Get debug info for unsigned 32-bit integer""" """Get debug info for unsigned 32-bit integer"""
return self.get_basic_type("unsigned int", 32, dc.DW_ATE_unsigned) return self.get_basic_type("unsigned int", 32, dc.DW_ATE_unsigned)

View File

@ -0,0 +1,4 @@
from .expr_pass import eval_expr, handle_expr
from .type_normalization import convert_to_bool
__all__ = ["eval_expr", "handle_expr", "convert_to_bool"]

445
pythonbpf/expr/expr_pass.py Normal file
View File

@ -0,0 +1,445 @@
import ast
from llvmlite import ir
from logging import Logger
import logging
from typing import Dict
from pythonbpf.type_deducer import ctypes_to_ir, is_ctypes
from .type_normalization import convert_to_bool, handle_comparator
logger: Logger = logging.getLogger(__name__)
def _handle_name_expr(expr: ast.Name, local_sym_tab: Dict, builder: ir.IRBuilder):
"""Handle ast.Name expressions."""
if expr.id in local_sym_tab:
var = local_sym_tab[expr.id].var
val = builder.load(var)
return val, local_sym_tab[expr.id].ir_type
else:
logger.info(f"Undefined variable {expr.id}")
return None
def _handle_constant_expr(expr: ast.Constant):
"""Handle ast.Constant expressions."""
if isinstance(expr.value, int) or isinstance(expr.value, bool):
return ir.Constant(ir.IntType(64), int(expr.value)), ir.IntType(64)
else:
logger.error("Unsupported constant type")
return None
def _handle_attribute_expr(
expr: ast.Attribute,
local_sym_tab: Dict,
structs_sym_tab: Dict,
builder: ir.IRBuilder,
):
"""Handle ast.Attribute expressions for struct field access."""
if isinstance(expr.value, ast.Name):
var_name = expr.value.id
attr_name = expr.attr
if var_name in local_sym_tab:
var_ptr, var_type, var_metadata = local_sym_tab[var_name]
logger.info(f"Loading attribute {attr_name} from variable {var_name}")
logger.info(f"Variable type: {var_type}, Variable ptr: {var_ptr}")
metadata = structs_sym_tab[var_metadata]
if attr_name in metadata.fields:
gep = metadata.gep(builder, var_ptr, attr_name)
val = builder.load(gep)
field_type = metadata.field_type(attr_name)
return val, field_type
return None
def _handle_deref_call(expr: ast.Call, local_sym_tab: Dict, builder: ir.IRBuilder):
"""Handle deref function calls."""
logger.info(f"Handling deref {ast.dump(expr)}")
if len(expr.args) != 1:
logger.info("deref takes exactly one argument")
return None
arg = expr.args[0]
if (
isinstance(arg, ast.Call)
and isinstance(arg.func, ast.Name)
and arg.func.id == "deref"
):
logger.info("Multiple deref not supported")
return None
if isinstance(arg, ast.Name):
if arg.id in local_sym_tab:
arg_ptr = local_sym_tab[arg.id].var
else:
logger.info(f"Undefined variable {arg.id}")
return None
else:
logger.info("Unsupported argument type for deref")
return None
if arg_ptr is None:
logger.info("Failed to evaluate deref argument")
return None
# Load the value from pointer
val = builder.load(arg_ptr)
return val, local_sym_tab[arg.id].ir_type
def _handle_ctypes_call(
func,
module,
builder,
expr,
local_sym_tab,
map_sym_tab,
structs_sym_tab=None,
):
"""Handle ctypes type constructor calls."""
if len(expr.args) != 1:
logger.info("ctypes constructor takes exactly one argument")
return None
arg = expr.args[0]
val = eval_expr(
func,
module,
builder,
arg,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
)
if val is None:
logger.info("Failed to evaluate argument to ctypes constructor")
return None
call_type = expr.func.id
expected_type = ctypes_to_ir(call_type)
if val[1] != expected_type:
# NOTE: We are only considering casting to and from int types for now
if isinstance(val[1], ir.IntType) and isinstance(expected_type, ir.IntType):
if val[1].width < expected_type.width:
val = (builder.sext(val[0], expected_type), expected_type)
else:
val = (builder.trunc(val[0], expected_type), expected_type)
else:
raise ValueError(f"Type mismatch: expected {expected_type}, got {val[1]}")
return val
def _handle_compare(
func, module, builder, cond, local_sym_tab, map_sym_tab, structs_sym_tab=None
):
"""Handle ast.Compare expressions."""
if len(cond.ops) != 1 or len(cond.comparators) != 1:
logger.error("Only single comparisons are supported")
return None
lhs = eval_expr(
func,
module,
builder,
cond.left,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
)
rhs = eval_expr(
func,
module,
builder,
cond.comparators[0],
local_sym_tab,
map_sym_tab,
structs_sym_tab,
)
if lhs is None or rhs is None:
logger.error("Failed to evaluate comparison operands")
return None
lhs, _ = lhs
rhs, _ = rhs
return handle_comparator(func, builder, cond.ops[0], lhs, rhs)
def _handle_unary_op(
func,
module,
builder,
expr: ast.UnaryOp,
local_sym_tab,
map_sym_tab,
structs_sym_tab=None,
):
"""Handle ast.UnaryOp expressions."""
if not isinstance(expr.op, ast.Not):
logger.error("Only 'not' unary operator is supported")
return None
operand = eval_expr(
func, module, builder, expr.operand, local_sym_tab, map_sym_tab, structs_sym_tab
)
if operand is None:
logger.error("Failed to evaluate operand for unary operation")
return None
operand_val, operand_type = operand
true_const = ir.Constant(ir.IntType(1), 1)
result = builder.xor(convert_to_bool(builder, operand_val), true_const)
return result, ir.IntType(1)
def _handle_and_op(func, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab):
"""Handle `and` boolean operations."""
logger.debug(f"Handling 'and' operator with {len(expr.values)} operands")
merge_block = func.append_basic_block(name="and.merge")
false_block = func.append_basic_block(name="and.false")
incoming_values = []
for i, value in enumerate(expr.values):
is_last = i == len(expr.values) - 1
# Evaluate current operand
operand_result = eval_expr(
func, None, builder, value, local_sym_tab, map_sym_tab, structs_sym_tab
)
if operand_result is None:
logger.error(f"Failed to evaluate operand {i} in 'and' expression")
return None
operand_val, operand_type = operand_result
# Convert to boolean if needed
operand_bool = convert_to_bool(builder, operand_val)
current_block = builder.block
if is_last:
# Last operand: result is this value
builder.branch(merge_block)
incoming_values.append((operand_bool, current_block))
else:
# Not last: check if true, continue or short-circuit
next_check = func.append_basic_block(name=f"and.check_{i + 1}")
builder.cbranch(operand_bool, next_check, false_block)
builder.position_at_end(next_check)
# False block: short-circuit with false
builder.position_at_end(false_block)
builder.branch(merge_block)
false_value = ir.Constant(ir.IntType(1), 0)
incoming_values.append((false_value, false_block))
# Merge block: phi node
builder.position_at_end(merge_block)
phi = builder.phi(ir.IntType(1), name="and.result")
for val, block in incoming_values:
phi.add_incoming(val, block)
logger.debug(f"Generated 'and' with {len(incoming_values)} incoming values")
return phi, ir.IntType(1)
def _handle_or_op(func, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab):
"""Handle `or` boolean operations."""
logger.debug(f"Handling 'or' operator with {len(expr.values)} operands")
merge_block = func.append_basic_block(name="or.merge")
true_block = func.append_basic_block(name="or.true")
incoming_values = []
for i, value in enumerate(expr.values):
is_last = i == len(expr.values) - 1
# Evaluate current operand
operand_result = eval_expr(
func, None, builder, value, local_sym_tab, map_sym_tab, structs_sym_tab
)
if operand_result is None:
logger.error(f"Failed to evaluate operand {i} in 'or' expression")
return None
operand_val, operand_type = operand_result
# Convert to boolean if needed
operand_bool = convert_to_bool(builder, operand_val)
current_block = builder.block
if is_last:
# Last operand: result is this value
builder.branch(merge_block)
incoming_values.append((operand_bool, current_block))
else:
# Not last: check if false, continue or short-circuit
next_check = func.append_basic_block(name=f"or.check_{i + 1}")
builder.cbranch(operand_bool, true_block, next_check)
builder.position_at_end(next_check)
# True block: short-circuit with true
builder.position_at_end(true_block)
builder.branch(merge_block)
true_value = ir.Constant(ir.IntType(1), 1)
incoming_values.append((true_value, true_block))
# Merge block: phi node
builder.position_at_end(merge_block)
phi = builder.phi(ir.IntType(1), name="or.result")
for val, block in incoming_values:
phi.add_incoming(val, block)
logger.debug(f"Generated 'or' with {len(incoming_values)} incoming values")
return phi, ir.IntType(1)
def _handle_boolean_op(
func,
module,
builder,
expr: ast.BoolOp,
local_sym_tab,
map_sym_tab,
structs_sym_tab=None,
):
"""Handle `and` and `or` boolean operations."""
if isinstance(expr.op, ast.And):
return _handle_and_op(
func, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab
)
elif isinstance(expr.op, ast.Or):
return _handle_or_op(
func, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab
)
else:
logger.error(f"Unsupported boolean operator: {type(expr.op).__name__}")
return None
def eval_expr(
func,
module,
builder,
expr,
local_sym_tab,
map_sym_tab,
structs_sym_tab=None,
):
logger.info(f"Evaluating expression: {ast.dump(expr)}")
if isinstance(expr, ast.Name):
return _handle_name_expr(expr, local_sym_tab, builder)
elif isinstance(expr, ast.Constant):
return _handle_constant_expr(expr)
elif isinstance(expr, ast.Call):
if isinstance(expr.func, ast.Name) and expr.func.id == "deref":
return _handle_deref_call(expr, local_sym_tab, builder)
if isinstance(expr.func, ast.Name) and is_ctypes(expr.func.id):
return _handle_ctypes_call(
func,
module,
builder,
expr,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
)
# delayed import to avoid circular dependency
from pythonbpf.helper import HelperHandlerRegistry, handle_helper_call
if isinstance(expr.func, ast.Name) and HelperHandlerRegistry.has_handler(
expr.func.id
):
return handle_helper_call(
expr,
module,
builder,
func,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
)
elif isinstance(expr.func, ast.Attribute):
logger.info(f"Handling method call: {ast.dump(expr.func)}")
if isinstance(expr.func.value, ast.Call) and isinstance(
expr.func.value.func, ast.Name
):
method_name = expr.func.attr
if HelperHandlerRegistry.has_handler(method_name):
return handle_helper_call(
expr,
module,
builder,
func,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
)
elif isinstance(expr.func.value, ast.Name):
obj_name = expr.func.value.id
method_name = expr.func.attr
if obj_name in map_sym_tab:
if HelperHandlerRegistry.has_handler(method_name):
return handle_helper_call(
expr,
module,
builder,
func,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
)
elif isinstance(expr, ast.Attribute):
return _handle_attribute_expr(expr, local_sym_tab, structs_sym_tab, builder)
elif isinstance(expr, ast.BinOp):
from pythonbpf.binary_ops import handle_binary_op
return handle_binary_op(expr, builder, None, local_sym_tab)
elif isinstance(expr, ast.Compare):
return _handle_compare(
func, module, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab
)
elif isinstance(expr, ast.UnaryOp):
return _handle_unary_op(
func, module, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab
)
elif isinstance(expr, ast.BoolOp):
return _handle_boolean_op(
func, module, builder, expr, local_sym_tab, map_sym_tab, structs_sym_tab
)
logger.info("Unsupported expression evaluation")
return None
def handle_expr(
func,
module,
builder,
expr,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
):
"""Handle expression statements in the function body."""
logger.info(f"Handling expression: {ast.dump(expr)}")
call = expr.value
if isinstance(call, ast.Call):
eval_expr(
func,
module,
builder,
call,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
)
else:
logger.info("Unsupported expression type")

View File

@ -0,0 +1,128 @@
from llvmlite import ir
import logging
import ast
logger = logging.getLogger(__name__)
COMPARISON_OPS = {
ast.Eq: "==",
ast.NotEq: "!=",
ast.Lt: "<",
ast.LtE: "<=",
ast.Gt: ">",
ast.GtE: ">=",
ast.Is: "==",
ast.IsNot: "!=",
}
def _get_base_type_and_depth(ir_type):
"""Get the base type for pointer types."""
cur_type = ir_type
depth = 0
while isinstance(cur_type, ir.PointerType):
depth += 1
cur_type = cur_type.pointee
return cur_type, depth
def _deref_to_depth(func, builder, val, target_depth):
"""Dereference a pointer to a certain depth."""
cur_val = val
cur_type = val.type
for depth in range(target_depth):
if not isinstance(val.type, ir.PointerType):
logger.error("Cannot dereference further, non-pointer type")
return None
# dereference with null check
pointee_type = cur_type.pointee
null_check_block = builder.block
not_null_block = func.append_basic_block(name=f"deref_not_null_{depth}")
merge_block = func.append_basic_block(name=f"deref_merge_{depth}")
null_ptr = ir.Constant(cur_type, None)
is_not_null = builder.icmp_signed("!=", cur_val, null_ptr)
logger.debug(f"Inserted null check for pointer at depth {depth}")
builder.cbranch(is_not_null, not_null_block, merge_block)
builder.position_at_end(not_null_block)
dereferenced_val = builder.load(cur_val)
logger.debug(f"Dereferenced to depth {depth - 1}, type: {pointee_type}")
builder.branch(merge_block)
builder.position_at_end(merge_block)
phi = builder.phi(pointee_type, name=f"deref_result_{depth}")
zero_value = (
ir.Constant(pointee_type, 0)
if isinstance(pointee_type, ir.IntType)
else ir.Constant(pointee_type, None)
)
phi.add_incoming(zero_value, null_check_block)
phi.add_incoming(dereferenced_val, not_null_block)
# Continue with phi result
cur_val = phi
cur_type = pointee_type
return cur_val
def _normalize_types(func, builder, lhs, rhs):
"""Normalize types for comparison."""
logger.info(f"Normalizing types: {lhs.type} vs {rhs.type}")
if isinstance(lhs.type, ir.IntType) and isinstance(rhs.type, ir.IntType):
if lhs.type.width < rhs.type.width:
lhs = builder.sext(lhs, rhs.type)
else:
rhs = builder.sext(rhs, lhs.type)
return lhs, rhs
elif not isinstance(lhs.type, ir.PointerType) and not isinstance(
rhs.type, ir.PointerType
):
logger.error(f"Type mismatch: {lhs.type} vs {rhs.type}")
return None, None
else:
lhs_base, lhs_depth = _get_base_type_and_depth(lhs.type)
rhs_base, rhs_depth = _get_base_type_and_depth(rhs.type)
if lhs_base == rhs_base:
if lhs_depth < rhs_depth:
rhs = _deref_to_depth(func, builder, rhs, rhs_depth - lhs_depth)
elif rhs_depth < lhs_depth:
lhs = _deref_to_depth(func, builder, lhs, lhs_depth - rhs_depth)
return _normalize_types(func, builder, lhs, rhs)
def convert_to_bool(builder, val):
"""Convert a value to boolean."""
if val.type == ir.IntType(1):
return val
if isinstance(val.type, ir.PointerType):
zero = ir.Constant(val.type, None)
else:
zero = ir.Constant(val.type, 0)
return builder.icmp_signed("!=", val, zero)
def handle_comparator(func, builder, op, lhs, rhs):
"""Handle comparison operations."""
if lhs.type != rhs.type:
lhs, rhs = _normalize_types(func, builder, lhs, rhs)
if lhs is None or rhs is None:
return None
if type(op) not in COMPARISON_OPS:
logger.error(f"Unsupported comparison operator: {type(op)}")
return None
predicate = COMPARISON_OPS[type(op)]
result = builder.icmp_signed(predicate, lhs, rhs)
logger.debug(f"Comparison result: {result}")
return result, ir.IntType(1)

View File

@ -1,155 +0,0 @@
import ast
from llvmlite import ir
def eval_expr(
func,
module,
builder,
expr,
local_sym_tab,
map_sym_tab,
structs_sym_tab=None,
local_var_metadata=None,
):
print(f"Evaluating expression: {ast.dump(expr)}")
print(local_var_metadata)
if isinstance(expr, ast.Name):
if expr.id in local_sym_tab:
var = local_sym_tab[expr.id][0]
val = builder.load(var)
return val, local_sym_tab[expr.id][1] # return value and type
else:
print(f"Undefined variable {expr.id}")
return None
elif isinstance(expr, ast.Constant):
if isinstance(expr.value, int):
return ir.Constant(ir.IntType(64), expr.value), ir.IntType(64)
elif isinstance(expr.value, bool):
return ir.Constant(ir.IntType(1), int(expr.value)), ir.IntType(1)
else:
print("Unsupported constant type")
return None
elif isinstance(expr, ast.Call):
# delayed import to avoid circular dependency
from .bpf_helper_handler import helper_func_list, handle_helper_call
if isinstance(expr.func, ast.Name):
# check deref
if expr.func.id == "deref":
print(f"Handling deref {ast.dump(expr)}")
if len(expr.args) != 1:
print("deref takes exactly one argument")
return None
arg = expr.args[0]
if (
isinstance(arg, ast.Call)
and isinstance(arg.func, ast.Name)
and arg.func.id == "deref"
):
print("Multiple deref not supported")
return None
if isinstance(arg, ast.Name):
if arg.id in local_sym_tab:
arg = local_sym_tab[arg.id][0]
else:
print(f"Undefined variable {arg.id}")
return None
if arg is None:
print("Failed to evaluate deref argument")
return None
# Since we are handling only name case, directly take type from sym tab
val = builder.load(arg)
return val, local_sym_tab[expr.args[0].id][1]
# check for helpers
if expr.func.id in helper_func_list:
return handle_helper_call(
expr,
module,
builder,
func,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
local_var_metadata,
)
elif isinstance(expr.func, ast.Attribute):
print(f"Handling method call: {ast.dump(expr.func)}")
if isinstance(expr.func.value, ast.Call) and isinstance(
expr.func.value.func, ast.Name
):
method_name = expr.func.attr
if method_name in helper_func_list:
return handle_helper_call(
expr,
module,
builder,
func,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
local_var_metadata,
)
elif isinstance(expr.func.value, ast.Name):
obj_name = expr.func.value.id
method_name = expr.func.attr
if obj_name in map_sym_tab:
if method_name in helper_func_list:
return handle_helper_call(
expr,
module,
builder,
func,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
local_var_metadata,
)
elif isinstance(expr, ast.Attribute):
if isinstance(expr.value, ast.Name):
var_name = expr.value.id
attr_name = expr.attr
if var_name in local_sym_tab:
var_ptr, var_type = local_sym_tab[var_name]
print(f"Loading attribute " f"{attr_name} from variable {var_name}")
print(f"Variable type: {var_type}, Variable ptr: {var_ptr}")
print(local_var_metadata)
if local_var_metadata and var_name in local_var_metadata:
metadata = structs_sym_tab[local_var_metadata[var_name]]
if attr_name in metadata.fields:
gep = metadata.gep(builder, var_ptr, attr_name)
val = builder.load(gep)
field_type = metadata.field_type(attr_name)
return val, field_type
print("Unsupported expression evaluation")
return None
def handle_expr(
func,
module,
builder,
expr,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
local_var_metadata,
):
"""Handle expression statements in the function body."""
print(f"Handling expression: {ast.dump(expr)}")
print(local_var_metadata)
call = expr.value
if isinstance(call, ast.Call):
eval_expr(
func,
module,
builder,
call,
local_sym_tab,
map_sym_tab,
structs_sym_tab,
local_var_metadata,
)
else:
print("Unsupported expression type")

View File

@ -0,0 +1,3 @@
from .functions_pass import func_proc
__all__ = ["func_proc"]

View File

@ -0,0 +1,22 @@
from typing import Dict
class StatementHandlerRegistry:
"""Registry for statement handlers."""
_handlers: Dict = {}
@classmethod
def register(cls, stmt_type):
"""Register a handler for a specific statement type."""
def decorator(handler):
cls._handlers[stmt_type] = handler
return handler
return decorator
@classmethod
def __getitem__(cls, stmt_type):
"""Get the handler for a specific statement type."""
return cls._handlers.get(stmt_type, None)

View File

@ -1,13 +1,30 @@
from llvmlite import ir from llvmlite import ir
import ast import ast
import logging
from typing import Any
from dataclasses import dataclass
from pythonbpf.helper import HelperHandlerRegistry, handle_helper_call
from pythonbpf.type_deducer import ctypes_to_ir
from pythonbpf.binary_ops import handle_binary_op
from pythonbpf.expr import eval_expr, handle_expr, convert_to_bool
from .return_utils import _handle_none_return, _handle_xdp_return, _is_xdp_name
from .bpf_helper_handler import helper_func_list, handle_helper_call logger = logging.getLogger(__name__)
from .type_deducer import ctypes_to_ir
from .binary_ops import handle_binary_op
from .expr_pass import eval_expr, handle_expr
local_var_metadata = {}
@dataclass
class LocalSymbol:
var: ir.AllocaInstr
ir_type: ir.Type
metadata: Any = None
def __iter__(self):
yield self.var
yield self.ir_type
yield self.metadata
def get_probe_string(func_node): def get_probe_string(func_node):
@ -32,28 +49,27 @@ def handle_assign(
): ):
"""Handle assignment statements in the function body.""" """Handle assignment statements in the function body."""
if len(stmt.targets) != 1: if len(stmt.targets) != 1:
print("Unsupported multiassignment") logger.info("Unsupported multiassignment")
return return
num_types = ("c_int32", "c_int64", "c_uint32", "c_uint64") num_types = ("c_int32", "c_int64", "c_uint32", "c_uint64")
target = stmt.targets[0] target = stmt.targets[0]
print(f"Handling assignment to {ast.dump(target)}") logger.info(f"Handling assignment to {ast.dump(target)}")
if not isinstance(target, ast.Name) and not isinstance(target, ast.Attribute): if not isinstance(target, ast.Name) and not isinstance(target, ast.Attribute):
print("Unsupported assignment target") logger.info("Unsupported assignment target")
return return
var_name = target.id if isinstance(target, ast.Name) else target.value.id var_name = target.id if isinstance(target, ast.Name) else target.value.id
rval = stmt.value rval = stmt.value
if isinstance(target, ast.Attribute): if isinstance(target, ast.Attribute):
# struct field assignment # struct field assignment
field_name = target.attr field_name = target.attr
if var_name in local_sym_tab and var_name in local_var_metadata: if var_name in local_sym_tab:
struct_type = local_var_metadata[var_name] struct_type = local_sym_tab[var_name].metadata
struct_info = structs_sym_tab[struct_type] struct_info = structs_sym_tab[struct_type]
if field_name in struct_info.fields: if field_name in struct_info.fields:
field_ptr = struct_info.gep( field_ptr = struct_info.gep(
builder, local_sym_tab[var_name][0], field_name builder, local_sym_tab[var_name].var, field_name
) )
val = eval_expr( val = eval_expr(
func, func,
@ -74,28 +90,31 @@ def handle_assign(
# print(f"Assigned to struct field {var_name}.{field_name}") # print(f"Assigned to struct field {var_name}.{field_name}")
pass pass
if val is None: if val is None:
print("Failed to evaluate struct field assignment") logger.info("Failed to evaluate struct field assignment")
return return
print(field_ptr) logger.info(field_ptr)
builder.store(val[0], field_ptr) builder.store(val[0], field_ptr)
print(f"Assigned to struct field {var_name}.{field_name}") logger.info(f"Assigned to struct field {var_name}.{field_name}")
return return
elif isinstance(rval, ast.Constant): elif isinstance(rval, ast.Constant):
if isinstance(rval.value, bool): if isinstance(rval.value, bool):
if rval.value: if rval.value:
builder.store(ir.Constant(ir.IntType(1), 1), local_sym_tab[var_name][0]) builder.store(
ir.Constant(ir.IntType(1), 1), local_sym_tab[var_name].var
)
else: else:
builder.store(ir.Constant(ir.IntType(1), 0), local_sym_tab[var_name][0]) builder.store(
print(f"Assigned constant {rval.value} to {var_name}") ir.Constant(ir.IntType(1), 0), local_sym_tab[var_name].var
)
logger.info(f"Assigned constant {rval.value} to {var_name}")
elif isinstance(rval.value, int): elif isinstance(rval.value, int):
# Assume c_int64 for now # Assume c_int64 for now
# var = builder.alloca(ir.IntType(64), name=var_name) # var = builder.alloca(ir.IntType(64), name=var_name)
# var.align = 8 # var.align = 8
builder.store( builder.store(
ir.Constant(ir.IntType(64), rval.value), local_sym_tab[var_name][0] ir.Constant(ir.IntType(64), rval.value), local_sym_tab[var_name].var
) )
# local_sym_tab[var_name] = var logger.info(f"Assigned constant {rval.value} to {var_name}")
print(f"Assigned constant {rval.value} to {var_name}")
elif isinstance(rval.value, str): elif isinstance(rval.value, str):
str_val = rval.value.encode("utf-8") + b"\x00" str_val = rval.value.encode("utf-8") + b"\x00"
str_const = ir.Constant( str_const = ir.Constant(
@ -108,14 +127,14 @@ def handle_assign(
global_str.global_constant = True global_str.global_constant = True
global_str.initializer = str_const global_str.initializer = str_const
str_ptr = builder.bitcast(global_str, ir.PointerType(ir.IntType(8))) str_ptr = builder.bitcast(global_str, ir.PointerType(ir.IntType(8)))
builder.store(str_ptr, local_sym_tab[var_name][0]) builder.store(str_ptr, local_sym_tab[var_name].var)
print(f"Assigned string constant '{rval.value}' to {var_name}") logger.info(f"Assigned string constant '{rval.value}' to {var_name}")
else: else:
print("Unsupported constant type") logger.info("Unsupported constant type")
elif isinstance(rval, ast.Call): elif isinstance(rval, ast.Call):
if isinstance(rval.func, ast.Name): if isinstance(rval.func, ast.Name):
call_type = rval.func.id call_type = rval.func.id
print(f"Assignment call type: {call_type}") logger.info(f"Assignment call type: {call_type}")
if ( if (
call_type in num_types call_type in num_types
and len(rval.args) == 1 and len(rval.args) == 1
@ -126,14 +145,13 @@ def handle_assign(
# var = builder.alloca(ir_type, name=var_name) # var = builder.alloca(ir_type, name=var_name)
# var.align = ir_type.width // 8 # var.align = ir_type.width // 8
builder.store( builder.store(
ir.Constant(ir_type, rval.args[0].value), local_sym_tab[var_name][0] ir.Constant(ir_type, rval.args[0].value),
local_sym_tab[var_name].var,
) )
print( logger.info(
f"Assigned {call_type} constant " f"Assigned {call_type} constant {rval.args[0].value} to {var_name}"
f"{rval.args[0].value} to {var_name}"
) )
# local_sym_tab[var_name] = var elif HelperHandlerRegistry.has_handler(call_type):
elif call_type in helper_func_list:
# var = builder.alloca(ir.IntType(64), name=var_name) # var = builder.alloca(ir.IntType(64), name=var_name)
# var.align = 8 # var.align = 8
val = handle_helper_call( val = handle_helper_call(
@ -144,13 +162,11 @@ def handle_assign(
local_sym_tab, local_sym_tab,
map_sym_tab, map_sym_tab,
structs_sym_tab, structs_sym_tab,
local_var_metadata,
) )
builder.store(val[0], local_sym_tab[var_name][0]) builder.store(val[0], local_sym_tab[var_name].var)
# local_sym_tab[var_name] = var logger.info(f"Assigned constant {rval.func.id} to {var_name}")
print(f"Assigned constant {rval.func.id} to {var_name}")
elif call_type == "deref" and len(rval.args) == 1: elif call_type == "deref" and len(rval.args) == 1:
print(f"Handling deref assignment {ast.dump(rval)}") logger.info(f"Handling deref assignment {ast.dump(rval)}")
val = eval_expr( val = eval_expr(
func, func,
module, module,
@ -161,36 +177,47 @@ def handle_assign(
structs_sym_tab, structs_sym_tab,
) )
if val is None: if val is None:
print("Failed to evaluate deref argument") logger.info("Failed to evaluate deref argument")
return return
print(f"Dereferenced value: {val}, storing in {var_name}") logger.info(f"Dereferenced value: {val}, storing in {var_name}")
builder.store(val[0], local_sym_tab[var_name][0]) builder.store(val[0], local_sym_tab[var_name].var)
# local_sym_tab[var_name] = var logger.info(f"Dereferenced and assigned to {var_name}")
print(f"Dereferenced and assigned to {var_name}")
elif call_type in structs_sym_tab and len(rval.args) == 0: elif call_type in structs_sym_tab and len(rval.args) == 0:
struct_info = structs_sym_tab[call_type] struct_info = structs_sym_tab[call_type]
ir_type = struct_info.ir_type ir_type = struct_info.ir_type
# var = builder.alloca(ir_type, name=var_name) # var = builder.alloca(ir_type, name=var_name)
# Null init # Null init
builder.store(ir.Constant(ir_type, None), local_sym_tab[var_name][0]) builder.store(ir.Constant(ir_type, None), local_sym_tab[var_name].var)
local_var_metadata[var_name] = call_type logger.info(f"Assigned struct {call_type} to {var_name}")
print(f"Assigned struct {call_type} to {var_name}")
# local_sym_tab[var_name] = var
else: else:
print(f"Unsupported assignment call type: {call_type}") logger.info(f"Unsupported assignment call type: {call_type}")
elif isinstance(rval.func, ast.Attribute): elif isinstance(rval.func, ast.Attribute):
print(f"Assignment call attribute: {ast.dump(rval.func)}") logger.info(f"Assignment call attribute: {ast.dump(rval.func)}")
if isinstance(rval.func.value, ast.Name): if isinstance(rval.func.value, ast.Name):
# TODO: probably a struct access if rval.func.value.id in map_sym_tab:
print(f"TODO STRUCT ACCESS {ast.dump(rval)}") map_name = rval.func.value.id
elif isinstance(rval.func.value, ast.Call) and isinstance( method_name = rval.func.attr
rval.func.value.func, ast.Name if HelperHandlerRegistry.has_handler(method_name):
): val = handle_helper_call(
map_name = rval.func.value.func.id rval,
method_name = rval.func.attr module,
if map_name in map_sym_tab: builder,
# map_ptr = map_sym_tab[map_name] func,
if method_name in helper_func_list: local_sym_tab,
map_sym_tab,
structs_sym_tab,
)
builder.store(val[0], local_sym_tab[var_name].var)
else:
# TODO: probably a struct access
logger.info(f"TODO STRUCT ACCESS {ast.dump(rval)}")
elif isinstance(rval.func.value, ast.Call) and isinstance(
rval.func.value.func, ast.Name
):
map_name = rval.func.value.func.id
method_name = rval.func.attr
if map_name in map_sym_tab:
if HelperHandlerRegistry.has_handler(method_name):
val = handle_helper_call( val = handle_helper_call(
rval, rval,
module, module,
@ -199,96 +226,34 @@ def handle_assign(
local_sym_tab, local_sym_tab,
map_sym_tab, map_sym_tab,
structs_sym_tab, structs_sym_tab,
local_var_metadata,
) )
# var = builder.alloca(ir.IntType(64), name=var_name) # var = builder.alloca(ir.IntType(64), name=var_name)
# var.align = 8 # var.align = 8
builder.store(val[0], local_sym_tab[var_name][0]) builder.store(val[0], local_sym_tab[var_name].var)
# local_sym_tab[var_name] = var
else: else:
print("Unsupported assignment call structure") logger.info("Unsupported assignment call structure")
else: else:
print("Unsupported assignment call function type") logger.info("Unsupported assignment call function type")
elif isinstance(rval, ast.BinOp): elif isinstance(rval, ast.BinOp):
handle_binary_op( handle_binary_op(rval, builder, var_name, local_sym_tab)
rval, module, builder, var_name, local_sym_tab, map_sym_tab, func
)
else: else:
print("Unsupported assignment value type") logger.info("Unsupported assignment value type")
def handle_cond(func, module, builder, cond, local_sym_tab, map_sym_tab): def handle_cond(
if isinstance(cond, ast.Constant): func, module, builder, cond, local_sym_tab, map_sym_tab, structs_sym_tab=None
if isinstance(cond.value, bool): ):
return ir.Constant(ir.IntType(1), int(cond.value)) val = eval_expr(
elif isinstance(cond.value, int): func, module, builder, cond, local_sym_tab, map_sym_tab, structs_sym_tab
return ir.Constant(ir.IntType(1), int(bool(cond.value))) )[0]
else: return convert_to_bool(builder, val)
print("Unsupported constant type in condition")
return None
elif isinstance(cond, ast.Name):
if cond.id in local_sym_tab:
var = local_sym_tab[cond.id][0]
val = builder.load(var)
if val.type != ir.IntType(1):
# Convert nonzero values to true, zero to false
if isinstance(val.type, ir.PointerType):
# For pointer types, compare with null pointer
zero = ir.Constant(val.type, None)
else:
# For integer types, compare with zero
zero = ir.Constant(val.type, 0)
val = builder.icmp_signed("!=", val, zero)
return val
else:
print(f"Undefined variable {cond.id} in condition")
return None
elif isinstance(cond, ast.Compare):
lhs = eval_expr(func, module, builder, cond.left, local_sym_tab, map_sym_tab)[0]
if len(cond.ops) != 1 or len(cond.comparators) != 1:
print("Unsupported complex comparison")
return None
rhs = eval_expr(
func, module, builder, cond.comparators[0], local_sym_tab, map_sym_tab
)[0]
op = cond.ops[0]
if lhs.type != rhs.type:
if isinstance(lhs.type, ir.IntType) and isinstance(rhs.type, ir.IntType):
# Extend the smaller type to the larger type
if lhs.type.width < rhs.type.width:
lhs = builder.sext(lhs, rhs.type)
elif lhs.type.width > rhs.type.width:
rhs = builder.sext(rhs, lhs.type)
else:
print("Type mismatch in comparison")
return None
if isinstance(op, ast.Eq):
return builder.icmp_signed("==", lhs, rhs)
elif isinstance(op, ast.NotEq):
return builder.icmp_signed("!=", lhs, rhs)
elif isinstance(op, ast.Lt):
return builder.icmp_signed("<", lhs, rhs)
elif isinstance(op, ast.LtE):
return builder.icmp_signed("<=", lhs, rhs)
elif isinstance(op, ast.Gt):
return builder.icmp_signed(">", lhs, rhs)
elif isinstance(op, ast.GtE):
return builder.icmp_signed(">=", lhs, rhs)
else:
print("Unsupported comparison operator")
return None
else:
print("Unsupported condition expression")
return None
def handle_if( def handle_if(
func, module, builder, stmt, map_sym_tab, local_sym_tab, structs_sym_tab=None func, module, builder, stmt, map_sym_tab, local_sym_tab, structs_sym_tab=None
): ):
"""Handle if statements in the function body.""" """Handle if statements in the function body."""
print("Handling if statement") logger.info("Handling if statement")
# start = builder.block.parent # start = builder.block.parent
then_block = func.append_basic_block(name="if.then") then_block = func.append_basic_block(name="if.then")
merge_block = func.append_basic_block(name="if.end") merge_block = func.append_basic_block(name="if.end")
@ -297,7 +262,9 @@ def handle_if(
else: else:
else_block = None else_block = None
cond = handle_cond(func, module, builder, stmt.test, local_sym_tab, map_sym_tab) cond = handle_cond(
func, module, builder, stmt.test, local_sym_tab, map_sym_tab, structs_sym_tab
)
if else_block: if else_block:
builder.cbranch(cond, then_block, else_block) builder.cbranch(cond, then_block, else_block)
else: else:
@ -330,6 +297,27 @@ def handle_if(
builder.position_at_end(merge_block) builder.position_at_end(merge_block)
def handle_return(builder, stmt, local_sym_tab, ret_type):
logger.info(f"Handling return statement: {ast.dump(stmt)}")
if stmt.value is None:
return _handle_none_return(builder)
elif isinstance(stmt.value, ast.Name) and _is_xdp_name(stmt.value.id):
return _handle_xdp_return(stmt, builder, ret_type)
else:
val = eval_expr(
func=None,
module=None,
builder=builder,
expr=stmt.value,
local_sym_tab=local_sym_tab,
map_sym_tab={},
structs_sym_tab={},
)
logger.info(f"Evaluated return expression to {val}")
builder.ret(val[0])
return True
def process_stmt( def process_stmt(
func, func,
module, module,
@ -341,9 +329,8 @@ def process_stmt(
did_return, did_return,
ret_type=ir.IntType(64), ret_type=ir.IntType(64),
): ):
print(f"Processing statement: {ast.dump(stmt)}") logger.info(f"Processing statement: {ast.dump(stmt)}")
if isinstance(stmt, ast.Expr): if isinstance(stmt, ast.Expr):
print(local_var_metadata)
handle_expr( handle_expr(
func, func,
module, module,
@ -352,7 +339,6 @@ def process_stmt(
local_sym_tab, local_sym_tab,
map_sym_tab, map_sym_tab,
structs_sym_tab, structs_sym_tab,
local_var_metadata,
) )
elif isinstance(stmt, ast.Assign): elif isinstance(stmt, ast.Assign):
handle_assign( handle_assign(
@ -365,36 +351,12 @@ def process_stmt(
func, module, builder, stmt, map_sym_tab, local_sym_tab, structs_sym_tab func, module, builder, stmt, map_sym_tab, local_sym_tab, structs_sym_tab
) )
elif isinstance(stmt, ast.Return): elif isinstance(stmt, ast.Return):
if stmt.value is None: did_return = handle_return(
builder.ret(ir.Constant(ir.IntType(32), 0)) builder,
did_return = True stmt,
elif ( local_sym_tab,
isinstance(stmt.value, ast.Call) ret_type,
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)
):
call_type = stmt.value.func.id
if ctypes_to_ir(call_type) != ret_type:
raise ValueError(
"Return type mismatch: expected"
f"{ctypes_to_ir(call_type)}, got {call_type}"
)
else:
builder.ret(ir.Constant(ret_type, stmt.value.args[0].value))
did_return = True
elif isinstance(stmt.value, ast.Name):
if stmt.value.id == "XDP_PASS":
builder.ret(ir.Constant(ret_type, 2))
did_return = True
elif stmt.value.id == "XDP_DROP":
builder.ret(ir.Constant(ret_type, 1))
did_return = True
else:
raise ValueError("Failed to evaluate return expression")
else:
raise ValueError("Unsupported return value")
return did_return return did_return
@ -402,6 +364,7 @@ def allocate_mem(
module, builder, body, func, ret_type, map_sym_tab, local_sym_tab, structs_sym_tab module, builder, body, func, ret_type, map_sym_tab, local_sym_tab, structs_sym_tab
): ):
for stmt in body: for stmt in body:
has_metadata = False
if isinstance(stmt, ast.If): if isinstance(stmt, ast.If):
if stmt.body: if stmt.body:
local_sym_tab = allocate_mem( local_sym_tab = allocate_mem(
@ -427,14 +390,17 @@ def allocate_mem(
) )
elif isinstance(stmt, ast.Assign): elif isinstance(stmt, ast.Assign):
if len(stmt.targets) != 1: if len(stmt.targets) != 1:
print("Unsupported multiassignment") logger.info("Unsupported multiassignment")
continue continue
target = stmt.targets[0] target = stmt.targets[0]
if not isinstance(target, ast.Name): if not isinstance(target, ast.Name):
print("Unsupported assignment target") logger.info("Unsupported assignment target")
continue continue
var_name = target.id var_name = target.id
rval = stmt.value rval = stmt.value
if var_name in local_sym_tab:
logger.info(f"Variable {var_name} already allocated")
continue
if isinstance(rval, ast.Call): if isinstance(rval, ast.Call):
if isinstance(rval.func, ast.Name): if isinstance(rval.func, ast.Name):
call_type = rval.func.id call_type = rval.func.id
@ -442,65 +408,71 @@ def allocate_mem(
ir_type = ctypes_to_ir(call_type) ir_type = ctypes_to_ir(call_type)
var = builder.alloca(ir_type, name=var_name) var = builder.alloca(ir_type, name=var_name)
var.align = ir_type.width // 8 var.align = ir_type.width // 8
print(f"Pre-allocated variable {var_name} of type {call_type}") logger.info(
elif call_type in helper_func_list: f"Pre-allocated variable {var_name} of type {call_type}"
)
elif HelperHandlerRegistry.has_handler(call_type):
# Assume return type is int64 for now # Assume return type is int64 for now
ir_type = ir.IntType(64) ir_type = ir.IntType(64)
var = builder.alloca(ir_type, name=var_name) var = builder.alloca(ir_type, name=var_name)
var.align = ir_type.width // 8 var.align = ir_type.width // 8
print(f"Pre-allocated variable {var_name} for helper") logger.info(f"Pre-allocated variable {var_name} for helper")
elif call_type == "deref" and len(rval.args) == 1: elif call_type == "deref" and len(rval.args) == 1:
# Assume return type is int64 for now # Assume return type is int64 for now
ir_type = ir.IntType(64) ir_type = ir.IntType(64)
var = builder.alloca(ir_type, name=var_name) var = builder.alloca(ir_type, name=var_name)
var.align = ir_type.width // 8 var.align = ir_type.width // 8
print(f"Pre-allocated variable {var_name} for deref") logger.info(f"Pre-allocated variable {var_name} for deref")
elif call_type in structs_sym_tab: elif call_type in structs_sym_tab:
struct_info = structs_sym_tab[call_type] struct_info = structs_sym_tab[call_type]
ir_type = struct_info.ir_type ir_type = struct_info.ir_type
var = builder.alloca(ir_type, name=var_name) var = builder.alloca(ir_type, name=var_name)
local_var_metadata[var_name] = call_type has_metadata = True
print( logger.info(
f"Pre-allocated variable {var_name} for struct {call_type}" f"Pre-allocated variable {var_name} for struct {call_type}"
) )
elif isinstance(rval.func, ast.Attribute): elif isinstance(rval.func, ast.Attribute):
ir_type = ir.PointerType(ir.IntType(64)) ir_type = ir.PointerType(ir.IntType(64))
var = builder.alloca(ir_type, name=var_name) var = builder.alloca(ir_type, name=var_name)
# var.align = ir_type.width // 8 # var.align = ir_type.width // 8
print(f"Pre-allocated variable {var_name} for map") logger.info(f"Pre-allocated variable {var_name} for map")
else: else:
print("Unsupported assignment call function type") logger.info("Unsupported assignment call function type")
continue continue
elif isinstance(rval, ast.Constant): elif isinstance(rval, ast.Constant):
if isinstance(rval.value, bool): if isinstance(rval.value, bool):
ir_type = ir.IntType(1) ir_type = ir.IntType(1)
var = builder.alloca(ir_type, name=var_name) var = builder.alloca(ir_type, name=var_name)
var.align = 1 var.align = 1
print(f"Pre-allocated variable {var_name} of type c_bool") logger.info(f"Pre-allocated variable {var_name} of type c_bool")
elif isinstance(rval.value, int): elif isinstance(rval.value, int):
# Assume c_int64 for now # Assume c_int64 for now
ir_type = ir.IntType(64) ir_type = ir.IntType(64)
var = builder.alloca(ir_type, name=var_name) var = builder.alloca(ir_type, name=var_name)
var.align = ir_type.width // 8 var.align = ir_type.width // 8
print(f"Pre-allocated variable {var_name} of type c_int64") logger.info(f"Pre-allocated variable {var_name} of type c_int64")
elif isinstance(rval.value, str): elif isinstance(rval.value, str):
ir_type = ir.PointerType(ir.IntType(8)) ir_type = ir.PointerType(ir.IntType(8))
var = builder.alloca(ir_type, name=var_name) var = builder.alloca(ir_type, name=var_name)
var.align = 8 var.align = 8
print(f"Pre-allocated variable {var_name} of type string") logger.info(f"Pre-allocated variable {var_name} of type string")
else: else:
print("Unsupported constant type") logger.info("Unsupported constant type")
continue continue
elif isinstance(rval, ast.BinOp): elif isinstance(rval, ast.BinOp):
# Assume c_int64 for now # Assume c_int64 for now
ir_type = ir.IntType(64) ir_type = ir.IntType(64)
var = builder.alloca(ir_type, name=var_name) var = builder.alloca(ir_type, name=var_name)
var.align = ir_type.width // 8 var.align = ir_type.width // 8
print(f"Pre-allocated variable {var_name} of type c_int64") logger.info(f"Pre-allocated variable {var_name} of type c_int64")
else: else:
print("Unsupported assignment value type") logger.info("Unsupported assignment value type")
continue continue
local_sym_tab[var_name] = (var, ir_type)
if has_metadata:
local_sym_tab[var_name] = LocalSymbol(var, ir_type, call_type)
else:
local_sym_tab[var_name] = LocalSymbol(var, ir_type)
return local_sym_tab return local_sym_tab
@ -525,7 +497,7 @@ def process_func_body(
structs_sym_tab, structs_sym_tab,
) )
print(f"Local symbol table: {local_sym_tab.keys()}") logger.info(f"Local symbol table: {local_sym_tab.keys()}")
for stmt in func_node.body: for stmt in func_node.body:
did_return = process_stmt( did_return = process_stmt(
@ -541,7 +513,7 @@ def process_func_body(
) )
if not did_return: if not did_return:
builder.ret(ir.Constant(ir.IntType(32), 0)) builder.ret(ir.Constant(ir.IntType(64), 0))
def process_bpf_chunk(func_node, module, return_type, map_sym_tab, structs_sym_tab): def process_bpf_chunk(func_node, module, return_type, map_sym_tab, structs_sym_tab):
@ -597,7 +569,7 @@ def func_proc(tree, module, chunks, map_sym_tab, structs_sym_tab):
if is_global: if is_global:
continue continue
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}") logger.info(f"Found probe_string of {func_node.name}: {func_type}")
process_bpf_chunk( process_bpf_chunk(
func_node, func_node,
@ -656,13 +628,13 @@ def infer_return_type(func_node: ast.FunctionDef):
except Exception: except Exception:
return type(e).__name__ return type(e).__name__
for node in ast.walk(func_node): for walked_node in ast.walk(func_node):
if isinstance(node, ast.Return): if isinstance(walked_node, ast.Return):
t = _expr_type(node.value) t = _expr_type(walked_node.value)
if found_type is None: if found_type is None:
found_type = t found_type = t
elif found_type != t: elif found_type != t:
raise ValueError("Conflicting return types:" f"{found_type} vs {t}") raise ValueError(f"Conflicting return types: {found_type} vs {t}")
return found_type or "None" return found_type or "None"

View File

@ -0,0 +1,45 @@
import logging
import ast
from llvmlite import ir
logger: logging.Logger = logging.getLogger(__name__)
XDP_ACTIONS = {
"XDP_ABORTED": 0,
"XDP_DROP": 1,
"XDP_PASS": 2,
"XDP_TX": 3,
"XDP_REDIRECT": 4,
}
def _handle_none_return(builder) -> bool:
"""Handle return or return None -> returns 0."""
builder.ret(ir.Constant(ir.IntType(64), 0))
logger.debug("Generated default return: 0")
return True
def _is_xdp_name(name: str) -> bool:
"""Check if a name is an XDP action"""
return name in XDP_ACTIONS
def _handle_xdp_return(stmt: ast.Return, builder, ret_type) -> bool:
"""Handle XDP returns"""
if not isinstance(stmt.value, ast.Name):
return False
action_name = stmt.value.id
if action_name not in XDP_ACTIONS:
raise ValueError(
f"Unknown XDP action: {action_name}. Available: {XDP_ACTIONS.keys()}"
)
return False
value = XDP_ACTIONS[action_name]
builder.ret(ir.Constant(ret_type, value))
logger.debug(f"Generated XDP action return: {action_name} = {value}")
return True

View File

@ -0,0 +1,13 @@
from .helper_utils import HelperHandlerRegistry
from .bpf_helper_handler import handle_helper_call
from .helpers import ktime, pid, deref, XDP_DROP, XDP_PASS
__all__ = [
"HelperHandlerRegistry",
"handle_helper_call",
"ktime",
"pid",
"deref",
"XDP_DROP",
"XDP_PASS",
]

View File

@ -0,0 +1,347 @@
import ast
from llvmlite import ir
from enum import Enum
from .helper_utils import (
HelperHandlerRegistry,
get_or_create_ptr_from_arg,
get_flags_val,
handle_fstring_print,
simple_string_print,
get_data_ptr_and_size,
)
from logging import Logger
import logging
logger: Logger = logging.getLogger(__name__)
class BPFHelperID(Enum):
BPF_MAP_LOOKUP_ELEM = 1
BPF_MAP_UPDATE_ELEM = 2
BPF_MAP_DELETE_ELEM = 3
BPF_KTIME_GET_NS = 5
BPF_PRINTK = 6
BPF_GET_CURRENT_PID_TGID = 14
BPF_PERF_EVENT_OUTPUT = 25
@HelperHandlerRegistry.register("ktime")
def bpf_ktime_get_ns_emitter(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
):
"""
Emit LLVM IR for bpf_ktime_get_ns helper function call.
"""
# func is an arg to just have a uniform signature with other emitters
helper_id = ir.Constant(ir.IntType(64), BPFHelperID.BPF_KTIME_GET_NS.value)
fn_type = ir.FunctionType(ir.IntType(64), [], var_arg=False)
fn_ptr_type = ir.PointerType(fn_type)
fn_ptr = builder.inttoptr(helper_id, fn_ptr_type)
result = builder.call(fn_ptr, [], tail=False)
return result, ir.IntType(64)
@HelperHandlerRegistry.register("lookup")
def bpf_map_lookup_elem_emitter(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
):
"""
Emit LLVM IR for bpf_map_lookup_elem helper function call.
"""
if not call.args or len(call.args) != 1:
raise ValueError(
f"Map lookup expects exactly one argument (key), got {len(call.args)}"
)
key_ptr = get_or_create_ptr_from_arg(call.args[0], builder, local_sym_tab)
map_void_ptr = builder.bitcast(map_ptr, ir.PointerType())
fn_type = ir.FunctionType(
ir.PointerType(), # Return type: void*
[ir.PointerType(), ir.PointerType()], # Args: (void*, void*)
var_arg=False,
)
fn_ptr_type = ir.PointerType(fn_type)
fn_addr = ir.Constant(ir.IntType(64), BPFHelperID.BPF_MAP_LOOKUP_ELEM.value)
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
result = builder.call(fn_ptr, [map_void_ptr, key_ptr], tail=False)
return result, ir.PointerType()
@HelperHandlerRegistry.register("print")
def bpf_printk_emitter(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
):
"""Emit LLVM IR for bpf_printk helper function call."""
if not hasattr(func, "_fmt_counter"):
func._fmt_counter = 0
if not call.args:
raise ValueError("bpf_printk expects at least one argument (format string)")
args = []
if isinstance(call.args[0], ast.JoinedStr):
args = handle_fstring_print(
call.args[0],
module,
builder,
func,
local_sym_tab,
struct_sym_tab,
)
elif isinstance(call.args[0], ast.Constant) and isinstance(call.args[0].value, str):
# TODO: We are only supporting single arguments for now.
# In case of multiple args, the first one will be taken.
args = simple_string_print(call.args[0].value, module, builder, func)
else:
raise NotImplementedError(
"Only simple strings or f-strings are supported in bpf_printk."
)
fn_type = ir.FunctionType(
ir.IntType(64), [ir.PointerType(), ir.IntType(32)], var_arg=True
)
fn_ptr_type = ir.PointerType(fn_type)
fn_addr = ir.Constant(ir.IntType(64), BPFHelperID.BPF_PRINTK.value)
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
builder.call(fn_ptr, args, tail=True)
return None
@HelperHandlerRegistry.register("update")
def bpf_map_update_elem_emitter(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
):
"""
Emit LLVM IR for bpf_map_update_elem helper function call.
Expected call signature: map.update(key, value, flags=0)
"""
if not call.args or len(call.args) < 2 or len(call.args) > 3:
raise ValueError(
f"Map update expects 2 or 3 args (key, value, flags), got {len(call.args)}"
)
key_arg = call.args[0]
value_arg = call.args[1]
flags_arg = call.args[2] if len(call.args) > 2 else None
key_ptr = get_or_create_ptr_from_arg(key_arg, builder, local_sym_tab)
value_ptr = get_or_create_ptr_from_arg(value_arg, builder, local_sym_tab)
flags_val = get_flags_val(flags_arg, builder, local_sym_tab)
map_void_ptr = builder.bitcast(map_ptr, ir.PointerType())
fn_type = ir.FunctionType(
ir.IntType(64),
[ir.PointerType(), ir.PointerType(), ir.PointerType(), ir.IntType(64)],
var_arg=False,
)
fn_ptr_type = ir.PointerType(fn_type)
fn_addr = ir.Constant(ir.IntType(64), BPFHelperID.BPF_MAP_UPDATE_ELEM.value)
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
if isinstance(flags_val, int):
flags_const = ir.Constant(ir.IntType(64), flags_val)
else:
flags_const = flags_val
result = builder.call(
fn_ptr, [map_void_ptr, key_ptr, value_ptr, flags_const], tail=False
)
return result, None
@HelperHandlerRegistry.register("delete")
def bpf_map_delete_elem_emitter(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
):
"""
Emit LLVM IR for bpf_map_delete_elem helper function call.
Expected call signature: map.delete(key)
"""
if not call.args or len(call.args) != 1:
raise ValueError(
f"Map delete expects exactly one argument (key), got {len(call.args)}"
)
key_ptr = get_or_create_ptr_from_arg(call.args[0], builder, local_sym_tab)
map_void_ptr = builder.bitcast(map_ptr, ir.PointerType())
# Define function type for bpf_map_delete_elem
fn_type = ir.FunctionType(
ir.IntType(64), # Return type: int64 (status code)
[ir.PointerType(), ir.PointerType()], # Args: (void*, void*)
var_arg=False,
)
fn_ptr_type = ir.PointerType(fn_type)
fn_addr = ir.Constant(ir.IntType(64), BPFHelperID.BPF_MAP_DELETE_ELEM.value)
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
result = builder.call(fn_ptr, [map_void_ptr, key_ptr], tail=False)
return result, None
@HelperHandlerRegistry.register("pid")
def bpf_get_current_pid_tgid_emitter(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
):
"""
Emit LLVM IR for bpf_get_current_pid_tgid helper function call.
"""
# func is an arg to just have a uniform signature with other emitters
helper_id = ir.Constant(ir.IntType(64), BPFHelperID.BPF_GET_CURRENT_PID_TGID.value)
fn_type = ir.FunctionType(ir.IntType(64), [], var_arg=False)
fn_ptr_type = ir.PointerType(fn_type)
fn_ptr = builder.inttoptr(helper_id, fn_ptr_type)
result = builder.call(fn_ptr, [], tail=False)
# Extract the lower 32 bits (PID) using bitwise AND with 0xFFFFFFFF
mask = ir.Constant(ir.IntType(64), 0xFFFFFFFF)
pid = builder.and_(result, mask)
return pid, ir.IntType(64)
@HelperHandlerRegistry.register("output")
def bpf_perf_event_output_handler(
call,
map_ptr,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
):
if len(call.args) != 1:
raise ValueError(
f"Perf event output expects exactly one argument, got {len(call.args)}"
)
data_arg = call.args[0]
ctx_ptr = func.args[0] # First argument to the function is ctx
data_ptr, size_val = get_data_ptr_and_size(data_arg, local_sym_tab, struct_sym_tab)
# BPF_F_CURRENT_CPU is -1 in 32 bit
flags_val = ir.Constant(ir.IntType(64), 0xFFFFFFFF)
map_void_ptr = builder.bitcast(map_ptr, ir.PointerType())
data_void_ptr = builder.bitcast(data_ptr, ir.PointerType())
fn_type = ir.FunctionType(
ir.IntType(64),
[
ir.PointerType(ir.IntType(8)),
ir.PointerType(),
ir.IntType(64),
ir.PointerType(),
ir.IntType(64),
],
var_arg=False,
)
fn_ptr_type = ir.PointerType(fn_type)
# helper id
fn_addr = ir.Constant(ir.IntType(64), BPFHelperID.BPF_PERF_EVENT_OUTPUT.value)
fn_ptr = builder.inttoptr(fn_addr, fn_ptr_type)
result = builder.call(
fn_ptr, [ctx_ptr, map_void_ptr, flags_val, data_void_ptr, size_val], tail=False
)
return result, None
def handle_helper_call(
call,
module,
builder,
func,
local_sym_tab=None,
map_sym_tab=None,
struct_sym_tab=None,
):
"""Process a BPF helper function call and emit the appropriate LLVM IR."""
# Helper function to get map pointer and invoke handler
def invoke_helper(method_name, map_ptr=None):
handler = HelperHandlerRegistry.get_handler(method_name)
if not handler:
raise NotImplementedError(
f"Helper function '{method_name}' is not implemented."
)
return handler(
call,
map_ptr,
module,
builder,
func,
local_sym_tab,
struct_sym_tab,
)
# Handle direct function calls (e.g., print(), ktime())
if isinstance(call.func, ast.Name):
return invoke_helper(call.func.id)
# Handle method calls (e.g., map.lookup(), map.update())
elif isinstance(call.func, ast.Attribute):
method_name = call.func.attr
value = call.func.value
logger.info(f"Handling method call: {ast.dump(call.func)}")
# Get map pointer from different styles of map access
if isinstance(value, ast.Call) and isinstance(value.func, ast.Name):
# Func style: my_map().lookup(key)
map_name = value.func.id
elif isinstance(value, ast.Name):
# Direct style: my_map.lookup(key)
map_name = value.id
else:
raise NotImplementedError(
f"Unsupported map access pattern: {ast.dump(value)}"
)
# Verify map exists and get pointer
if not map_sym_tab or map_name not in map_sym_tab:
raise ValueError(f"Map '{map_name}' not found in symbol table")
return invoke_helper(method_name, map_sym_tab[map_name])
return None

View File

@ -0,0 +1,307 @@
import ast
import logging
from collections.abc import Callable
from llvmlite import ir
from pythonbpf.expr import eval_expr
logger = logging.getLogger(__name__)
class HelperHandlerRegistry:
"""Registry for BPF helpers"""
_handlers: dict[str, Callable] = {}
@classmethod
def register(cls, helper_name):
"""Decorator to register a handler function for a helper"""
def decorator(func):
cls._handlers[helper_name] = func
return func
return decorator
@classmethod
def get_handler(cls, helper_name):
"""Get the handler function for a helper"""
return cls._handlers.get(helper_name)
@classmethod
def has_handler(cls, helper_name):
"""Check if a handler function is registered for a helper"""
return helper_name in cls._handlers
def get_var_ptr_from_name(var_name, local_sym_tab):
"""Get a pointer to a variable from the symbol table."""
if local_sym_tab and var_name in local_sym_tab:
return local_sym_tab[var_name].var
raise ValueError(f"Variable '{var_name}' not found in local symbol table")
def create_int_constant_ptr(value, builder, int_width=64):
"""Create a pointer to an integer constant."""
# Default to 64-bit integer
int_type = ir.IntType(int_width)
ptr = builder.alloca(int_type)
ptr.align = int_type.width // 8
builder.store(ir.Constant(int_type, value), ptr)
return ptr
def get_or_create_ptr_from_arg(arg, builder, local_sym_tab):
"""Extract or create pointer from the call arguments."""
if isinstance(arg, ast.Name):
ptr = get_var_ptr_from_name(arg.id, local_sym_tab)
elif isinstance(arg, ast.Constant) and isinstance(arg.value, int):
ptr = create_int_constant_ptr(arg.value, builder)
else:
raise NotImplementedError(
"Only simple variable names are supported as args in map helpers."
)
return ptr
def get_flags_val(arg, builder, local_sym_tab):
"""Extract or create flags value from the call arguments."""
if not arg:
return 0
if isinstance(arg, ast.Name):
if local_sym_tab and arg.id in local_sym_tab:
flags_ptr = local_sym_tab[arg.id].var
return builder.load(flags_ptr)
else:
raise ValueError(f"Variable '{arg.id}' not found in local symbol table")
elif isinstance(arg, ast.Constant) and isinstance(arg.value, int):
return arg.value
raise NotImplementedError(
"Only var names or int consts are supported as map helpers flags."
)
def simple_string_print(string_value, module, builder, func):
"""Prepare arguments for bpf_printk from a simple string value"""
fmt_str = string_value + "\n\0"
fmt_ptr = _create_format_string_global(fmt_str, func, module, builder)
args = [fmt_ptr, ir.Constant(ir.IntType(32), len(fmt_str))]
return args
def handle_fstring_print(
joined_str,
module,
builder,
func,
local_sym_tab=None,
struct_sym_tab=None,
):
"""Handle f-string formatting for bpf_printk emitter."""
fmt_parts = []
exprs = []
for value in joined_str.values:
logger.debug(f"Processing f-string value: {ast.dump(value)}")
if isinstance(value, ast.Constant):
_process_constant_in_fstring(value, fmt_parts, exprs)
elif isinstance(value, ast.FormattedValue):
_process_fval(
value,
fmt_parts,
exprs,
local_sym_tab,
struct_sym_tab,
)
else:
raise NotImplementedError(f"Unsupported f-string value type: {type(value)}")
fmt_str = "".join(fmt_parts)
args = simple_string_print(fmt_str, module, builder, func)
# NOTE: Process expressions (limited to 3 due to BPF constraints)
if len(exprs) > 3:
logger.warning("bpf_printk supports up to 3 args, extra args will be ignored.")
for expr in exprs[:3]:
arg_value = _prepare_expr_args(
expr,
func,
module,
builder,
local_sym_tab,
struct_sym_tab,
)
args.append(arg_value)
return args
def _process_constant_in_fstring(cst, fmt_parts, exprs):
"""Process constant values in f-string."""
if isinstance(cst.value, str):
fmt_parts.append(cst.value)
elif isinstance(cst.value, int):
fmt_parts.append("%lld")
exprs.append(ir.Constant(ir.IntType(64), cst.value))
else:
raise NotImplementedError(
f"Unsupported constant type in f-string: {type(cst.value)}"
)
def _process_fval(fval, fmt_parts, exprs, local_sym_tab, struct_sym_tab):
"""Process formatted values in f-string."""
logger.debug(f"Processing formatted value: {ast.dump(fval)}")
if isinstance(fval.value, ast.Name):
_process_name_in_fval(fval.value, fmt_parts, exprs, local_sym_tab)
elif isinstance(fval.value, ast.Attribute):
_process_attr_in_fval(
fval.value,
fmt_parts,
exprs,
local_sym_tab,
struct_sym_tab,
)
else:
raise NotImplementedError(
f"Unsupported formatted value in f-string: {type(fval.value)}"
)
def _process_name_in_fval(name_node, fmt_parts, exprs, local_sym_tab):
"""Process name nodes in formatted values."""
if local_sym_tab and name_node.id in local_sym_tab:
_, var_type, tmp = local_sym_tab[name_node.id]
_populate_fval(var_type, name_node, fmt_parts, exprs)
def _process_attr_in_fval(attr_node, fmt_parts, exprs, local_sym_tab, struct_sym_tab):
"""Process attribute nodes in formatted values."""
if (
isinstance(attr_node.value, ast.Name)
and local_sym_tab
and attr_node.value.id in local_sym_tab
):
var_name = attr_node.value.id
field_name = attr_node.attr
var_type = local_sym_tab[var_name].metadata
if var_type not in struct_sym_tab:
raise ValueError(
f"Struct '{var_type}' for '{var_name}' not in symbol table"
)
struct_info = struct_sym_tab[var_type]
if field_name not in struct_info.fields:
raise ValueError(f"Field '{field_name}' not found in struct '{var_type}'")
field_type = struct_info.field_type(field_name)
_populate_fval(field_type, attr_node, fmt_parts, exprs)
else:
raise NotImplementedError(
"Only simple attribute on local vars is supported in f-strings."
)
def _populate_fval(ftype, node, fmt_parts, exprs):
"""Populate format parts and expressions based on field type."""
if isinstance(ftype, ir.IntType):
# TODO: We print as signed integers only for now
if ftype.width == 64:
fmt_parts.append("%lld")
exprs.append(node)
elif ftype.width == 32:
fmt_parts.append("%d")
exprs.append(node)
else:
raise NotImplementedError(
f"Unsupported integer width in f-string: {ftype.width}"
)
elif ftype == ir.PointerType(ir.IntType(8)):
# NOTE: We assume i8* is a string
fmt_parts.append("%s")
exprs.append(node)
else:
raise NotImplementedError(f"Unsupported field type in f-string: {ftype}")
def _create_format_string_global(fmt_str, func, module, builder):
"""Create a global variable for the format string."""
fmt_name = f"{func.name}____fmt{func._fmt_counter}"
func._fmt_counter += 1
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(
ir.ArrayType(ir.IntType(8), len(fmt_str)), bytearray(fmt_str.encode("utf8"))
)
fmt_gvar.linkage = "internal"
fmt_gvar.align = 1
return builder.bitcast(fmt_gvar, ir.PointerType())
def _prepare_expr_args(expr, func, module, builder, local_sym_tab, struct_sym_tab):
"""Evaluate and prepare an expression to use as an arg for bpf_printk."""
val, _ = eval_expr(
func,
module,
builder,
expr,
local_sym_tab,
None,
struct_sym_tab,
)
if val:
if isinstance(val.type, ir.PointerType):
val = builder.ptrtoint(val, ir.IntType(64))
elif isinstance(val.type, ir.IntType):
if val.type.width < 64:
val = builder.sext(val, ir.IntType(64))
else:
logger.warning(
"Only int and ptr supported in bpf_printk args. Others default to 0."
)
val = ir.Constant(ir.IntType(64), 0)
return val
else:
logger.warning(
"Failed to evaluate expression for bpf_printk argument. "
"It will be converted to 0."
)
return ir.Constant(ir.IntType(64), 0)
def get_data_ptr_and_size(data_arg, local_sym_tab, struct_sym_tab):
"""Extract data pointer and size information for perf event output."""
if isinstance(data_arg, ast.Name):
data_name = data_arg.id
if local_sym_tab and data_name in local_sym_tab:
data_ptr = local_sym_tab[data_name].var
else:
raise ValueError(
f"Data variable {data_name} not found in local symbol table."
)
# Check if data_name is a struct
data_type = local_sym_tab[data_name].metadata
if data_type in struct_sym_tab:
struct_info = struct_sym_tab[data_type]
size_val = ir.Constant(ir.IntType(64), struct_info.size)
return data_ptr, size_val
else:
raise ValueError(f"Struct {data_type} for {data_name} not in symbol table.")
else:
raise NotImplementedError(
"Only simple object names are supported as data in perf event output."
)

View File

@ -1,5 +1,9 @@
from llvmlite import ir from llvmlite import ir
import ast import ast
from logging import Logger
import logging
logger: Logger = logging.getLogger(__name__)
def emit_license(module: ir.Module, license_str: str): def emit_license(module: ir.Module, license_str: str):
@ -41,9 +45,9 @@ def license_processing(tree, module):
emit_license(module, node.body[0].value.value) emit_license(module, node.body[0].value.value)
return "LICENSE" return "LICENSE"
else: else:
print("ERROR: LICENSE() must return a string literal") logger.info("ERROR: LICENSE() must return a string literal")
return None return None
else: else:
print("ERROR: LICENSE already defined") logger.info("ERROR: LICENSE already defined")
return None return None
return None return None

View File

@ -1,4 +1,4 @@
from .maps import HashMap, PerfEventArray from .maps import HashMap, PerfEventArray, RingBuf
from .maps_pass import maps_proc from .maps_pass import maps_proc
__all__ = ["HashMap", "PerfEventArray", "maps_proc"] __all__ = ["HashMap", "PerfEventArray", "maps_proc", "RingBuf"]

View File

@ -1,3 +1,4 @@
# This file provides type and function hints only and does not actually give any functionality.
class HashMap: class HashMap:
def __init__(self, key, value, max_entries): def __init__(self, key, value, max_entries):
self.key = key self.key = key
@ -33,3 +34,18 @@ class PerfEventArray:
def output(self, data): def output(self, data):
pass # Placeholder for output method pass # Placeholder for output method
class RingBuf:
def __init__(self, max_entries):
self.max_entries = max_entries
def reserve(self, size: int, flags=0):
if size > self.max_entries:
raise ValueError("size cannot be greater than set maximum entries")
return 0
def submit(self, data, flags=0):
pass
# add discard, output and also give names to flags and stuff

View File

@ -1,11 +1,12 @@
import ast import ast
from logging import Logger
from llvmlite import ir from llvmlite import ir
from enum import Enum from enum import Enum
from .maps_utils import MapProcessorRegistry from .maps_utils import MapProcessorRegistry
from ..debuginfo import DebugInfoGenerator from pythonbpf.debuginfo import DebugInfoGenerator
import logging import logging
logger = logging.getLogger(__name__) logger: Logger = logging.getLogger(__name__)
def maps_proc(tree, module, chunks): def maps_proc(tree, module, chunks):
@ -13,7 +14,7 @@ def maps_proc(tree, module, chunks):
map_sym_tab = {} map_sym_tab = {}
for func_node in chunks: for func_node in chunks:
if is_map(func_node): if is_map(func_node):
print(f"Found BPF map: {func_node.name}") logger.info(f"Found BPF map: {func_node.name}")
map_sym_tab[func_node.name] = process_bpf_map(func_node, module) map_sym_tab[func_node.name] = process_bpf_map(func_node, module)
return map_sym_tab return map_sym_tab
@ -26,8 +27,41 @@ def is_map(func_node):
class BPFMapType(Enum): class BPFMapType(Enum):
UNSPEC = 0
HASH = 1 HASH = 1
ARRAY = 2
PROG_ARRAY = 3
PERF_EVENT_ARRAY = 4 PERF_EVENT_ARRAY = 4
PERCPU_HASH = 5
PERCPU_ARRAY = 6
STACK_TRACE = 7
CGROUP_ARRAY = 8
LRU_HASH = 9
LRU_PERCPU_HASH = 10
LPM_TRIE = 11
ARRAY_OF_MAPS = 12
HASH_OF_MAPS = 13
DEVMAP = 14
SOCKMAP = 15
CPUMAP = 16
XSKMAP = 17
SOCKHASH = 18
CGROUP_STORAGE_DEPRECATED = 19
CGROUP_STORAGE = 19
REUSEPORT_SOCKARRAY = 20
PERCPU_CGROUP_STORAGE_DEPRECATED = 21
PERCPU_CGROUP_STORAGE = 21
QUEUE = 22
STACK = 23
SK_STORAGE = 24
DEVMAP_HASH = 25
STRUCT_OPS = 26
RINGBUF = 27
INODE_STORAGE = 28
TASK_STORAGE = 29
BLOOM_FILTER = 30
USER_RINGBUF = 31
CGRP_STORAGE = 32
def create_bpf_map(module, map_name, map_params): def create_bpf_map(module, map_name, map_params):
@ -51,13 +85,13 @@ def create_bpf_map(module, map_name, map_params):
def create_map_debug_info(module, map_global, map_name, map_params): def create_map_debug_info(module, map_global, map_name, map_params):
"""Generate debug information metadata for BPF map""" """Generate debug info metadata for BPF maps HASH and PERF_EVENT_ARRAY"""
generator = DebugInfoGenerator(module) generator = DebugInfoGenerator(module)
uint_type = generator.get_uint32_type() uint_type = generator.get_uint32_type()
ulong_type = generator.get_uint64_type() ulong_type = generator.get_uint64_type()
array_type = generator.create_array_type( array_type = generator.create_array_type(
uint_type, map_params.get("type", BPFMapType.HASH).value uint_type, map_params.get("type", BPFMapType.UNSPEC).value
) )
type_ptr = generator.create_pointer_type(array_type, 64) type_ptr = generator.create_pointer_type(array_type, 64)
key_ptr = generator.create_pointer_type( key_ptr = generator.create_pointer_type(
@ -112,6 +146,60 @@ def create_map_debug_info(module, map_global, map_name, map_params):
return global_var return global_var
def create_ringbuf_debug_info(module, map_global, map_name, map_params):
"""Generate debug information metadata for BPF RINGBUF map"""
generator = DebugInfoGenerator(module)
int_type = generator.get_int32_type()
type_array = generator.create_array_type(
int_type, map_params.get("type", BPFMapType.RINGBUF).value
)
type_ptr = generator.create_pointer_type(type_array, 64)
type_member = generator.create_struct_member("type", type_ptr, 0)
max_entries_array = generator.create_array_type(int_type, map_params["max_entries"])
max_entries_ptr = generator.create_pointer_type(max_entries_array, 64)
max_entries_member = generator.create_struct_member(
"max_entries", max_entries_ptr, 64
)
elements_arr = [type_member, max_entries_member]
struct_type = generator.create_struct_type(elements_arr, 128, is_distinct=True)
global_var = generator.create_global_var_debug_info(
map_name, struct_type, is_local=False
)
map_global.set_metadata("dbg", global_var)
return global_var
@MapProcessorRegistry.register("RingBuf")
def process_ringbuf_map(map_name, rval, module):
"""Process a BPF_RINGBUF map declaration"""
logger.info(f"Processing Ringbuf: {map_name}")
map_params = {"type": BPFMapType.RINGBUF}
# Parse max_entries if present
if len(rval.args) >= 1 and isinstance(rval.args[0], ast.Constant):
const_val = rval.args[0].value
if isinstance(const_val, int):
map_params["max_entries"] = const_val
for keyword in rval.keywords:
if keyword.arg == "max_entries" and isinstance(keyword.value, ast.Constant):
const_val = keyword.value.value
if isinstance(const_val, int):
map_params["max_entries"] = const_val
logger.info(f"Ringbuf map parameters: {map_params}")
map_global = create_bpf_map(module, map_name, map_params)
create_ringbuf_debug_info(module, map_global, map_name, map_params)
return map_global
@MapProcessorRegistry.register("HashMap") @MapProcessorRegistry.register("HashMap")
def process_hash_map(map_name, rval, module): def process_hash_map(map_name, rval, module):
"""Process a BPF_HASH map declaration""" """Process a BPF_HASH map declaration"""
@ -190,9 +278,7 @@ def process_bpf_map(func_node, module):
if handler: if handler:
return handler(map_name, rval, module) return handler(map_name, rval, module)
else: else:
logger.warning( logger.warning(f"Unknown map type {rval.func.id}, defaulting to HashMap")
f"Unknown map type " f"{rval.func.id}, defaulting to HashMap"
)
return process_hash_map(map_name, rval, module) return process_hash_map(map_name, rval, module)
else: else:
raise ValueError("Function under @map must return a map") raise ValueError("Function under @map must return a map")

View File

@ -1,7 +1,11 @@
from collections.abc import Callable
from typing import Any
class MapProcessorRegistry: class MapProcessorRegistry:
"""Registry for map processor functions""" """Registry for map processor functions"""
_processors = {} _processors: dict[str, Callable[..., Any]] = {}
@classmethod @classmethod
def register(cls, map_type_name): def register(cls, map_type_name):

View File

@ -19,7 +19,7 @@ def structs_proc(tree, module, chunks):
structs_sym_tab = {} structs_sym_tab = {}
for cls_node in chunks: for cls_node in chunks:
if is_bpf_struct(cls_node): if is_bpf_struct(cls_node):
print(f"Found BPF struct: {cls_node.name}") logger.info(f"Found BPF struct: {cls_node.name}")
struct_info = process_bpf_struct(cls_node, module) struct_info = process_bpf_struct(cls_node, module)
structs_sym_tab[cls_node.name] = struct_info structs_sym_tab[cls_node.name] = struct_info
return structs_sym_tab return structs_sym_tab

View File

@ -1,24 +1,28 @@
from llvmlite import ir from llvmlite import ir
# TODO: THIS IS NOT SUPPOSED TO MATCH STRINGS :skull: # TODO: THIS IS NOT SUPPOSED TO MATCH STRINGS :skull:
mapping = {
"c_int8": ir.IntType(8),
"c_uint8": ir.IntType(8),
"c_int16": ir.IntType(16),
"c_uint16": ir.IntType(16),
"c_int32": ir.IntType(32),
"c_uint32": ir.IntType(32),
"c_int64": ir.IntType(64),
"c_uint64": ir.IntType(64),
"c_float": ir.FloatType(),
"c_double": ir.DoubleType(),
"c_void_p": ir.IntType(64),
# Not so sure about this one
"str": ir.PointerType(ir.IntType(8)),
}
def ctypes_to_ir(ctype: str): def ctypes_to_ir(ctype: str):
mapping = {
"c_int8": ir.IntType(8),
"c_uint8": ir.IntType(8),
"c_int16": ir.IntType(16),
"c_uint16": ir.IntType(16),
"c_int32": ir.IntType(32),
"c_uint32": ir.IntType(32),
"c_int64": ir.IntType(64),
"c_uint64": ir.IntType(64),
"c_float": ir.FloatType(),
"c_double": ir.DoubleType(),
"c_void_p": ir.IntType(64),
# Not so sure about this one
"str": ir.PointerType(ir.IntType(8)),
}
if ctype in mapping: if ctype in mapping:
return mapping[ctype] return mapping[ctype]
raise NotImplementedError(f"No mapping for {ctype}") raise NotImplementedError(f"No mapping for {ctype}")
def is_ctypes(ctype: str) -> bool:
return ctype in mapping

View File

@ -1,47 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <linux/blkdev.h>
#define __TARGET_ARCH_aarch64
#define u64 unsigned long long
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10240);
__type(key, struct request *);
__type(value, u64);
} start SEC(".maps");
SEC("kprobe/blk_start_request")
int BPF_KPROBE(trace_start_req, struct request *req)
{
u64 ts = bpf_ktime_get_ns();
bpf_map_update_elem(&start, &req, &ts, BPF_ANY);
return 0;
}
SEC("kprobe/blk_mq_start_request")
int BPF_KPROBE(trace_start_mq, struct request *req)
{
u64 ts = bpf_ktime_get_ns();
bpf_map_update_elem(&start, &req, &ts, BPF_ANY);
return 0;
}
SEC("kprobe/blk_account_io_completion")
int BPF_KPROBE(trace_completion, struct request *req)
{
u64 *tsp, delta;
tsp = bpf_map_lookup_elem(&start, &req);
if (tsp) {
delta = bpf_ktime_get_ns() - *tsp;
bpf_printk("%d %x %d\n", req->__data_len,
req->cmd_flags, delta / 1000);
bpf_map_delete_elem(&start, &req);
}
return 0;
}
char LICENSE[] SEC("license") = "GPL";

View File

@ -0,0 +1,51 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <linux/types.h>
// Define the structure to be sent via ringbuf
struct event {
__u32 pid;
__u32 uid;
__u64 timestamp;
char comm[16]; // Process name
};
// Define the ringbuffer map
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024); // 256 KB
} events SEC(".maps");
// Tracepoint for execve system calls
SEC("tracepoint/syscalls/sys_enter_execve")
int trace_execve(void *ctx)
{
struct event *e;
__u64 pid_tgid;
__u64 uid_gid;
// Reserve space in the ringbuffer
e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
if (!e)
return 0;
// Fill the struct with data
pid_tgid = bpf_get_current_pid_tgid();
e->pid = pid_tgid >> 32;
uid_gid = bpf_get_current_uid_gid();
e->uid = uid_gid & 0xFFFFFFFF;
e->timestamp = bpf_ktime_get_ns();
bpf_get_current_comm(&e->comm, sizeof(e->comm));
// Submit the event to ringbuffer
bpf_ringbuf_submit(e, 0);
return 0;
}
char LICENSE[] SEC("license") = "GPL";

View File

@ -0,0 +1,34 @@
from pythonbpf import bpf, map, section, bpfglobal, compile
from ctypes import c_void_p, c_int64, c_uint64
from pythonbpf.maps import HashMap
# NOTE: Decided against fixing this
# as a workaround is assigning the result of lookup to a variable
# and then using that variable in the if statement.
# Might fix in future.
@bpf
@map
def last() -> HashMap:
return HashMap(key=c_uint64, value=c_uint64, max_entries=3)
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
last.update(0, 1)
if last.lookup(0) > 0:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,34 @@
from pythonbpf import bpf, struct, section, bpfglobal, compile
from ctypes import c_void_p, c_int64, c_uint64
# NOTE: Decided against fixing this
# as one workaround is to just check any field of the struct
# in the if statement. Ugly but works.
# Might fix in future.
@bpf
@struct
class data_t:
pid: c_uint64
ts: c_uint64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
dat = data_t()
if dat:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -1,9 +1,21 @@
from pythonbpf import bpf, map, section, bpfglobal, compile from pythonbpf import bpf, map, section, bpfglobal, compile
from pythonbpf.helpers import XDP_PASS from pythonbpf.helper import XDP_PASS
from pythonbpf.maps import HashMap from pythonbpf.maps import HashMap
from ctypes import c_void_p, c_int64 from ctypes import c_void_p, c_int64
# NOTE: I have decided to not fix this example for now.
# The issue is in line 31, where we are passing an expression.
# The update helper expects a pointer type. But the problem is
# that we must allocate the space for said pointer in the first
# basic block. As that usage is in a different basic block, we
# are unable to cast the expression to a pointer type. (as we never
# allocated space for it).
# Shall we change our space allocation logic? That allows users to
# spam the same helper with the same args, and still run out of
# stack space. So we consider this usage invalid for now.
# Might fix it later.
@bpf @bpf
@map @map
@ -14,12 +26,12 @@ def count() -> HashMap:
@bpf @bpf
@section("xdp") @section("xdp")
def hello_world(ctx: c_void_p) -> c_int64: def hello_world(ctx: c_void_p) -> c_int64:
prev = count().lookup(0) prev = count.lookup(0)
if prev: if prev:
count().update(0, prev + 1) count.update(0, prev + 1)
return XDP_PASS return XDP_PASS
else: else:
count().update(0, 1) count.update(0, 1)
return XDP_PASS return XDP_PASS

View File

@ -0,0 +1,40 @@
from pythonbpf import bpf, map, section, bpfglobal, compile
from pythonbpf.helper import XDP_PASS
from pythonbpf.maps import HashMap
from ctypes import c_void_p, c_int64
# NOTE: This example exposes the problems with our typing system.
# We can't do steps on line 25 and 27.
# prev is of type i64**. For prev + 1, we deref it down to i64
# To assign it back to prev, we need to go back to i64**.
# We cannot allocate space for the intermediate type now.
# We probably need to track the ref/deref chain for each variable.
@bpf
@map
def count() -> HashMap:
return HashMap(key=c_int64, value=c_int64, max_entries=1)
@bpf
@section("xdp")
def hello_world(ctx: c_void_p) -> c_int64:
prev = count.lookup(0)
if prev:
prev = prev + 1
count.update(0, prev)
return XDP_PASS
else:
count.update(0, 1)
return XDP_PASS
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -3,9 +3,9 @@ from ctypes import c_void_p, c_int64
@bpf @bpf
@section("sometag1") @section("tracepoint/syscalls/sys_enter_sync")
def sometag(ctx: c_void_p) -> c_int64: def sometag(ctx: c_void_p) -> c_int64:
a = 1 + 2 + 1 a = 1 + 2 + 1 + 12 + 13
print(f"{a}") print(f"{a}")
return c_int64(0) return c_int64(0)

View File

@ -0,0 +1,20 @@
from pythonbpf import compile, bpf, section, bpfglobal
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_sync")
def sometag(ctx: c_void_p) -> c_int64:
b = 1 + 2
a = 1 + b
print(f"{a}")
return c_int64(0)
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,32 @@
from pythonbpf import bpf, map, section, bpfglobal, compile
from ctypes import c_void_p, c_int64, c_uint64
from pythonbpf.maps import HashMap
@bpf
@map
def last() -> HashMap:
return HashMap(key=c_uint64, value=c_uint64, max_entries=3)
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
last.update(0, 1)
last.update(1, 2)
x = last.lookup(0)
y = last.lookup(1)
if x and y:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,21 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
if True:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,21 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
if (0 + 1) * 0:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,21 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
if 0:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,30 @@
from pythonbpf import bpf, map, section, bpfglobal, compile
from ctypes import c_void_p, c_int64, c_uint64
from pythonbpf.maps import HashMap
@bpf
@map
def last() -> HashMap:
return HashMap(key=c_uint64, value=c_uint64, max_entries=3)
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
# last.update(0, 1)
tsp = last.lookup(0)
if tsp:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,30 @@
from pythonbpf import bpf, map, section, bpfglobal, compile
from ctypes import c_void_p, c_int64, c_uint64
from pythonbpf.maps import HashMap
@bpf
@map
def last() -> HashMap:
return HashMap(key=c_uint64, value=c_uint64, max_entries=3)
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
last.update(0, 1)
tsp = last.lookup(0)
if tsp > 0:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,30 @@
from pythonbpf import bpf, map, section, bpfglobal, compile
from ctypes import c_void_p, c_int64, c_uint64
from pythonbpf.maps import HashMap
@bpf
@map
def last() -> HashMap:
return HashMap(key=c_uint64, value=c_uint64, max_entries=3)
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
# last.update(0, 1)
tsp = last.lookup(0)
if not tsp:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,32 @@
from pythonbpf import bpf, map, section, bpfglobal, compile
from ctypes import c_void_p, c_int64, c_uint64
from pythonbpf.maps import HashMap
@bpf
@map
def last() -> HashMap:
return HashMap(key=c_uint64, value=c_uint64, max_entries=3)
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
last.update(0, 1)
# last.update(1, 2)
x = last.lookup(0)
y = last.lookup(1)
if x or y:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,29 @@
from pythonbpf import bpf, struct, section, bpfglobal, compile
from ctypes import c_void_p, c_int64, c_uint64
@bpf
@struct
class data_t:
pid: c_uint64
ts: c_uint64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
dat = data_t()
if dat.ts:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,23 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64, c_int32
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
x = 0
y = c_int32(0)
if x == y:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,22 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
x = 0
if x:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,22 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
x = 0
if x * 1:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,22 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
x = 2
if x > 3:
print("Hello, World!")
else:
print("Goodbye, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,51 @@
from pythonbpf import bpf, map, struct, section, bpfglobal, compile, compile_to_ir, BPF
from pythonbpf.helper import ktime, pid
from pythonbpf.maps import PerfEventArray
import logging
from ctypes import c_void_p, c_int32, c_uint64
# PLACEHOLDER EXAMPLE. THIS SHOULD TECHNICALLY STILL FAIL TESTS
@bpf
@struct
class data_t:
pid: c_uint64
ts: c_uint64
comm: str(16)
@bpf
@map
def events() -> PerfEventArray:
return PerfEventArray(key_size=c_int32, value_size=c_int32)
@bpf
@section("tracepoint/syscalls/sys_enter_clone")
def hello(ctx: c_void_p) -> c_int32:
dataobj = data_t()
ts = ktime()
strobj = "hellohellohello"
dataobj.pid = pid()
dataobj.ts = ktime()
# dataobj.comm = strobj
print(
f"clone called at {dataobj.ts} by pid {dataobj.pid}, comm {strobj} at time {ts}"
)
events.output(dataobj)
return c_int32(0)
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile_to_ir("perf_buffer_map.py", "perf_buffer_map.ll")
compile(loglevel=logging.INFO)
b = BPF()
b.load_and_attach()
while True:
print("running")

View File

@ -0,0 +1,18 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
print("Hello, World!")
return 1 + 1 - 2
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,19 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
print("Hello, World!")
a = 2
return a - 2
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,18 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
print("Hello, World!")
return True
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,18 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
print("Hello, World!")
return 1
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,18 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
print("Hello, World!")
return
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,20 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int32
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int32:
print("Hello, World!")
a = 1 # int64
x = 1 # int64
return c_int32(a - x) # typecast to int32
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,18 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int32
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int32:
print("Hello, World!")
return c_int32(1)
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,19 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int32
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int32:
print("Hello, World!")
a = 1 # int64
return c_int32(a) # typecast to int32
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,19 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
print("Hello, World!")
a = 1
return a
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,19 @@
from pythonbpf import bpf, section, bpfglobal, compile
from ctypes import c_void_p, c_int64
from pythonbpf.helper import XDP_PASS
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello_world(ctx: c_void_p) -> c_int64:
print("Hello, World!")
return XDP_PASS
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile()

View File

@ -0,0 +1,35 @@
from pythonbpf import bpf, BPF, map, bpfglobal, section, compile, compile_to_ir
from pythonbpf.maps import RingBuf, HashMap
from ctypes import c_int32, c_void_p
# Define a map
@bpf
@map
def mymap() -> RingBuf:
return RingBuf(max_entries=(1024))
@bpf
@map
def mymap2() -> HashMap:
return HashMap(key=c_int32, value=c_int32, max_entries=1024)
@bpf
@section("tracepoint/syscalls/sys_enter_clone")
def random_section(ctx: c_void_p) -> c_int32:
print("Hello")
return c_int32(0)
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
compile_to_ir("ringbuf.py", "ringbuf.ll")
compile()
b = BPF()
b.load_and_attach()

View File

@ -1,3 +1,5 @@
import logging
from pythonbpf import compile, bpf, section, bpfglobal from pythonbpf import compile, bpf, section, bpfglobal
from ctypes import c_void_p, c_int64 from ctypes import c_void_p, c_int64
@ -5,8 +7,7 @@ from ctypes import c_void_p, c_int64
@bpf @bpf
@section("sometag1") @section("sometag1")
def sometag(ctx: c_void_p) -> c_int64: def sometag(ctx: c_void_p) -> c_int64:
b = 1 + 2 a = 1 - 1
a = 1 + b
return c_int64(a) return c_int64(a)
@ -16,4 +17,4 @@ def LICENSE() -> str:
return "GPL" return "GPL"
compile() compile(loglevel=logging.INFO)