Skip to content

Commit 84fdf52

Browse files
Merge branch 'master' into bcc_examples
2 parents f4d903d + 0d4ebf7 commit 84fdf52

26 files changed

+1286
-68
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ Python-BPF is an LLVM IR generator for eBPF programs written in Python. It uses
4040

4141
---
4242

43+
## Try It Out!
44+
Run
45+
```bash
46+
curl -s https://raw.githubusercontent.com/pythonbpf/Python-BPF/refs/heads/master/tools/setup.sh | sudo bash
47+
```
48+
4349
## Installation
4450

4551
Dependencies:

pyproject.toml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,26 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "pythonbpf"
7-
version = "0.1.4"
7+
version = "0.1.5"
88
description = "Reduced Python frontend for eBPF"
99
authors = [
1010
{ name = "r41k0u", email="[email protected]" },
1111
{ name = "varun-r-mallya", email="[email protected]" }
1212
]
13+
classifiers = [
14+
"Development Status :: 3 - Alpha",
15+
"Intended Audience :: Developers",
16+
"Operating System :: POSIX :: Linux",
17+
"Programming Language :: Python :: 3",
18+
"Programming Language :: Python :: 3.8",
19+
"Programming Language :: Python :: 3.9",
20+
"Programming Language :: Python :: 3.10",
21+
"Programming Language :: Python :: 3.11",
22+
"Programming Language :: Python :: 3.12",
23+
"Programming Language :: Python",
24+
"Topic :: Software Development :: Libraries :: Python Modules",
25+
"Topic :: System :: Operating System Kernels :: Linux",
26+
]
1327
readme = "README.md"
1428
license = {text = "Apache-2.0"}
1529
requires-python = ">=3.8"

pythonbpf/allocation_pass.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from dataclasses import dataclass
66
from typing import Any
77
from pythonbpf.helper import HelperHandlerRegistry
8+
from .expr import VmlinuxHandlerRegistry
89
from pythonbpf.type_deducer import ctypes_to_ir
910

1011
logger = logging.getLogger(__name__)
@@ -64,6 +65,15 @@ def handle_assign_allocation(builder, stmt, local_sym_tab, structs_sym_tab):
6465
if var_name in local_sym_tab:
6566
logger.debug(f"Variable {var_name} already allocated, skipping")
6667
continue
68+
69+
# When allocating a variable, check if it's a vmlinux struct type
70+
if isinstance(stmt.value, ast.Name) and VmlinuxHandlerRegistry.is_vmlinux_struct(
71+
stmt.value.id
72+
):
73+
# Handle vmlinux struct allocation
74+
# This requires more implementation
75+
print(stmt.value)
76+
pass
6777

6878
# Determine type and allocate based on rval
6979
if isinstance(rval, ast.Call):
@@ -85,7 +95,6 @@ def handle_assign_allocation(builder, stmt, local_sym_tab, structs_sym_tab):
8595
f"Unsupported assignment value type for {var_name}: {type(rval).__name__}"
8696
)
8797

88-
8998
def _allocate_for_call(builder, var_name, rval, local_sym_tab, structs_sym_tab):
9099
"""Allocate memory for variable assigned from a call."""
91100

pythonbpf/codegen.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from .maps import maps_proc
66
from .structs import structs_proc
77
from .vmlinux_parser import vmlinux_proc
8+
from pythonbpf.vmlinux_parser.vmlinux_exports_handler import VmlinuxHandler
9+
from .expr import VmlinuxHandlerRegistry
810
from .globals_pass import (
911
globals_list_creation,
1012
globals_processing,
@@ -19,10 +21,20 @@
1921
import tempfile
2022
from logging import Logger
2123
import logging
24+
import re
2225

2326
logger: Logger = logging.getLogger(__name__)
2427

25-
VERSION = "v0.1.4"
28+
VERSION = "v0.1.5"
29+
30+
31+
def finalize_module(original_str):
32+
"""After all IR generation is complete, we monkey patch btf_ama attribute"""
33+
34+
# Create a string with applied transformation of btf_ama attribute addition to BTF struct field accesses.
35+
pattern = r'(@"llvm\.[^"]+:[^"]*" = external global i64, !llvm\.preserve\.access\.index ![0-9]+)'
36+
replacement = r'\1 "btf_ama"'
37+
return re.sub(pattern, replacement, original_str)
2638

2739

2840
def find_bpf_chunks(tree):
@@ -45,11 +57,14 @@ def processor(source_code, filename, module):
4557
for func_node in bpf_chunks:
4658
logger.info(f"Found BPF function/struct: {func_node.name}")
4759

48-
vmlinux_proc(tree, module)
60+
vmlinux_symtab = vmlinux_proc(tree, module)
61+
if vmlinux_symtab:
62+
handler = VmlinuxHandler.initialize(vmlinux_symtab)
63+
VmlinuxHandlerRegistry.set_handler(handler)
64+
4965
populate_global_symbol_table(tree, module)
5066
license_processing(tree, module)
5167
globals_processing(tree, module)
52-
5368
structs_sym_tab = structs_proc(tree, module, bpf_chunks)
5469
map_sym_tab = maps_proc(tree, module, bpf_chunks)
5570
func_proc(tree, module, bpf_chunks, map_sym_tab, structs_sym_tab)
@@ -122,10 +137,12 @@ def compile_to_ir(filename: str, output: str, loglevel=logging.INFO):
122137

123138
module.add_named_metadata("llvm.ident", [f"PythonBPF {VERSION}"])
124139

140+
module_string = finalize_module(str(module))
141+
125142
logger.info(f"IR written to {output}")
126143
with open(output, "w") as f:
127144
f.write(f'source_filename = "{filename}"\n')
128-
f.write(str(module))
145+
f.write(module_string)
129146
f.write("\n")
130147

131148
return output, structs_sym_tab, maps_sym_tab

pythonbpf/debuginfo/debug_info_generator.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,20 @@ def create_array_type(self, base_type: Any, count: int) -> Any:
8181
},
8282
)
8383

84+
def create_array_type_vmlinux(self, type_info: Any, count: int) -> Any:
85+
"""Create an array type of the given base type with specified count"""
86+
base_type, type_sizing = type_info
87+
subrange = self.module.add_debug_info("DISubrange", {"count": count})
88+
return self.module.add_debug_info(
89+
"DICompositeType",
90+
{
91+
"tag": dc.DW_TAG_array_type,
92+
"baseType": base_type,
93+
"size": type_sizing,
94+
"elements": [subrange],
95+
},
96+
)
97+
8498
@staticmethod
8599
def _compute_array_size(base_type: Any, count: int) -> int:
86100
# Extract size from base_type if possible
@@ -101,6 +115,23 @@ def create_struct_member(self, name: str, base_type: Any, offset: int) -> Any:
101115
},
102116
)
103117

118+
def create_struct_member_vmlinux(
119+
self, name: str, base_type_with_size: Any, offset: int
120+
) -> Any:
121+
"""Create a struct member with the given name, type, and offset"""
122+
base_type, type_size = base_type_with_size
123+
return self.module.add_debug_info(
124+
"DIDerivedType",
125+
{
126+
"tag": dc.DW_TAG_member,
127+
"name": name,
128+
"file": self.module._file_metadata,
129+
"baseType": base_type,
130+
"size": type_size,
131+
"offset": offset,
132+
},
133+
)
134+
104135
def create_struct_type(
105136
self, members: List[Any], size: int, is_distinct: bool
106137
) -> Any:
@@ -116,6 +147,22 @@ def create_struct_type(
116147
is_distinct=is_distinct,
117148
)
118149

150+
def create_struct_type_with_name(
151+
self, name: str, members: List[Any], size: int, is_distinct: bool
152+
) -> Any:
153+
"""Create a struct type with the given members and size"""
154+
return self.module.add_debug_info(
155+
"DICompositeType",
156+
{
157+
"name": name,
158+
"tag": dc.DW_TAG_structure_type,
159+
"file": self.module._file_metadata,
160+
"size": size,
161+
"elements": members,
162+
},
163+
is_distinct=is_distinct,
164+
)
165+
119166
def create_global_var_debug_info(
120167
self, name: str, var_type: Any, is_local: bool = False
121168
) -> Any:

pythonbpf/expr/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .type_normalization import convert_to_bool, get_base_type_and_depth
33
from .ir_ops import deref_to_depth
44
from .call_registry import CallHandlerRegistry
5+
from .vmlinux_registry import VmlinuxHandlerRegistry
56

67
__all__ = [
78
"eval_expr",
@@ -11,4 +12,5 @@
1112
"deref_to_depth",
1213
"get_operand_value",
1314
"CallHandlerRegistry",
15+
"VmlinuxHandlerRegistry",
1416
]

pythonbpf/expr/expr_pass.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
get_base_type_and_depth,
1313
deref_to_depth,
1414
)
15+
from .vmlinux_registry import VmlinuxHandlerRegistry
1516

1617
logger: Logger = logging.getLogger(__name__)
1718

@@ -27,8 +28,12 @@ def _handle_name_expr(expr: ast.Name, local_sym_tab: Dict, builder: ir.IRBuilder
2728
val = builder.load(var)
2829
return val, local_sym_tab[expr.id].ir_type
2930
else:
30-
logger.info(f"Undefined variable {expr.id}")
31-
return None
31+
# Check if it's a vmlinux enum/constant
32+
vmlinux_result = VmlinuxHandlerRegistry.handle_name(expr.id)
33+
if vmlinux_result is not None:
34+
return vmlinux_result
35+
36+
raise SyntaxError(f"Undefined variable {expr.id}")
3237

3338

3439
def _handle_constant_expr(module, builder, expr: ast.Constant):
@@ -74,6 +79,13 @@ def _handle_attribute_expr(
7479
val = builder.load(gep)
7580
field_type = metadata.field_type(attr_name)
7681
return val, field_type
82+
83+
# Try vmlinux handler as fallback
84+
vmlinux_result = VmlinuxHandlerRegistry.handle_attribute(
85+
expr, local_sym_tab, None, builder
86+
)
87+
if vmlinux_result is not None:
88+
return vmlinux_result
7789
return None
7890

7991

@@ -130,7 +142,12 @@ def get_operand_value(
130142
logger.info(f"var is {var}, base_type is {base_type}, depth is {depth}")
131143
val = deref_to_depth(func, builder, var, depth)
132144
return val
133-
raise ValueError(f"Undefined variable: {operand.id}")
145+
else:
146+
# Check if it's a vmlinux enum/constant
147+
vmlinux_result = VmlinuxHandlerRegistry.handle_name(operand.id)
148+
if vmlinux_result is not None:
149+
val, _ = vmlinux_result
150+
return val
134151
elif isinstance(operand, ast.Constant):
135152
if isinstance(operand.value, int):
136153
cst = ir.Constant(ir.IntType(64), int(operand.value))
@@ -332,6 +349,7 @@ def _handle_unary_op(
332349
neg_one = ir.Constant(ir.IntType(64), -1)
333350
result = builder.mul(operand, neg_one)
334351
return result, ir.IntType(64)
352+
return None
335353

336354

337355
# ============================================================================

pythonbpf/expr/vmlinux_registry.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import ast
2+
3+
4+
class VmlinuxHandlerRegistry:
5+
"""Registry for vmlinux handler operations"""
6+
7+
_handler = None
8+
9+
@classmethod
10+
def set_handler(cls, handler):
11+
"""Set the vmlinux handler"""
12+
cls._handler = handler
13+
14+
@classmethod
15+
def get_handler(cls):
16+
"""Get the vmlinux handler"""
17+
return cls._handler
18+
19+
@classmethod
20+
def handle_name(cls, name):
21+
"""Try to handle a name as vmlinux enum/constant"""
22+
if cls._handler is None:
23+
return None
24+
return cls._handler.handle_vmlinux_enum(name)
25+
26+
@classmethod
27+
def handle_attribute(cls, expr, local_sym_tab, module, builder):
28+
"""Try to handle an attribute access as vmlinux struct field"""
29+
if cls._handler is None:
30+
return None
31+
32+
if isinstance(expr.value, ast.Name):
33+
var_name = expr.value.id
34+
field_name = expr.attr
35+
return cls._handler.handle_vmlinux_struct_field(
36+
var_name, field_name, module, builder, local_sym_tab
37+
)
38+
return None
39+
40+
@classmethod
41+
def is_vmlinux_struct(cls, name):
42+
"""Check if a name refers to a vmlinux struct"""
43+
if cls._handler is None:
44+
return False
45+
return cls._handler.is_vmlinux_struct(name)

pythonbpf/functions/functions_pass.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,13 @@ def process_stmt(
310310

311311

312312
def process_func_body(
313-
module, builder, func_node, func, ret_type, map_sym_tab, structs_sym_tab
313+
module,
314+
builder,
315+
func_node,
316+
func,
317+
ret_type,
318+
map_sym_tab,
319+
structs_sym_tab,
314320
):
315321
"""Process the body of a bpf function"""
316322
# TODO: A lot. We just have print -> bpf_trace_printk for now
@@ -383,7 +389,13 @@ def process_bpf_chunk(func_node, module, return_type, map_sym_tab, structs_sym_t
383389
builder = ir.IRBuilder(block)
384390

385391
process_func_body(
386-
module, builder, func_node, func, ret_type, map_sym_tab, structs_sym_tab
392+
module,
393+
builder,
394+
func_node,
395+
func,
396+
ret_type,
397+
map_sym_tab,
398+
structs_sym_tab,
387399
)
388400
return func
389401

pythonbpf/helper/printk_formatter.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from llvmlite import ir
55
from pythonbpf.expr import eval_expr, get_base_type_and_depth, deref_to_depth
6+
from pythonbpf.expr.vmlinux_registry import VmlinuxHandlerRegistry
67

78
logger = logging.getLogger(__name__)
89

@@ -108,6 +109,16 @@ def _process_name_in_fval(name_node, fmt_parts, exprs, local_sym_tab):
108109
if local_sym_tab and name_node.id in local_sym_tab:
109110
_, var_type, tmp = local_sym_tab[name_node.id]
110111
_populate_fval(var_type, name_node, fmt_parts, exprs)
112+
else:
113+
# Try to resolve through vmlinux registry if not in local symbol table
114+
result = VmlinuxHandlerRegistry.handle_name(name_node.id)
115+
if result:
116+
val, var_type = result
117+
_populate_fval(var_type, name_node, fmt_parts, exprs)
118+
else:
119+
raise ValueError(
120+
f"Variable '{name_node.id}' not found in symbol table or vmlinux"
121+
)
111122

112123

113124
def _process_attr_in_fval(attr_node, fmt_parts, exprs, local_sym_tab, struct_sym_tab):

0 commit comments

Comments
 (0)