Skip to content

Commit a273604

Browse files
complete vmlinux transpiler.
TODO: struct_kioctx for x86_64 vmlinux.h has anonymous structs that refused to transpile well, so an extra rule has been written to make only the structs of that external. Fix this in the future.
1 parent c423cc6 commit a273604

File tree

3 files changed

+163
-4
lines changed

3 files changed

+163
-4
lines changed

examples/kprobes.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from pythonbpf import bpf, section, bpfglobal, BPF
2+
from ctypes import c_void_p, c_int64
3+
4+
5+
@bpf
6+
@section("kretprobe/do_unlinkat")
7+
def hello_world(ctx: c_void_p) -> c_int64:
8+
print("Hello, World!")
9+
return c_int64(0)
10+
11+
@bpf
12+
@section("kprobe/do_unlinkat")
13+
def hello_world(ctx: c_void_p) -> c_int64:
14+
print("Hello, World!")
15+
return c_int64(0)
16+
17+
@bpf
18+
@bpfglobal
19+
def LICENSE() -> str:
20+
return "GPL"
21+
22+
23+
b = BPF()
24+
b.load_and_attach()
25+
while True:
26+
print("running")
27+
# Now cat /sys/kernel/debug/tracing/trace_pipe to see results of unlink kprobe.

tests/c-form/kprobe.bpf.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include "vmlinux.h"
2+
#include <bpf/bpf_helpers.h>
3+
#include <bpf/bpf_tracing.h>
4+
5+
char LICENSE[] SEC("license") = "Dual BSD/GPL";
6+
7+
SEC("kprobe/do_unlinkat")
8+
int kprobe_execve(struct pt_regs *ctx)
9+
{
10+
bpf_printk("unlinkat created");
11+
return 0;
12+
}
13+
14+
SEC("kretprobe/do_unlinkat")
15+
int kretprobe_execve(struct pt_regs *ctx)
16+
{
17+
bpf_printk("unlinkat returned\n");
18+
return 0;
19+
}

tools/vmlinux-gen.py

Lines changed: 117 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
This tool automates the process of:
77
1. Dumping BTF from vmlinux
88
2. Preprocessing enum definitions
9-
3. Running C preprocessor
10-
4. Converting to Python ctypes using clang2py
11-
5. Post-processing the output
9+
3. Processing struct kioctx to extract anonymous nested structs
10+
4. Running C preprocessor
11+
5. Converting to Python ctypes using clang2py
12+
6. Post-processing the output
1213
1314
Requirements:
1415
- bpftool
@@ -96,6 +97,115 @@ def step2_preprocess_enums(self, input_file):
9697

9798
return output_file
9899

100+
def step2_5_process_kioctx(self, input_file):
101+
#TODO: this is a very bad bug and design decision. A single struct has an issue mostly.
102+
"""Step 2.5: Process struct kioctx to extract nested anonymous structs."""
103+
self.log("Processing struct kioctx nested structs...")
104+
105+
with open(input_file, 'r') as f:
106+
content = f.read()
107+
108+
# Pattern to match struct kioctx with its full body (handles multiple nesting levels)
109+
kioctx_pattern = r'struct\s+kioctx\s*\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}\s*;'
110+
111+
def process_kioctx_replacement(match):
112+
full_struct = match.group(0)
113+
self.log(f"Found struct kioctx, length: {len(full_struct)} chars")
114+
115+
# Extract the struct body (everything between outermost { and })
116+
body_match = re.search(r'struct\s+kioctx\s*\{(.*)\}\s*;', full_struct, re.DOTALL)
117+
if not body_match:
118+
return full_struct
119+
120+
body = body_match.group(1)
121+
122+
# Find all anonymous structs within the body
123+
# Pattern: struct { ... } followed by ; (not a member name)
124+
anon_struct_pattern = r'struct\s*\{[^}]*\}'
125+
126+
anon_structs = []
127+
anon_counter = 4 # Start from 4, counting down to 1
128+
129+
def replace_anonymous_struct(m):
130+
nonlocal anon_counter
131+
anon_struct_content = m.group(0)
132+
133+
# Extract the body of the anonymous struct
134+
anon_body_match = re.search(r'struct\s*\{(.*)\}', anon_struct_content, re.DOTALL)
135+
if not anon_body_match:
136+
return anon_struct_content
137+
138+
anon_body = anon_body_match.group(1)
139+
140+
# Create the named struct definition
141+
anon_name = f"__anon{anon_counter}"
142+
member_name = f"a{anon_counter}"
143+
144+
# Store the struct definition
145+
anon_structs.append(f"struct {anon_name} {{{anon_body}}};")
146+
147+
anon_counter -= 1
148+
149+
# Return the member declaration
150+
return f"struct {anon_name} {member_name}"
151+
152+
# Process the body, finding and replacing anonymous structs
153+
# We need to be careful to only match anonymous structs followed by ;
154+
processed_body = body
155+
156+
# Find all occurrences and process them
157+
pattern_with_semicolon = r'struct\s*\{([^}]*)\}\s*;'
158+
matches = list(re.finditer(pattern_with_semicolon, body, re.DOTALL))
159+
160+
if not matches:
161+
self.log("No anonymous structs found in kioctx")
162+
return full_struct
163+
164+
self.log(f"Found {len(matches)} anonymous struct(s)")
165+
166+
# Process in reverse order to maintain string positions
167+
for match in reversed(matches):
168+
anon_struct_content = match.group(1)
169+
start_pos = match.start()
170+
end_pos = match.end()
171+
172+
# Create the named struct definition
173+
anon_name = f"__anon{anon_counter}"
174+
member_name = f"a{anon_counter}"
175+
176+
# Store the struct definition
177+
anon_structs.insert(0, f"struct {anon_name} {{{anon_struct_content}}};")
178+
179+
# Replace in the body
180+
replacement = f"struct {anon_name} {member_name};"
181+
processed_body = processed_body[:start_pos] + replacement + processed_body[end_pos:]
182+
183+
anon_counter -= 1
184+
185+
# Rebuild the complete definition
186+
if anon_structs:
187+
# Prepend the anonymous struct definitions
188+
anon_definitions = '\n'.join(anon_structs) + '\n\n'
189+
new_struct = f"struct kioctx {{{processed_body}}};"
190+
return anon_definitions + new_struct
191+
else:
192+
return full_struct
193+
194+
# Apply the transformation
195+
processed_content = re.sub(
196+
kioctx_pattern,
197+
process_kioctx_replacement,
198+
content,
199+
flags=re.DOTALL
200+
)
201+
202+
output_file = os.path.join(self.temp_dir, "vmlinux_kioctx_processed.h")
203+
with open(output_file, 'w') as f:
204+
f.write(processed_content)
205+
206+
self.log(f"Saved kioctx-processed output to {output_file}")
207+
return output_file
208+
99209
def step3_run_preprocessor(self, input_file):
100210
"""Step 2: Run C preprocessor."""
101211
output_file = os.path.join(self.temp_dir, "vmlinux.i")
@@ -161,14 +271,17 @@ def convert(self):
161271
# Run conversion pipeline
162272
vmlinux_h = self.step1_dump_btf()
163273
vmlinux_processed_h = self.step2_preprocess_enums(vmlinux_h)
164-
vmlinux_i = self.step3_run_preprocessor(vmlinux_processed_h)
274+
vmlinux_kioctx_h = self.step2_5_process_kioctx(vmlinux_processed_h)
275+
vmlinux_i = self.step3_run_preprocessor(vmlinux_kioctx_h)
165276
vmlinux_raw_py = self.step4_convert_to_ctypes(vmlinux_i)
166277
self.step5_postprocess(vmlinux_raw_py)
167278

168279
print(f"\n✓ Conversion complete! Output saved to: {self.output_file}")
169280

170281
except Exception as e:
171282
print(f"\n✗ Error during conversion: {e}", file=sys.stderr)
283+
import traceback
284+
traceback.print_exc()
172285
sys.exit(1)
173286
finally:
174287
self.cleanup()

0 commit comments

Comments
 (0)