Skip to content

Commit 32dc8e6

Browse files
Merge pull request #21 from pythonbpf/globals
Adds support for globals SO...... *I'm not merging this because it's complete, but because I don't want it to diverge from master too much. *Stuff I still need to complete: -> Structs and eval expressions in these globals. -> handling the global keyword. -> assigning back to the global and reading from inside a function. -> Basically, `global` keyword in Python is used to write only and reading can be done directly without declaring as global as a direct assign without global declaration is going to diverge from Python. -> The above logic is going to be supported by `global_sym_tab` generated using the new order of passes that we are doing. -> This needs to be fixed and done ASAP to avoid conflicts. so yes, im gonna do it soon.
2 parents 8485460 + 8e3942d commit 32dc8e6

File tree

5 files changed

+278
-8
lines changed

5 files changed

+278
-8
lines changed

pythonbpf/codegen.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
from .functions import func_proc
55
from .maps import maps_proc
66
from .structs import structs_proc
7-
from .globals_pass import globals_processing
7+
from .globals_pass import (
8+
globals_list_creation,
9+
globals_processing,
10+
populate_global_symbol_table,
11+
)
812
from .debuginfo import DW_LANG_C11, DwarfBehaviorEnum, DebugInfoGenerator
913
import os
1014
import subprocess
@@ -40,12 +44,15 @@ def processor(source_code, filename, module):
4044
for func_node in bpf_chunks:
4145
logger.info(f"Found BPF function/struct: {func_node.name}")
4246

47+
populate_global_symbol_table(tree, module)
48+
license_processing(tree, module)
49+
globals_processing(tree, module)
50+
4351
structs_sym_tab = structs_proc(tree, module, bpf_chunks)
4452
map_sym_tab = maps_proc(tree, module, bpf_chunks)
4553
func_proc(tree, module, bpf_chunks, map_sym_tab, structs_sym_tab)
4654

47-
license_processing(tree, module)
48-
globals_processing(tree, module)
55+
globals_list_creation(tree, module)
4956

5057

5158
def compile_to_ir(filename: str, output: str, loglevel=logging.INFO):

pythonbpf/globals_pass.py

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,121 @@
11
from llvmlite import ir
22
import ast
33

4+
from logging import Logger
5+
import logging
6+
from .type_deducer import ctypes_to_ir
47

5-
def emit_globals(module: ir.Module, names: list[str]):
8+
logger: Logger = logging.getLogger(__name__)
9+
10+
# TODO: this is going to be a huge fuck of a headache in the future.
11+
global_sym_tab = []
12+
13+
14+
def populate_global_symbol_table(tree, module: ir.Module):
15+
for node in tree.body:
16+
if isinstance(node, ast.FunctionDef):
17+
for dec in node.decorator_list:
18+
if (
19+
isinstance(dec, ast.Call)
20+
and isinstance(dec.func, ast.Name)
21+
and dec.func.id == "section"
22+
and len(dec.args) == 1
23+
and isinstance(dec.args[0], ast.Constant)
24+
and isinstance(dec.args[0].value, str)
25+
):
26+
global_sym_tab.append(node)
27+
elif isinstance(dec, ast.Name) and dec.id == "bpfglobal":
28+
global_sym_tab.append(node)
29+
30+
elif isinstance(dec, ast.Name) and dec.id == "map":
31+
global_sym_tab.append(node)
32+
return False
33+
34+
35+
def emit_global(module: ir.Module, node, name):
36+
logger.info(f"global identifier {name} processing")
37+
# deduce LLVM type from the annotated return
38+
if not isinstance(node.returns, ast.Name):
39+
raise ValueError(f"Unsupported return annotation {ast.dump(node.returns)}")
40+
ty = ctypes_to_ir(node.returns.id)
41+
42+
# extract the return expression
43+
# TODO: turn this return extractor into a generic function I can use everywhere.
44+
ret_stmt = node.body[0]
45+
if not isinstance(ret_stmt, ast.Return) or ret_stmt.value is None:
46+
raise ValueError(f"Global '{name}' has no valid return")
47+
48+
init_val = ret_stmt.value
49+
50+
# simple constant like "return 0"
51+
if isinstance(init_val, ast.Constant):
52+
llvm_init = ir.Constant(ty, init_val.value)
53+
54+
# variable reference like "return SOME_CONST"
55+
elif isinstance(init_val, ast.Name):
56+
# need symbol resolution here, stub as 0 for now
57+
raise ValueError(f"Name reference {init_val.id} not yet supported")
58+
59+
# constructor call like "return c_int64(0)" or dataclass(...)
60+
elif isinstance(init_val, ast.Call):
61+
if len(init_val.args) >= 1 and isinstance(init_val.args[0], ast.Constant):
62+
llvm_init = ir.Constant(ty, init_val.args[0].value)
63+
else:
64+
logger.info("Defaulting to zero as no constant argument found")
65+
llvm_init = ir.Constant(ty, 0)
66+
else:
67+
raise ValueError(f"Unsupported return expr {ast.dump(init_val)}")
68+
69+
gvar = ir.GlobalVariable(module, ty, name=name)
70+
gvar.initializer = llvm_init
71+
gvar.align = 8
72+
gvar.linkage = "dso_local"
73+
gvar.global_constant = False
74+
return gvar
75+
76+
77+
def globals_processing(tree, module):
78+
"""Process stuff decorated with @bpf and @bpfglobal except license and return the section name"""
79+
globals_sym_tab = []
80+
81+
for node in tree.body:
82+
# Skip non-assignment and non-function nodes
83+
if not (isinstance(node, ast.FunctionDef)):
84+
continue
85+
86+
# Get the name based on node type
87+
if isinstance(node, ast.FunctionDef):
88+
name = node.name
89+
else:
90+
continue
91+
92+
# Check for duplicate names
93+
if name in globals_sym_tab:
94+
raise SyntaxError(f"ERROR: Global name '{name}' previously defined")
95+
else:
96+
globals_sym_tab.append(name)
97+
98+
if isinstance(node, ast.FunctionDef) and node.name != "LICENSE":
99+
decorators = [
100+
dec.id for dec in node.decorator_list if isinstance(dec, ast.Name)
101+
]
102+
if "bpf" in decorators and "bpfglobal" in decorators:
103+
if (
104+
len(node.body) == 1
105+
and isinstance(node.body[0], ast.Return)
106+
and node.body[0].value is not None
107+
and isinstance(
108+
node.body[0].value, (ast.Constant, ast.Name, ast.Call)
109+
)
110+
):
111+
emit_global(module, node, name)
112+
else:
113+
raise SyntaxError(f"ERROR: Invalid syntax for {name} global")
114+
115+
return None
116+
117+
118+
def emit_llvm_compiler_used(module: ir.Module, names: list[str]):
6119
"""
7120
Emit the @llvm.compiler.used global given a list of function/global names.
8121
"""
@@ -24,7 +137,7 @@ def emit_globals(module: ir.Module, names: list[str]):
24137
gv.section = "llvm.metadata"
25138

26139

27-
def globals_processing(tree, module: ir.Module):
140+
def globals_list_creation(tree, module: ir.Module):
28141
collected = ["LICENSE"]
29142

30143
for node in tree.body:
@@ -40,10 +153,11 @@ def globals_processing(tree, module: ir.Module):
40153
):
41154
collected.append(node.name)
42155

43-
elif isinstance(dec, ast.Name) and dec.id == "bpfglobal":
44-
collected.append(node.name)
156+
# NOTE: all globals other than
157+
# elif isinstance(dec, ast.Name) and dec.id == "bpfglobal":
158+
# collected.append(node.name)
45159

46160
elif isinstance(dec, ast.Name) and dec.id == "map":
47161
collected.append(node.name)
48162

49-
emit_globals(module, collected)
163+
emit_llvm_compiler_used(module, collected)

tests/c-form/globals.bpf.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2+
#include <linux/bpf.h>
3+
#include <bpf/bpf_helpers.h>
4+
#include <bpf/bpf_tracing.h>
5+
#include <linux/types.h>
6+
7+
struct test_struct {
8+
__u64 a;
9+
__u64 b;
10+
};
11+
12+
struct test_struct w = {};
13+
volatile __u64 prev_time = 0;
14+
15+
SEC("tracepoint/syscalls/sys_enter_execve")
16+
int trace_execve(void *ctx)
17+
{
18+
bpf_printk("previous %ul now %ul", w.b, w.a);
19+
__u64 ts = bpf_ktime_get_ns();
20+
bpf_printk("prev %ul now %ul", prev_time, ts);
21+
w.a = ts;
22+
w.b = prev_time;
23+
prev_time = ts;
24+
return 0;
25+
}
26+
27+
char LICENSE[] SEC("license") = "GPL";

tests/failing_tests/globals.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import logging
2+
3+
from pythonbpf import compile, bpf, section, bpfglobal, compile_to_ir
4+
from ctypes import c_void_p, c_int64, c_int32
5+
6+
@bpf
7+
@bpfglobal
8+
def somevalue() -> c_int32:
9+
return c_int32(42)
10+
11+
@bpf
12+
@bpfglobal
13+
def somevalue2() -> c_int64:
14+
return c_int64(69)
15+
16+
@bpf
17+
@bpfglobal
18+
def somevalue1() -> c_int32:
19+
return c_int32(42)
20+
21+
22+
# --- Passing examples ---
23+
24+
# Simple constant return
25+
@bpf
26+
@bpfglobal
27+
def g1() -> c_int64:
28+
return c_int64(42)
29+
30+
# Constructor with one constant argument
31+
@bpf
32+
@bpfglobal
33+
def g2() -> c_int64:
34+
return c_int64(69)
35+
36+
37+
# --- Failing examples ---
38+
39+
# No return annotation
40+
# @bpf
41+
# @bpfglobal
42+
# def g3():
43+
# return 42
44+
45+
# Return annotation is complex
46+
# @bpf
47+
# @bpfglobal
48+
# def g4() -> List[int]:
49+
# return []
50+
51+
# # Return is missing
52+
# @bpf
53+
# @bpfglobal
54+
# def g5() -> c_int64:
55+
# pass
56+
57+
# # Return is a variable reference
58+
# #TODO: maybe fix this sometime later. It defaults to 0
59+
# CONST = 5
60+
# @bpf
61+
# @bpfglobal
62+
# def g6() -> c_int64:
63+
# return c_int64(CONST)
64+
65+
# Constructor with multiple args
66+
#TODO: this is not working. should it work ?
67+
@bpf
68+
@bpfglobal
69+
def g7() -> c_int64:
70+
return c_int64(1)
71+
72+
# Dataclass call
73+
#TODO: fails with dataclass
74+
# @dataclass
75+
# class Point:
76+
# x: c_int64
77+
# y: c_int64
78+
79+
# @bpf
80+
# @bpfglobal
81+
# def g8() -> Point:
82+
# return Point(1, 2)
83+
84+
85+
@bpf
86+
@section("tracepoint/syscalls/sys_enter_execve")
87+
def sometag(ctx: c_void_p) -> c_int64:
88+
print("test")
89+
global somevalue
90+
somevalue = 2
91+
print(f"{somevalue}")
92+
return c_int64(1)
93+
94+
@bpf
95+
@bpfglobal
96+
def LICENSE() -> str:
97+
return "GPL"
98+
99+
100+
compile_to_ir("globals.py", "globals.ll", loglevel=logging.INFO)
101+
compile()
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import logging
2+
3+
from pythonbpf import compile, bpf, section, bpfglobal, compile_to_ir
4+
from ctypes import c_void_p, c_int64
5+
6+
# This should not pass as somevalue is not declared at all.
7+
@bpf
8+
@section("tracepoint/syscalls/sys_enter_execve")
9+
def sometag(ctx: c_void_p) -> c_int64:
10+
print("test")
11+
print(f"{somevalue}") # noqa: F821
12+
return c_int64(1)
13+
14+
@bpf
15+
@bpfglobal
16+
def LICENSE() -> str:
17+
return "GPL"
18+
19+
20+
compile_to_ir("globals.py", "globals.ll", loglevel=logging.INFO)
21+
compile()

0 commit comments

Comments
 (0)