Skip to content

Commit c423cc6

Browse files
add vmlinux.py transpiler from experiment repository
Signed-off-by: varun-r-mallya <[email protected]>
1 parent 552cd35 commit c423cc6

File tree

1 file changed

+256
-0
lines changed

1 file changed

+256
-0
lines changed

tools/vmlinux-gen.py

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
#!/usr/bin/env python3
2+
"""
3+
BTF to Python ctypes Converter
4+
Converts Linux kernel BTF (BPF Type Format) to Python ctypes definitions.
5+
6+
This tool automates the process of:
7+
1. Dumping BTF from vmlinux
8+
2. Preprocessing enum definitions
9+
3. Running C preprocessor
10+
4. Converting to Python ctypes using clang2py
11+
5. Post-processing the output
12+
13+
Requirements:
14+
- bpftool
15+
- clang
16+
- ctypeslib2 (pip install ctypeslib2)
17+
"""
18+
19+
import argparse
20+
import os
21+
import re
22+
import subprocess
23+
import sys
24+
import tempfile
25+
26+
27+
class BTFConverter:
28+
def __init__(self, btf_source="/sys/kernel/btf/vmlinux", output_file="vmlinux.py",
29+
keep_intermediate=False, verbose=False):
30+
self.btf_source = btf_source
31+
self.output_file = output_file
32+
self.keep_intermediate = keep_intermediate
33+
self.verbose = verbose
34+
self.temp_dir = tempfile.mkdtemp() if not keep_intermediate else "."
35+
36+
def log(self, message):
37+
"""Print message if verbose mode is enabled."""
38+
if self.verbose:
39+
print(f"[*] {message}")
40+
41+
def run_command(self, cmd, description):
42+
"""Run a shell command and handle errors."""
43+
self.log(f"{description}...")
44+
try:
45+
result = subprocess.run(
46+
cmd,
47+
shell=True,
48+
check=True,
49+
capture_output=True,
50+
text=True
51+
)
52+
if self.verbose and result.stdout:
53+
print(result.stdout)
54+
return result
55+
except subprocess.CalledProcessError as e:
56+
print(f"Error during {description}:", file=sys.stderr)
57+
print(e.stderr, file=sys.stderr)
58+
sys.exit(1)
59+
60+
def step1_dump_btf(self):
61+
"""Step 1: Dump BTF from vmlinux."""
62+
vmlinux_h = os.path.join(self.temp_dir, "vmlinux.h")
63+
cmd = f"bpftool btf dump file {self.btf_source} format c > {vmlinux_h}"
64+
self.run_command(cmd, "Dumping BTF from vmlinux")
65+
return vmlinux_h
66+
67+
def step2_preprocess_enums(self, input_file):
68+
"""Step 1.5: Preprocess enum definitions."""
69+
self.log("Preprocessing enum definitions...")
70+
71+
with open(input_file, 'r') as f:
72+
original_code = f.read()
73+
74+
# Extract anonymous enums
75+
enums = re.findall(
76+
r'(?<!typedef\s)(enum\s*\{[^}]*\})\s*(\w+)\s*(?::\s*\d+)?\s*;',
77+
original_code
78+
)
79+
enum_defs = [enum_block + ';' for enum_block, _ in enums]
80+
81+
# Replace anonymous enums with int declarations
82+
processed_code = re.sub(
83+
r'(?<!typedef\s)enum\s*\{[^}]*\}\s*(\w+)\s*(?::\s*\d+)?\s*;',
84+
r'int \1;',
85+
original_code
86+
)
87+
88+
# Prepend enum definitions
89+
if enum_defs:
90+
enum_text = '\n'.join(enum_defs) + '\n\n'
91+
processed_code = enum_text + processed_code
92+
93+
output_file = os.path.join(self.temp_dir, "vmlinux_processed.h")
94+
with open(output_file, 'w') as f:
95+
f.write(processed_code)
96+
97+
return output_file
98+
99+
def step3_run_preprocessor(self, input_file):
100+
"""Step 2: Run C preprocessor."""
101+
output_file = os.path.join(self.temp_dir, "vmlinux.i")
102+
cmd = f"clang -E {input_file} > {output_file}"
103+
self.run_command(cmd, "Running C preprocessor")
104+
return output_file
105+
106+
def step4_convert_to_ctypes(self, input_file):
107+
"""Step 3: Convert to Python ctypes using clang2py."""
108+
output_file = os.path.join(self.temp_dir, "vmlinux_raw.py")
109+
cmd = (
110+
f"clang2py {input_file} -o {output_file} "
111+
f"--clang-args=\"-fno-ms-extensions -I/usr/include -I/usr/include/linux\""
112+
)
113+
self.run_command(cmd, "Converting to Python ctypes")
114+
return output_file
115+
116+
def step5_postprocess(self, input_file):
117+
"""Step 4: Post-process the generated Python file."""
118+
self.log("Post-processing Python ctypes definitions...")
119+
120+
with open(input_file, "r") as f:
121+
data = f.read()
122+
123+
# Remove lines like ('_45', ctypes.c_int64, 0)
124+
data = re.sub(r"\('_[0-9]+',\s*ctypes\.[a-zA-Z0-9_]+,\s*0\),?\s*\n?", "", data)
125+
126+
# Replace ('_20', ctypes.c_uint64, 64) → ('_20', ctypes.c_uint64)
127+
data = re.sub(r"\('(_[0-9]+)',\s*(ctypes\.[a-zA-Z0-9_]+),\s*[0-9]+\)", r"('\1', \2)", data)
128+
129+
# Replace ('_20', ctypes.c_char, 8) with ('_20', ctypes.c_uint8, 8)
130+
data = re.sub(
131+
r"(ctypes\.c_char)(\s*,\s*\d+\))",
132+
r"ctypes.c_uint8\2",
133+
data
134+
)
135+
136+
# Remove ctypes. prefix from invalid entries
137+
invalid_ctypes = ["bpf_iter_state", "_cache_type", "fs_context_purpose"]
138+
for name in invalid_ctypes:
139+
data = re.sub(rf"\bctypes\.{name}\b", name, data)
140+
141+
with open(self.output_file, "w") as f:
142+
f.write(data)
143+
144+
self.log(f"Saved final output to {self.output_file}")
145+
146+
def cleanup(self):
147+
"""Remove temporary files if not keeping them."""
148+
if not self.keep_intermediate and self.temp_dir != ".":
149+
self.log(f"Cleaning up temporary directory: {self.temp_dir}")
150+
import shutil
151+
shutil.rmtree(self.temp_dir, ignore_errors=True)
152+
153+
def convert(self):
154+
"""Run the complete conversion pipeline."""
155+
try:
156+
self.log("Starting BTF to Python ctypes conversion...")
157+
158+
# Check dependencies
159+
self.check_dependencies()
160+
161+
# Run conversion pipeline
162+
vmlinux_h = self.step1_dump_btf()
163+
vmlinux_processed_h = self.step2_preprocess_enums(vmlinux_h)
164+
vmlinux_i = self.step3_run_preprocessor(vmlinux_processed_h)
165+
vmlinux_raw_py = self.step4_convert_to_ctypes(vmlinux_i)
166+
self.step5_postprocess(vmlinux_raw_py)
167+
168+
print(f"\n✓ Conversion complete! Output saved to: {self.output_file}")
169+
170+
except Exception as e:
171+
print(f"\n✗ Error during conversion: {e}", file=sys.stderr)
172+
sys.exit(1)
173+
finally:
174+
self.cleanup()
175+
176+
def check_dependencies(self):
177+
"""Check if required tools are available."""
178+
self.log("Checking dependencies...")
179+
180+
dependencies = {
181+
"bpftool": "bpftool --version",
182+
"clang": "clang --version",
183+
"clang2py": "clang2py --version"
184+
}
185+
186+
missing = []
187+
for tool, cmd in dependencies.items():
188+
try:
189+
subprocess.run(
190+
cmd,
191+
shell=True,
192+
check=True,
193+
capture_output=True
194+
)
195+
except subprocess.CalledProcessError:
196+
missing.append(tool)
197+
198+
if missing:
199+
print("Error: Missing required dependencies:", file=sys.stderr)
200+
for tool in missing:
201+
print(f" - {tool}", file=sys.stderr)
202+
if "clang2py" in missing:
203+
print("\nInstall ctypeslib2: pip install ctypeslib2", file=sys.stderr)
204+
sys.exit(1)
205+
206+
207+
def main():
208+
parser = argparse.ArgumentParser(
209+
description="Convert Linux kernel BTF to Python ctypes definitions",
210+
formatter_class=argparse.RawDescriptionHelpFormatter,
211+
epilog="""
212+
Examples:
213+
%(prog)s
214+
%(prog)s -o kernel_types.py
215+
%(prog)s --btf-source /sys/kernel/btf/custom_module -k -v
216+
"""
217+
)
218+
219+
parser.add_argument(
220+
"--btf-source",
221+
default="/sys/kernel/btf/vmlinux",
222+
help="Path to BTF source (default: /sys/kernel/btf/vmlinux)"
223+
)
224+
225+
parser.add_argument(
226+
"-o", "--output",
227+
default="vmlinux.py",
228+
help="Output Python file (default: vmlinux.py)"
229+
)
230+
231+
parser.add_argument(
232+
"-k", "--keep-intermediate",
233+
action="store_true",
234+
help="Keep intermediate files (vmlinux.h, vmlinux_processed.h, etc.)"
235+
)
236+
237+
parser.add_argument(
238+
"-v", "--verbose",
239+
action="store_true",
240+
help="Enable verbose output"
241+
)
242+
243+
args = parser.parse_args()
244+
245+
converter = BTFConverter(
246+
btf_source=args.btf_source,
247+
output_file=args.output,
248+
keep_intermediate=args.keep_intermediate,
249+
verbose=args.verbose
250+
)
251+
252+
converter.convert()
253+
254+
255+
if __name__ == "__main__":
256+
main()

0 commit comments

Comments
 (0)