Skip to content

Commit 54993ce

Browse files
authored
Merge branch 'master' into fix-maps
2 parents 05083bd + 62ca3b5 commit 54993ce

File tree

10 files changed

+146
-107
lines changed

10 files changed

+146
-107
lines changed

pyproject.toml

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

55
[project]
66
name = "pythonbpf"
7-
version = "0.1.3"
7+
version = "0.1.4"
88
description = "Reduced Python frontend for eBPF"
99
authors = [
1010
{ name = "r41k0u", email="[email protected]" },

pythonbpf/binary_ops.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import ast
22
from llvmlite import ir
3+
from logging import Logger
4+
import logging
5+
6+
logger: Logger = logging.getLogger(__name__)
37

48

59
def recursive_dereferencer(var, builder):
@@ -17,7 +21,7 @@ def recursive_dereferencer(var, builder):
1721

1822

1923
def handle_binary_op(rval, module, builder, var_name, local_sym_tab, map_sym_tab, func):
20-
print(module)
24+
logger.info(f"module {module}")
2125
left = rval.left
2226
right = rval.right
2327
op = rval.op
@@ -43,7 +47,7 @@ def handle_binary_op(rval, module, builder, var_name, local_sym_tab, map_sym_tab
4347
else:
4448
raise SyntaxError("Unsupported right operand type")
4549

46-
print(f"left is {left}, right is {right}, op is {op}")
50+
logger.info(f"left is {left}, right is {right}, op is {op}")
4751

4852
if isinstance(op, ast.Add):
4953
builder.store(builder.add(left, right), local_sym_tab[var_name].var)

pythonbpf/codegen.py

Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
from .maps import maps_proc
66
from .structs import structs_proc
77
from .globals_pass import globals_processing
8-
from .debuginfo import DW_LANG_C11, DwarfBehaviorEnum
8+
from .debuginfo import DW_LANG_C11, DwarfBehaviorEnum, DebugInfoGenerator
99
import os
1010
import subprocess
1111
import inspect
1212
from pathlib import Path
1313
from pylibbpf import BpfProgram
1414
import tempfile
15+
from logging import Logger
16+
import logging
1517

16-
VERSION = "v0.1.3"
18+
logger: Logger = logging.getLogger(__name__)
19+
20+
VERSION = "v0.1.4"
1721

1822

1923
def find_bpf_chunks(tree):
@@ -30,11 +34,11 @@ def find_bpf_chunks(tree):
3034

3135
def processor(source_code, filename, module):
3236
tree = ast.parse(source_code, filename)
33-
print(ast.dump(tree, indent=4))
37+
logger.debug(ast.dump(tree, indent=4))
3438

3539
bpf_chunks = find_bpf_chunks(tree)
3640
for func_node in bpf_chunks:
37-
print(f"Found BPF function/struct: {func_node.name}")
41+
logger.info(f"Found BPF function/struct: {func_node.name}")
3842

3943
structs_sym_tab = structs_proc(tree, module, bpf_chunks)
4044
map_sym_tab = maps_proc(tree, module, bpf_chunks)
@@ -44,7 +48,10 @@ def processor(source_code, filename, module):
4448
globals_processing(tree, module)
4549

4650

47-
def compile_to_ir(filename: str, output: str):
51+
def compile_to_ir(filename: str, output: str, loglevel=logging.WARNING):
52+
logging.basicConfig(
53+
level=loglevel, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
54+
)
4855
with open(filename) as f:
4956
source = f.read()
5057

@@ -53,33 +60,17 @@ def compile_to_ir(filename: str, output: str):
5360
module.triple = "bpf"
5461

5562
if not hasattr(module, "_debug_compile_unit"):
56-
module._file_metadata = module.add_debug_info(
57-
"DIFile",
58-
{ # type: ignore
59-
"filename": filename,
60-
"directory": os.path.dirname(filename),
61-
},
62-
)
63-
64-
module._debug_compile_unit = module.add_debug_info(
65-
"DICompileUnit",
66-
{ # type: ignore
67-
"language": DW_LANG_C11,
68-
"file": module._file_metadata, # type: ignore
69-
"producer": f"PythonBPF {VERSION}",
70-
"isOptimized": True, # TODO: This is probably not true
71-
# TODO: add a global field here that keeps track of all the globals. Works without it, but I think it might
72-
# be required for kprobes.
73-
"runtimeVersion": 0,
74-
"emissionKind": 1,
75-
"splitDebugInlining": False,
76-
"nameTableKind": 0,
77-
},
78-
is_distinct=True,
63+
debug_generator = DebugInfoGenerator(module)
64+
debug_generator.generate_file_metadata(filename, os.path.dirname(filename))
65+
debug_generator.generate_debug_cu(
66+
DW_LANG_C11,
67+
f"PythonBPF {VERSION}",
68+
True, # TODO: This is probably not true
69+
# TODO: add a global field here that keeps track of all the globals. Works without it, but I think it might
70+
# be required for kprobes.
71+
True,
7972
)
8073

81-
module.add_named_metadata("llvm.dbg.cu", module._debug_compile_unit) # type: ignore
82-
8374
processor(source, filename, module)
8475

8576
wchar_size = module.add_metadata(
@@ -121,7 +112,7 @@ def compile_to_ir(filename: str, output: str):
121112

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

124-
print(f"IR written to {output}")
115+
logger.info(f"IR written to {output}")
125116
with open(output, "w") as f:
126117
f.write(f'source_filename = "{filename}"\n')
127118
f.write(str(module))
@@ -130,7 +121,7 @@ def compile_to_ir(filename: str, output: str):
130121
return output
131122

132123

133-
def compile() -> bool:
124+
def compile(loglevel=logging.WARNING) -> bool:
134125
# Look one level up the stack to the caller of this function
135126
caller_frame = inspect.stack()[1]
136127
caller_file = Path(caller_frame.filename).resolve()
@@ -139,7 +130,9 @@ def compile() -> bool:
139130
o_file = caller_file.with_suffix(".o")
140131

141132
success = True
142-
success = compile_to_ir(str(caller_file), str(ll_file)) and success
133+
success = (
134+
compile_to_ir(str(caller_file), str(ll_file), loglevel=loglevel) and success
135+
)
143136

144137
success = bool(
145138
subprocess.run(
@@ -157,11 +150,11 @@ def compile() -> bool:
157150
and success
158151
)
159152

160-
print(f"Object written to {o_file}")
153+
logger.info(f"Object written to {o_file}")
161154
return success
162155

163156

164-
def BPF() -> BpfProgram:
157+
def BPF(loglevel=logging.WARNING) -> BpfProgram:
165158
caller_frame = inspect.stack()[1]
166159
src = inspect.getsource(caller_frame.frame)
167160
with tempfile.NamedTemporaryFile(
@@ -174,7 +167,7 @@ def BPF() -> BpfProgram:
174167
f.write(src)
175168
f.flush()
176169
source = f.name
177-
compile_to_ir(source, str(inter.name))
170+
compile_to_ir(source, str(inter.name), loglevel=loglevel)
178171
subprocess.run(
179172
[
180173
"llc",

pythonbpf/debuginfo/debug_info_generator.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,34 @@ def __init__(self, module):
1212
self.module = module
1313
self._type_cache = {} # Cache for common debug types
1414

15+
def generate_file_metadata(self, filename, dirname):
16+
self.module._file_metadata = self.module.add_debug_info(
17+
"DIFile",
18+
{ # type: ignore
19+
"filename": filename,
20+
"directory": dirname,
21+
},
22+
)
23+
24+
def generate_debug_cu(
25+
self, language, producer: str, is_optimized: bool, is_distinct: bool
26+
):
27+
self.module._debug_compile_unit = self.module.add_debug_info(
28+
"DICompileUnit",
29+
{ # type: ignore
30+
"language": language,
31+
"file": self.module._file_metadata, # type: ignore
32+
"producer": producer,
33+
"isOptimized": is_optimized,
34+
"runtimeVersion": 0,
35+
"emissionKind": 1,
36+
"splitDebugInlining": False,
37+
"nameTableKind": 0,
38+
},
39+
is_distinct=is_distinct,
40+
)
41+
self.module.add_named_metadata("llvm.dbg.cu", self.module._debug_compile_unit) # type: ignore
42+
1543
def get_basic_type(self, name: str, size: int, encoding: int) -> Any:
1644
"""Get or create a basic type with caching"""
1745
key = (name, size, encoding)

pythonbpf/expr_pass.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import ast
22
from llvmlite import ir
3+
from logging import Logger
4+
import logging
5+
6+
logger: Logger = logging.getLogger(__name__)
37

48

59
def eval_expr(
@@ -11,22 +15,22 @@ def eval_expr(
1115
map_sym_tab,
1216
structs_sym_tab=None,
1317
):
14-
print(f"Evaluating expression: {ast.dump(expr)}")
18+
logger.info(f"Evaluating expression: {ast.dump(expr)}")
1519
if isinstance(expr, ast.Name):
1620
if expr.id in local_sym_tab:
1721
var = local_sym_tab[expr.id].var
1822
val = builder.load(var)
1923
return val, local_sym_tab[expr.id].ir_type # return value and type
2024
else:
21-
print(f"Undefined variable {expr.id}")
25+
logger.info(f"Undefined variable {expr.id}")
2226
return None
2327
elif isinstance(expr, ast.Constant):
2428
if isinstance(expr.value, int):
2529
return ir.Constant(ir.IntType(64), expr.value), ir.IntType(64)
2630
elif isinstance(expr.value, bool):
2731
return ir.Constant(ir.IntType(1), int(expr.value)), ir.IntType(1)
2832
else:
29-
print("Unsupported constant type")
33+
logger.info("Unsupported constant type")
3034
return None
3135
elif isinstance(expr, ast.Call):
3236
# delayed import to avoid circular dependency
@@ -35,26 +39,26 @@ def eval_expr(
3539
if isinstance(expr.func, ast.Name):
3640
# check deref
3741
if expr.func.id == "deref":
38-
print(f"Handling deref {ast.dump(expr)}")
42+
logger.info(f"Handling deref {ast.dump(expr)}")
3943
if len(expr.args) != 1:
40-
print("deref takes exactly one argument")
44+
logger.info("deref takes exactly one argument")
4145
return None
4246
arg = expr.args[0]
4347
if (
4448
isinstance(arg, ast.Call)
4549
and isinstance(arg.func, ast.Name)
4650
and arg.func.id == "deref"
4751
):
48-
print("Multiple deref not supported")
52+
logger.info("Multiple deref not supported")
4953
return None
5054
if isinstance(arg, ast.Name):
5155
if arg.id in local_sym_tab:
5256
arg = local_sym_tab[arg.id].var
5357
else:
54-
print(f"Undefined variable {arg.id}")
58+
logger.info(f"Undefined variable {arg.id}")
5559
return None
5660
if arg is None:
57-
print("Failed to evaluate deref argument")
61+
logger.info("Failed to evaluate deref argument")
5862
return None
5963
# Since we are handling only name case, directly take type from sym tab
6064
val = builder.load(arg)
@@ -72,7 +76,7 @@ def eval_expr(
7276
structs_sym_tab,
7377
)
7478
elif isinstance(expr.func, ast.Attribute):
75-
print(f"Handling method call: {ast.dump(expr.func)}")
79+
logger.info(f"Handling method call: {ast.dump(expr.func)}")
7680
if isinstance(expr.func.value, ast.Call) and isinstance(
7781
expr.func.value.func, ast.Name
7882
):
@@ -107,15 +111,15 @@ def eval_expr(
107111
attr_name = expr.attr
108112
if var_name in local_sym_tab:
109113
var_ptr, var_type, var_metadata = local_sym_tab[var_name]
110-
print(f"Loading attribute {attr_name} from variable {var_name}")
111-
print(f"Variable type: {var_type}, Variable ptr: {var_ptr}")
114+
logger.info(f"Loading attribute {attr_name} from variable {var_name}")
115+
logger.info(f"Variable type: {var_type}, Variable ptr: {var_ptr}")
112116
metadata = structs_sym_tab[var_metadata]
113117
if attr_name in metadata.fields:
114118
gep = metadata.gep(builder, var_ptr, attr_name)
115119
val = builder.load(gep)
116120
field_type = metadata.field_type(attr_name)
117121
return val, field_type
118-
print("Unsupported expression evaluation")
122+
logger.info("Unsupported expression evaluation")
119123
return None
120124

121125

@@ -129,7 +133,7 @@ def handle_expr(
129133
structs_sym_tab,
130134
):
131135
"""Handle expression statements in the function body."""
132-
print(f"Handling expression: {ast.dump(expr)}")
136+
logger.info(f"Handling expression: {ast.dump(expr)}")
133137
call = expr.value
134138
if isinstance(call, ast.Call):
135139
eval_expr(
@@ -142,4 +146,4 @@ def handle_expr(
142146
structs_sym_tab,
143147
)
144148
else:
145-
print("Unsupported expression type")
149+
logger.info("Unsupported expression type")

0 commit comments

Comments
 (0)