Skip to content

Commit f3ef100

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

File tree

5 files changed

+267
-95
lines changed

5 files changed

+267
-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: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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 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+
structs = capture_serde(arch)
46+
if not structs:
47+
raise RuntimeError(
48+
f"No structs found for {arch}, you need to invoke this command under rustvmm/kvm repo root"
49+
)
50+
51+
# Step 2: Build bindgen command with dynamic paths
52+
base_cmd = [
53+
"bindgen",
54+
os.path.abspath(kvm_header), # Use absolute path to header
55+
"--impl-debug",
56+
"--impl-partialeq",
57+
"--with-derive-default",
58+
"--with-derive-partialeq",
59+
]
60+
61+
# Add custom attributes for each struct
62+
for struct in structs:
63+
base_cmd += ["--with-attribute-custom-struct", f"{struct}={attribute}"]
64+
65+
# Add include paths relative to source directory
66+
base_cmd += ["--", f"-I{arch_headers}/include"] # Use absolute include path
67+
68+
# Step 3: Execute command with error handling
69+
print(f"\nGenerating bindings for {arch}...")
70+
bindings = subprocess.run(
71+
base_cmd, check=True, capture_output=True, text=True, encoding="utf-8"
72+
).stdout
73+
74+
print("Successfully generated bindings")
75+
76+
# Generate architecture-specific filename
77+
output_file_path = f"{output_path}/{arch}/bindings.rs"
78+
79+
print(f"Generating to: {output_file_path}")
80+
81+
except subprocess.CalledProcessError as e:
82+
err_msg = f"Bindgen failed (code {e.returncode})"
83+
raise RuntimeError(err_msg) from e
84+
except Exception as e:
85+
raise RuntimeError(f"Generation failed: {str(e)}") from e
86+
87+
try:
88+
with open(output_file_path, "w") as f:
89+
f.write(bindings)
90+
91+
# Format with rustfmt
92+
subprocess.run(["rustfmt", output_file_path], check=True)
93+
print(f"Generation succeeded: {output_file_path}")
94+
except subprocess.CalledProcessError:
95+
raise RuntimeError("rustfmt formatting failed")
96+
except IOError as e:
97+
raise RuntimeError(f"File write error: {str(e)}")
98+
99+
100+
def capture_serde(arch: str) -> list[str]:
101+
"""
102+
Parse serde implementations for specified architecture
103+
104+
:param arch: Architecture name (e.g. aarch64, riscv64, x86_64)
105+
:return: List of found struct names
106+
:raises FileNotFoundError: When target file is missing
107+
:raises ValueError: When serde_impls block is not found
108+
"""
109+
# Build target file path
110+
target_path = Path(f"{KVM_BINDINGS_DIR}/{arch}/serialize.rs")
111+
112+
# Validate file existence
113+
if not target_path.is_file():
114+
raise FileNotFoundError(
115+
f"Serialization file not found for {arch}: {target_path}"
116+
)
117+
118+
print(f"Extracting serde structs of {arch} from: {target_path}")
119+
120+
# Read file content
121+
content = target_path.read_text(encoding="utf-8")
122+
123+
# Multi-line regex pattern to find serde_impls block
124+
pattern = re.compile(
125+
r"serde_impls!\s*\{\s*(?P<struct>.*?)\s*\}", re.DOTALL | re.MULTILINE
126+
)
127+
128+
# Extract struct list from matched block
129+
match = pattern.search(content)
130+
if not match:
131+
raise ValueError(f"No serde_impls! block found in {target_path}")
132+
133+
struct_list = match.group("struct")
134+
135+
structs = []
136+
for line in struct_list.splitlines():
137+
# Split and clean individual words
138+
for word in line.split():
139+
clean_word = word.strip().rstrip(",")
140+
if clean_word:
141+
structs.append(clean_word)
142+
143+
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)