Skip to content

Commit c9b17a9

Browse files
committed
stage
Signed-off-by: Ruoqing He <[email protected]>
1 parent d871f25 commit c9b17a9

File tree

5 files changed

+268
-95
lines changed

5 files changed

+268
-95
lines changed

scripts/lib/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Supported architectures (arch used in kernel)
2+
SUPPORT_ARCHS = ["arm64", "x86_64", "riscv"]
3+
4+
# Map arch used in linux kernel to arch understandable for Rust
5+
MAP_RUST_ARCH = {"arm64": "aarch64", "x86_64": "x86_64", "riscv": "riscv64"}

scripts/lib/kernel_source.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,42 @@
77
import requests
88
import subprocess
99
import tempfile
10+
from lib import SUPPORT_ARCHS
1011

1112
KERNEL_ORG_CDN = "https://cdn.kernel.org/pub/linux/kernel"
1213

1314

15+
def prepare_source(args):
16+
check_kernel_version(args.version)
17+
18+
# Create `temp_dir` under `/tmp`
19+
temp_dir = create_temp_dir(args.version)
20+
21+
# Download kernel tarball from https://cdn.kernel.org/
22+
tarball = download_kernel(args.version, temp_dir)
23+
24+
# Extract kernel source
25+
src_dir = extract_kernel(tarball, temp_dir)
26+
27+
# If arch is not provided, install headers for all supported archs
28+
if args.arch is None:
29+
for arch in SUPPORT_ARCHS:
30+
installed_header_path = install_headers(
31+
src_dir=src_dir,
32+
arch=arch,
33+
install_path=args.install_path,
34+
)
35+
else:
36+
installed_header_path = install_headers(
37+
src_dir=src_dir,
38+
arch=args.arch,
39+
install_path=args.install_path,
40+
)
41+
42+
print(f"\nSuccessfully installed kernel headers to {installed_header_path}")
43+
return installed_header_path
44+
45+
1446
def check_kernel_version(version):
1547
"""
1648
Validate if the input kernel version exists in remote. Supports both X.Y
@@ -96,14 +128,17 @@ def extract_kernel(tarball_path, temp_dir):
96128

97129

98130
def install_headers(src_dir, arch, install_path):
99-
parent_dir = os.path.dirname(src_dir)
131+
# If install_path is not provided, install to parent directory of src_dir to
132+
# prevent messing up with extracted kernel source code
100133
if install_path is None:
101-
install_path = os.path.join(parent_dir, f"{arch}_headers")
134+
install_path = os.path.dirname(src_dir)
102135

103136
try:
104137
os.makedirs(install_path, exist_ok=True)
105138

106-
abs_install_path = os.path.abspath(install_path)
139+
abs_install_path = os.path.abspath(
140+
os.path.join(install_path, f"{arch}_headers")
141+
)
107142
print(f"Installing to {abs_install_path}")
108143
result = subprocess.run(
109144
[

scripts/lib/kvm_bindings.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import re
2+
import os
3+
import subprocess
4+
from pathlib import Path
5+
from lib.kernel_source import prepare_source
6+
from lib import MAP_RUST_ARCH, SUPPORT_ARCHS
7+
8+
9+
KVM_BINDINGS_DIR = "kvm-bindings/src/"
10+
11+
12+
def generate_kvm_bindings(args):
13+
installed_header_path = prepare_source(args)
14+
15+
# If arch is not provided, install headers for all supported archs
16+
if args.arch is None:
17+
for arch in SUPPORT_ARCHS:
18+
generate_bindings(
19+
installed_header_path, arch, args.attribute, args.output_path
20+
)
21+
else:
22+
generate_bindings(
23+
installed_header_path, args.arch, args.attribute, args.output_path
24+
)
25+
26+
27+
def generate_bindings(
28+
installed_header_path: str, arch: str, attribute: str, output_path: str
29+
):
30+
"""
31+
Generate bindings with source directory support
32+
33+
:param src_dir: Root source directory containing include/ and kvm-bindings/
34+
:param arch: Target architecture (e.g. aarch64, riscv64, x86_64)
35+
:param attribute: Attribute template for custom structs
36+
:raises RuntimeError: If any generation step fails
37+
"""
38+
try:
39+
# Validate source directory structure
40+
arch_headers = os.path.join(installed_header_path, f"{arch}_headers")
41+
kvm_header = Path(os.path.join(arch_headers, f"include/linux/kvm.h"))
42+
if not kvm_header.is_file():
43+
raise FileNotFoundError(f"KVM header missing at {kvm_header}")
44+
45+
arch = MAP_RUST_ARCH[arch]
46+
structs = capture_serde(arch)
47+
if not structs:
48+
raise RuntimeError(
49+
f"No structs found for {arch}, you need to invoke this command under rustvmm/kvm repo root"
50+
)
51+
52+
# Step 2: Build bindgen command with dynamic paths
53+
base_cmd = [
54+
"bindgen",
55+
os.path.abspath(kvm_header), # Use absolute path to header
56+
"--impl-debug",
57+
"--impl-partialeq",
58+
"--with-derive-default",
59+
"--with-derive-partialeq",
60+
]
61+
62+
# Add custom attributes for each struct
63+
for struct in structs:
64+
base_cmd += ["--with-attribute-custom-struct", f"{struct}={attribute}"]
65+
66+
# Add include paths relative to source directory
67+
base_cmd += ["--", f"-I{arch_headers}/include"] # Use absolute include path
68+
69+
# Step 3: Execute command with error handling
70+
print(f"\nGenerating bindings for {arch}...")
71+
bindings = subprocess.run(
72+
base_cmd, check=True, capture_output=True, text=True, encoding="utf-8"
73+
).stdout
74+
75+
print("Successfully generated bindings")
76+
77+
# Generate architecture-specific filename
78+
output_file_path = f"{output_path}/{arch}/bindings.rs"
79+
80+
print(f"Generating to: {output_file_path}")
81+
82+
except subprocess.CalledProcessError as e:
83+
err_msg = f"Bindgen failed (code {e.returncode})"
84+
raise RuntimeError(err_msg) from e
85+
except Exception as e:
86+
raise RuntimeError(f"Generation failed: {str(e)}") from e
87+
88+
try:
89+
with open(output_file_path, "w") as f:
90+
f.write(bindings)
91+
92+
# Format with rustfmt
93+
subprocess.run(["rustfmt", output_file_path], check=True)
94+
print(f"Generation succeeded: {output_file_path}")
95+
except subprocess.CalledProcessError:
96+
raise RuntimeError("rustfmt formatting failed")
97+
except IOError as e:
98+
raise RuntimeError(f"File write error: {str(e)}")
99+
100+
101+
def capture_serde(arch: str) -> list[str]:
102+
"""
103+
Parse serde implementations for specified architecture
104+
105+
:param arch: Architecture name (e.g. aarch64, riscv64, x86_64)
106+
:return: List of found struct names
107+
:raises FileNotFoundError: When target file is missing
108+
:raises ValueError: When serde_impls block is not found
109+
"""
110+
# Build target file path
111+
target_path = Path(f"{KVM_BINDINGS_DIR}/{arch}/serialize.rs")
112+
113+
# Validate file existence
114+
if not target_path.is_file():
115+
raise FileNotFoundError(
116+
f"Serialization file not found for {arch}: {target_path}"
117+
)
118+
119+
print(f"Extracting serde structs of {arch} from: {target_path}")
120+
121+
# Read file content
122+
content = target_path.read_text(encoding="utf-8")
123+
124+
# Multi-line regex pattern to find serde_impls block
125+
pattern = re.compile(
126+
r"serde_impls!\s*\{\s*(?P<struct>.*?)\s*\}", re.DOTALL | re.MULTILINE
127+
)
128+
129+
# Extract struct list from matched block
130+
match = pattern.search(content)
131+
if not match:
132+
raise ValueError(f"No serde_impls! block found in {target_path}")
133+
134+
struct_list = match.group("struct")
135+
136+
structs = []
137+
for line in struct_list.splitlines():
138+
# Split and clean individual words
139+
for word in line.split():
140+
clean_word = word.strip().rstrip(",")
141+
if clean_word:
142+
structs.append(clean_word)
143+
144+
return structs

scripts/lib/syscall.py renamed to scripts/lib/seccompiler.py

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,39 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
import subprocess
5+
import os
56
import re
7+
from lib.kernel_source import prepare_source
8+
from lib import MAP_RUST_ARCH, SUPPORT_ARCHS
9+
from pathlib import Path
610

11+
SECCOMPILER_SYSCALL_DIR = "src/syscall_table"
712

8-
def generate_syscall_table(file_path):
9-
"""Generate syscall table from specified header file"""
10-
try:
11-
with open(file_path, "r") as f:
12-
syscalls = []
13-
pattern = re.compile(r"^#define __NR_(\w+)\s+(\d+)")
1413

15-
for line in f:
16-
line = line.strip()
17-
if line.startswith("#define __NR_"):
18-
match = pattern.match(line)
19-
if match:
20-
name = match.group(1)
21-
num = int(match.group(2))
22-
syscalls.append((name, num))
14+
def generate_seccompiler(args):
15+
installed_header_path = prepare_source(args)
2316

24-
# Sort alphabetically by syscall name
25-
syscalls.sort(key=lambda x: x[0])
26-
syscall_list = [f'("{name}", {num}),' for name, num in syscalls]
27-
return " ".join(syscall_list)
17+
# If arch is not provided, install headers for all supported archs
18+
if args.arch is None:
19+
for arch in SUPPORT_ARCHS:
20+
generate_rust_code(installed_header_path, arch, args.output_path)
21+
else:
22+
generate_rust_code(installed_header_path, args.arch, args.output_path)
2823

29-
except FileNotFoundError:
30-
raise RuntimeError(f"Header file not found: {file_path}")
31-
except Exception as e:
32-
raise RuntimeError(f"File processing failed: {str(e)}")
3324

25+
def generate_rust_code(installed_header_path: str, arch: str, output_path: str):
26+
# Generate syscall table
27+
arch_headers = os.path.join(installed_header_path, f"{arch}_headers")
28+
syscall_header = Path(os.path.join(arch_headers, f"include/asm/unistd_64.h"))
29+
if not syscall_header.is_file():
30+
raise FileNotFoundError(f"syscall headers missing at {syscall_header}")
31+
syscalls = generate_syscall_table(syscall_header)
32+
33+
arch = MAP_RUST_ARCH[arch]
34+
output_file_path = f"{output_path}/{arch}.rs"
3435

35-
def generate_rust_code(syscalls, output_path):
3636
"""Generate Rust code and format with rustfmt"""
37-
print(f"Generating to: {output_path}")
37+
print(f"Generating to: {output_file_path}")
3838
code = f"""use std::collections::HashMap;
3939
pub(crate) fn make_syscall_table() -> HashMap<&'static str, i64> {{
4040
vec![
@@ -43,13 +43,38 @@ def generate_rust_code(syscalls, output_path):
4343
}}
4444
"""
4545
try:
46-
with open(output_path, "w") as f:
46+
with open(output_file_path, "w") as f:
4747
f.write(code)
4848

4949
# Format with rustfmt
50-
subprocess.run(["rustfmt", output_path], check=True)
51-
print(f"Generation succeeded: {output_path}")
50+
subprocess.run(["rustfmt", output_file_path], check=True)
51+
print(f"Generation succeeded: {output_file_path}")
5252
except subprocess.CalledProcessError:
5353
raise RuntimeError("rustfmt formatting failed")
5454
except IOError as e:
5555
raise RuntimeError(f"File write error: {str(e)}")
56+
57+
58+
def generate_syscall_table(syscall_header_path: str):
59+
"""Generate syscall table from specified header file"""
60+
try:
61+
with open(syscall_header_path, "r") as f:
62+
syscalls = []
63+
pattern = re.compile(r"^#define __NR_(\w+)\s+(\d+)")
64+
65+
for line in f:
66+
line = line.strip()
67+
if line.startswith("#define __NR_"):
68+
match = pattern.match(line)
69+
if match:
70+
name = match.group(1)
71+
num = int(match.group(2))
72+
syscalls.append((name, num))
73+
74+
# Sort alphabetically by syscall name
75+
syscalls.sort(key=lambda x: x[0])
76+
syscall_list = [f'("{name}", {num}),' for name, num in syscalls]
77+
return " ".join(syscall_list)
78+
79+
except Exception as e:
80+
raise RuntimeError(f"File processing failed: {str(e)}")

0 commit comments

Comments
 (0)