Implement OSDK functionalities and opt-in OSDK for asterinas

This commit is contained in:
Zhang Junyang 2024-02-21 16:58:40 +08:00 committed by Tate, Hongliang Tian
parent bc9bce9dea
commit f97d0f1260
103 changed files with 1663 additions and 1295 deletions

View File

@ -1,14 +0,0 @@
[target.'cfg(target_os = "none")']
runner = "cargo run --package aster-runner --"
[alias]
kcheck = "check --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem"
kbuild = "build --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem"
krun = "run --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem"
kclippy = "clippy --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem"
component-check = "component check --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem"
[install]
root = "./target"

View File

@ -16,6 +16,8 @@ jobs:
- uses: actions/checkout@v3
- run: make install_osdk
- name: Check
id: check
run: make check

View File

@ -16,6 +16,8 @@ jobs:
- uses: actions/checkout@v3
- run: make install_osdk
- name: Boot Test (Multiboot)
id: boot_test_mb
run: make run AUTO_TEST=boot ENABLE_KVM=0 BOOT_PROTOCOL=multiboot RELEASE_MODE=1
@ -26,7 +28,7 @@ jobs:
- name: Boot Test (MicroVM)
id: boot_test_microvm
run: make run AUTO_TEST=boot ENABLE_KVM=0 BOOT_METHOD=microvm RELEASE_MODE=1
run: make run AUTO_TEST=boot ENABLE_KVM=0 QEMU_MACHINE=microvm RELEASE_MODE=1
- name: Boot Test (Linux Legacy 32-bit Boot Protocol)
id: boot_test_linux_legacy32
@ -42,9 +44,9 @@ jobs:
- name: Syscall Test at Ext2 (MicroVM)
id: syscall_test_at_ext2_microvm
run: make run AUTO_TEST=syscall SYSCALL_TEST_DIR=/ext2 ENABLE_KVM=0 BOOT_METHOD=microvm RELEASE_MODE=1
run: make run AUTO_TEST=syscall SYSCALL_TEST_DIR=/ext2 ENABLE_KVM=0 QEMU_MACHINE=microvm RELEASE_MODE=1
- name: Regression Test (MicroVM)
id: regression_test_linux
run: make run AUTO_TEST=regression ENABLE_KVM=0 BOOT_METHOD=microvm RELEASE_MODE=1
run: make run AUTO_TEST=regression ENABLE_KVM=0 QEMU_MACHINE=microvm RELEASE_MODE=1

View File

@ -16,12 +16,14 @@ jobs:
- uses: actions/checkout@v3
- name: Ktest Unit Test
id: ktest_unit_test
run: make run KTEST=1 ENABLE_KVM=0 RELEASE_MODE=1
- run: make install_osdk
- name: Usermode Unit test
id: usermode_unit_test
run: make test
- name: Ktest Unit Test
id: ktest_unit_test
run: make update_initramfs && make ktest
# TODO: add component check.

View File

@ -1,9 +1,9 @@
{
"rust-analyzer.check.allTargets": false,
"rust-analyzer.cargo.target": "x86_64-custom.json",
"rust-analyzer.cargo.target": "x86_64-unknown-none",
"rust-analyzer.check.extraArgs": [
"--target",
"x86_64-custom.json",
"x86_64-unknown-none",
"-Zbuild-std=core,alloc,compiler_builtins",
"-Zbuild-std-features=compiler-builtins-mem"
],

201
Cargo.lock generated
View File

@ -47,60 +47,6 @@ dependencies = [
"spinning_top",
]
[[package]]
name = "anstream"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "arrayvec"
version = "0.5.2"
@ -148,6 +94,7 @@ dependencies = [
"acpi",
"align_ext",
"aml",
"aster-main",
"bit_field",
"bitflags 1.3.2",
"bitvec",
@ -201,6 +148,15 @@ dependencies = [
"spin 0.9.8",
]
[[package]]
name = "aster-main"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
]
[[package]]
name = "aster-network"
version = "0.1.0"
@ -285,17 +241,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "aster-runner"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"linux-bzimage-builder",
"rand",
"xmas-elf 0.8.0",
]
[[package]]
name = "aster-time"
version = "0.1.0"
@ -448,52 +393,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim 0.11.0",
]
[[package]]
name = "clap_derive"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.49",
]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "component"
version = "0.1.0"
@ -596,7 +495,7 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim 0.10.0",
"strsim",
"syn 1.0.109",
]
@ -796,12 +695,6 @@ dependencies = [
"stable_deref_trait",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "ident_case"
version = "1.0.1"
@ -1384,12 +1277,6 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strsim"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]]
name = "syn"
version = "1.0.109"
@ -1670,72 +1557,6 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "winnow"
version = "0.5.7"

View File

@ -3,10 +3,6 @@ name = "asterinas"
version = "0.3.1"
edition = "2021"
[[bin]]
name = "asterinas"
path = "kernel/main.rs"
[dependencies]
aster-frame = { path = "framework/aster-frame" }
aster-nix = { path = "services/aster-nix" }
@ -32,9 +28,9 @@ panic = "unwind"
[workspace]
members = [
"runner",
"framework/aster-frame",
"framework/libs/align_ext",
"framework/libs/aster-main",
"framework/libs/linux-bzimage/builder",
"framework/libs/linux-bzimage/boot-params",
"framework/libs/linux-bzimage/setup",
@ -61,6 +57,7 @@ members = [
exclude = [
"osdk",
"target/osdk/base",
"services/libs/comp-sys/cargo-component",
"services/libs/comp-sys/component",
"services/libs/comp-sys/component-macro",

112
Makefile
View File

@ -1,92 +1,72 @@
# SPDX-License-Identifier: MPL-2.0
# Make varaiables and defaults, you should refer to aster-runner for more details
# The Makefile provides a way to run arbitrary tests in the kernel
# mode using the kernel command line.
# Here are the options for the auto test feature.
AUTO_TEST ?= none
BOOT_METHOD ?= qemu-grub
BOOT_LOADER ?= grub
BOOT_PROTOCOL ?= multiboot2
QEMU_MACHINE ?= q35
BUILD_SYSCALL_TEST ?= 0
EMULATE_IOMMU ?= 0
ENABLE_KVM ?= 1
GDB_CLIENT ?= 0
GDB_SERVER ?= 0
INTEL_TDX ?= 0
KTEST ?= 0
KTEST_CRATES ?= all
KTEST_WHITELIST ?=
SKIP_GRUB_MENU ?= 1
SYSCALL_TEST_DIR ?= /tmp
RELEASE_MODE ?= 0
# End of setting up Make varaiables
# End of auto test features.
CARGO_OSDK_ARGS :=
KERNEL_CMDLINE := SHELL="/bin/sh" LOGNAME="root" HOME="/" USER="root" PATH="/bin:/benchmark" init=/usr/bin/busybox
KERNEL_CMDLINE += ktest.whitelist="$(KTEST_WHITELIST)"
INIT_CMDLINE := sh -l
ifeq ($(AUTO_TEST), syscall)
BUILD_SYSCALL_TEST := 1
KERNEL_CMDLINE += SYSCALL_TEST_DIR=$(SYSCALL_TEST_DIR)
INIT_CMDLINE += /opt/syscall_test/run_syscall_test.sh
CARGO_OSDK_ARGS += --kcmd_args="SYSCALL_TEST_DIR=$(SYSCALL_TEST_DIR)"
CARGO_OSDK_ARGS += --init_args="/opt/syscall_test/run_syscall_test.sh"
endif
ifeq ($(AUTO_TEST), regression)
INIT_CMDLINE += /regression/run_regression_test.sh
CARGO_OSDK_ARGS += --init_args="/regression/run_regression_test.sh"
endif
ifeq ($(AUTO_TEST), boot)
INIT_CMDLINE += -c exit 0
CARGO_OSDK_ARGS += --init_args="-c exit 0"
endif
CARGO_KBUILD_ARGS :=
CARGO_KRUN_ARGS :=
GLOBAL_RUSTC_FLAGS :=
ifeq ($(RELEASE_MODE), 1)
CARGO_KBUILD_ARGS += --release
CARGO_KRUN_ARGS += --release
CARGO_OSDK_ARGS += --profile release
endif
ifeq ($(INTEL_TDX), 1)
CARGO_KBUILD_ARGS += --features intel_tdx
CARGO_KRUN_ARGS += --features intel_tdx
CARGO_OSDK_ARGS += --features intel_tdx
endif
RUNNER_ARGS := '$(KERNEL_CMDLINE) -- $(INIT_CMDLINE)'
RUNNER_ARGS += --boot-method="$(BOOT_METHOD)"
RUNNER_ARGS += --boot-protocol="$(BOOT_PROTOCOL)"
CARGO_OSDK_ARGS += --boot.loader="$(BOOT_LOADER)"
CARGO_OSDK_ARGS += --boot.protocol="$(BOOT_PROTOCOL)"
CARGO_OSDK_ARGS += --qemu.machine="$(QEMU_MACHINE)"
ifeq ($(QEMU_MACHINE), microvm)
CARGO_OSDK_ARGS += --select microvm
endif
# To test the linux-efi-handover64 boot protocol, we need to use Debian's
# GRUB release, which is installed in /usr/bin in our Docker image.
ifeq ($(BOOT_PROTOCOL), linux-efi-handover64)
CARGO_OSDK_ARGS += --boot.grub-mkrescue=/usr/bin/grub-mkrescue
endif
ifeq ($(EMULATE_IOMMU), 1)
RUNNER_ARGS += --emulate-iommu
CARGO_OSDK_ARGS += --select iommu
endif
ifeq ($(ENABLE_KVM), 1)
RUNNER_ARGS += --enable-kvm
endif
ifeq ($(GDB_SERVER), 1)
ENABLE_KVM := 0
RUNNER_ARGS += --halt-for-gdb
endif
ifeq ($(GDB_CLIENT), 1)
RUNNER_ARGS += --run-gdb-client
endif
ifeq ($(KTEST), 1)
comma := ,
GLOBAL_RUSTC_FLAGS += --cfg ktest --cfg ktest=\"$(subst $(comma),\" --cfg ktest=\",$(KTEST_CRATES))\"
endif
ifeq ($(SKIP_GRUB_MENU), 1)
RUNNER_ARGS += --skip-grub-menu
CARGO_OSDK_ARGS += --qemu.args="--enable-kvm"
endif
# Pass make variables to all subdirectory makes
export
# Toolchain variables that are used when building the Linux setup header
export CARGO := cargo
# Maintain a list of usermode crates that can be tested with `cargo test`
USERMODE_TESTABLE := \
runner \
framework/libs/align_ext \
framework/libs/aster-main \
framework/libs/linux-bzimage/builder \
framework/libs/linux-bzimage/boot-params \
framework/libs/ktest \
@ -100,31 +80,45 @@ USERMODE_TESTABLE := \
services/libs/typeflags \
services/libs/typeflags-util
.PHONY: all setup build tools run test docs check clean update_initramfs
# Maintain a list of kernel crates that can be tested with `cargo osdk test`
KTEST_TESTABLE := \
"framework/aster-frame" \
"services/aster-nix" \
"services/comps/block" \
"services/comps/console" \
"services/comps/framebuffer" \
"services/comps/input" \
"services/comps/network" \
"services/comps/time" \
"services/comps/virtio"
.PHONY: all install_osdk build tools run test docs check clean update_initramfs
all: build
setup:
@rustup component add rust-src
@rustup component add rustc-dev
@rustup component add llvm-tools-preview
@cargo install mdbook
install_osdk:
@cargo install cargo-osdk --path osdk --force
build:
@make --no-print-directory -C regression
@RUSTFLAGS="$(GLOBAL_RUSTC_FLAGS)" cargo kbuild $(CARGO_KBUILD_ARGS)
@cargo osdk build $(CARGO_OSDK_ARGS)
tools:
@cd services/libs/comp-sys && cargo install --path cargo-component
run: build
@RUSTFLAGS="$(GLOBAL_RUSTC_FLAGS)" cargo krun $(CARGO_KRUN_ARGS) -- $(RUNNER_ARGS)
@cargo osdk run $(CARGO_OSDK_ARGS)
test:
@for dir in $(USERMODE_TESTABLE); do \
(cd $$dir && cargo test) || exit 1; \
done
ktest:
@for dir in $(KTEST_TESTABLE); do \
(cd $$dir && cargo osdk test) || exit 1; \
done
docs:
@cargo doc # Build Rust docs
@echo "" # Add a blank line
@ -135,7 +129,7 @@ format:
check:
./tools/format_all.sh --check # Check Rust format issues
@cargo kclippy -- -D warnings # Make build fail if any warnings are found by rustc and clippy
@cargo osdk clippy
clean:
@cargo clean

88
OSDK.toml Normal file
View File

@ -0,0 +1,88 @@
kcmd_args = [
"SHELL=/bin/sh",
"LOGNAME=root",
"HOME=/",
"USER=root",
"PATH=/bin:/benchmark",
"init=/usr/bin/busybox",
]
init_args = ["sh", "-l"]
initramfs = "regression/build/initramfs.cpio.gz"
[boot]
protocol = "multiboot2"
loader = "grub"
ovmf = "/root/ovmf/release"
[qemu]
machine = "q35"
drive_files = [
["regression/build/ext2.img", "if=none,format=raw,id=x0"],
]
args = [
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off",
"-device virtconsole,chardev=mux",
]
[qemu.'cfg(select="iommu")']
machine = "q35"
drive_files = [
["regression/build/ext2.img", "if=none,format=raw,id=x0"],
]
args = [
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtconsole,chardev=mux",
"-device intel-iommu,intremap=on,device-iotlb=on",
"-device ioh3420,id=pcie.0,chassis=1",
]
[qemu.'cfg(select="microvm")']
machine = "microvm"
drive_files = [
["regression/build/ext2.img", "if=none,format=raw,id=x0"],
]
args = [
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
"-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080",
"-nodefaults",
"-no-user-config",
"-device virtio-blk-device,drive=x0",
"-device virtio-keyboard-device",
"-device virtio-net-device,netdev=net01",
"-device virtio-serial-device",
"-device virtconsole,chardev=mux",
]

View File

@ -1,21 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
use std::{error::Error, path::PathBuf};
fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
let target = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let linker_script_path = if target == "x86_64" {
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("framework")
.join("aster-frame")
.join("src")
.join("arch")
.join("x86")
.join("linker.ld")
} else {
panic!("Unsupported target arch: {}", target);
};
println!("cargo:rerun-if-changed={}", linker_script_path.display());
println!("cargo:rustc-link-arg=-T{}", linker_script_path.display());
Ok(())
}

View File

@ -7,6 +7,7 @@ edition = "2021"
[dependencies]
align_ext = { path = "../libs/align_ext" }
aster-main = { path = "../libs/aster-main" }
bit_field = "0.10.1"
bitflags = "1.3"
bitvec = { version = "1.0", default-features = false, features = ["alloc"] }

View File

@ -91,11 +91,6 @@ impl From<&str> for KCmdlineArg {
// The main parse loop. The processing steps are arranged (not very strictly)
// by the analysis over the BackusNaur form syntax tree.
for arg in split_arg(cmdline) {
// FIXME: The -kernel option in QEMU seems to add this string to the command line, which we skip for now.
if arg.starts_with("target/x86_64-custom/") {
warn!("Found kcmdline: {:?}, skipped for now.", arg);
continue;
}
// Cmdline => KernelArg "--" InitArg
// KernelArg => Arg "\s+" KernelArg | %empty
// InitArg => Arg "\s+" InitArg | %empty
@ -116,7 +111,8 @@ impl From<&str> for KCmdlineArg {
1 => (arg_pattern[0], None),
2 => (arg_pattern[0], Some(arg_pattern[1])),
_ => {
panic!("Unable to parse argument {}", arg);
warn!("Unable to parse kernel argument {}, skip for now", arg);
continue;
}
};
// Entry => Module "." ModuleOptionName | KernelOptionName
@ -125,7 +121,11 @@ impl From<&str> for KCmdlineArg {
1 => (None, entry_pattern[0]),
2 => (Some(entry_pattern[0]), entry_pattern[1]),
_ => {
panic!("Unable to parse entry {} in argument {}", entry, arg);
warn!(
"Unable to parse entry {} in argument {}, skip for now",
entry, arg
);
continue;
}
};
if let Some(modname) = node {

View File

@ -113,48 +113,38 @@ pub fn call_aster_main() -> ! {
// The entry point of kernel code, which should be defined by the package that
// uses aster-frame.
extern "Rust" {
fn aster_main() -> !;
fn __aster_main() -> !;
}
aster_main();
__aster_main();
}
#[cfg(ktest)]
{
use alloc::{boxed::Box, string::ToString};
use core::any::Any;
use crate::arch::qemu::{exit_qemu, QemuExitCode};
unsafe {
crate::init();
let fn_catch_unwind = &(unwinding::panic::catch_unwind::<(), fn()>
as fn(fn()) -> Result<(), Box<(dyn Any + Send + 'static)>>);
// Parse the whitelist from the kernel command line.
let mut paths = None;
let args = kernel_cmdline().get_module_args("ktest");
if let Some(args) = args {
for options in args {
match options {
kcmdline::ModuleArg::KeyVal(key, val) => {
if key.to_str().unwrap() == "whitelist" && val.to_str().unwrap() != "" {
let paths_str = val.to_str().unwrap();
paths = Some(
paths_str
.split(',')
.map(|s| s.to_string())
.collect::<Vec<_>>(),
);
}
}
_ => {}
}
}
}
use ktest::runner::{run_ktests, KtestResult};
match run_ktests(
&crate::console::print,
fn_catch_unwind,
paths.map(|v| v.into_iter()),
) {
KtestResult::Ok => exit_qemu(QemuExitCode::Success),
KtestResult::Failed => exit_qemu(QemuExitCode::Failed),
// The whitelists that will be generated by OSDK runner as static consts.
extern "Rust" {
static KTEST_TEST_WHITELIST: Option<&'static [&'static str]>;
static KTEST_CRATE_WHITELIST: Option<&'static [&'static str]>;
}
run_ktests(KTEST_TEST_WHITELIST, KTEST_CRATE_WHITELIST);
}
}
fn run_ktests(test_whitelist: Option<&[&str]>, crate_whitelist: Option<&[&str]>) -> ! {
use crate::arch::qemu::{exit_qemu, QemuExitCode};
use alloc::{boxed::Box, string::ToString};
use core::any::Any;
let fn_catch_unwind = &(unwinding::panic::catch_unwind::<(), fn()>
as fn(fn()) -> Result<(), Box<(dyn Any + Send + 'static)>>);
use ktest::runner::{run_ktests, KtestResult};
match run_ktests(
&crate::console::print,
fn_catch_unwind,
test_whitelist.map(|s| s.iter().map(|s| s.to_string())),
crate_whitelist,
) {
KtestResult::Ok => exit_qemu(QemuExitCode::Success),
KtestResult::Failed => exit_qemu(QemuExitCode::Failed),
};
}

View File

@ -19,6 +19,7 @@
#![no_std]
extern crate alloc;
#[cfg(ktest)]
#[macro_use]
extern crate ktest;
#[macro_use]
@ -83,7 +84,7 @@ fn invoke_ffi_init_funcs() {
}
/// Simple unit tests for the ktest framework.
#[if_cfg_ktest]
#[cfg(ktest)]
mod test {
#[ktest]
fn trivial_assertion() {

View File

@ -23,12 +23,12 @@ use unwinding::{
panic::begin_panic,
};
fn abort() -> ! {
exit_qemu(QemuExitCode::Failed);
}
#[panic_handler]
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
/// The panic handler must be defined in the binary crate or in the crate that the binary
/// crate explicity declares by `extern crate`. We cannot let the base crate depend on the
/// framework due to prismatic dependencies. That's why we export this symbol and state the
/// panic handler in the binary crate.
#[export_name = "__aster_panic_handler"]
pub fn panic_handler(info: &core::panic::PanicInfo) -> ! {
let throw_info = ktest::PanicInfo {
message: info.message().unwrap().to_string(),
file: info.location().unwrap().file().to_string(),
@ -46,6 +46,10 @@ fn panic_handler(info: &core::panic::PanicInfo) -> ! {
abort();
}
fn abort() -> ! {
exit_qemu(QemuExitCode::Failed);
}
fn print_stack_trace() {
struct CallbackData {
counter: usize,

View File

@ -8,3 +8,7 @@ pub(crate) use alloc::{boxed::Box, sync::Arc, vec::Vec};
pub(crate) use core::any::Any;
pub use crate::vm::{Paddr, Vaddr};
pub use crate::early_print as print;
pub use crate::early_println as println;
pub use aster_main::aster_main;

View File

@ -288,7 +288,7 @@ impl fmt::Debug for AtomicBits {
}
}
#[if_cfg_ktest]
#[cfg(ktest)]
mod test {
use super::*;

View File

@ -162,7 +162,7 @@ impl HasPaddr for DmaCoherent {
}
}
#[if_cfg_ktest]
#[cfg(ktest)]
mod test {
use alloc::vec;

View File

@ -195,7 +195,7 @@ impl HasPaddr for DmaStream {
}
}
#[if_cfg_ktest]
#[cfg(ktest)]
mod test {
use alloc::vec;

View File

@ -0,0 +1,14 @@
[package]
name = "aster-main"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.78"
quote = "1.0.35"
syn = { version = "2.0.48", features = ["full"] }

View File

@ -0,0 +1,22 @@
// SPDX-License-Identifier: MPL-2.0
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};
#[proc_macro_attribute]
pub fn aster_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
let main_fn = parse_macro_input!(item as ItemFn);
let main_fn_name = &main_fn.sig.ident;
quote!(
#[no_mangle]
pub fn __aster_main() -> ! {
aster_frame::init();
#main_fn_name();
}
#main_fn
)
.into()
}

View File

@ -7,24 +7,7 @@ extern crate proc_macro2;
use proc_macro::TokenStream;
use quote::quote;
use rand::{distributions::Alphanumeric, Rng};
use syn::{parse_macro_input, Expr, Ident, ItemFn, ItemMod};
/// The conditional compilation attribute macro to control the compilation of test
/// modules.
#[proc_macro_attribute]
pub fn if_cfg_ktest(_attr: TokenStream, item: TokenStream) -> TokenStream {
// Assuming that the item is a module declearation, otherwise panics.
let input = parse_macro_input!(item as ItemMod);
let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
let output = quote! {
#[cfg(all(ktest, any(ktest = "all", ktest = #crate_name)))]
#input
};
TokenStream::from(output)
}
use syn::{parse_macro_input, Expr, Ident, ItemFn};
/// The test attribute macro to mark a test function.
#[proc_macro_attribute]

View File

@ -21,8 +21,8 @@
//! module, e.g.:
//!
//! ```rust
//! use ktest::{ktest, if_cfg_ktest};
//! #[if_cfg_ktest]
//! use ktest::ktest;
//! #[cfg(ktest)]
//! mod test {
//! #[ktest]
//! fn trivial_assertion() {
@ -97,7 +97,7 @@ extern crate alloc;
use alloc::{boxed::Box, string::String};
use core::result::Result;
pub use ktest_proc_macro::{if_cfg_ktest, ktest};
pub use ktest_proc_macro::ktest;
#[derive(Clone, Debug)]
pub struct PanicInfo {

View File

@ -3,7 +3,7 @@
//! Test runner enabling control over the tests.
//!
use alloc::{string::String, vec::Vec};
use alloc::{string::String, vec::Vec, collections::BTreeSet};
use core::format_args;
use owo_colors::OwoColorize;
@ -35,7 +35,8 @@ pub enum KtestResult {
pub fn run_ktests<PrintFn, PathsIter>(
print: &PrintFn,
catch_unwind: &CatchUnwindImpl,
whitelist: Option<PathsIter>,
test_whitelist: Option<PathsIter>,
crate_whitelist: Option<&[&str]>,
) -> KtestResult
where
PrintFn: Fn(core::fmt::Arguments),
@ -48,7 +49,7 @@ where
}
let whitelist_trie =
whitelist.map(|paths| SuffixTrie::from_paths(paths.map(|p| KtestPath::from(&p))));
test_whitelist.map(|paths| SuffixTrie::from_paths(paths.map(|p| KtestPath::from(&p))));
let tree = KtestTree::from_iter(KtestIter::new());
print!(
@ -56,7 +57,15 @@ where
tree.nr_tot_tests(),
tree.nr_tot_crates()
);
let crate_set =
crate_whitelist.map(|crates| crates.iter().copied().collect::<BTreeSet<&str>>());
for crate_ in tree.iter() {
if let Some(crate_set) = &crate_set {
if !crate_set.contains(crate_.name()) {
print!("\n[ktest runner] skipping crate \"{}\".\n", crate_.name());
continue;
}
}
match run_crate_ktests(crate_, print, catch_unwind, &whitelist_trie) {
KtestResult::Ok => {}
KtestResult::Failed => return KtestResult::Failed,

View File

@ -39,26 +39,16 @@ pub enum BzImageType {
/// - `target_image_path`: The path to the target bzImage.
/// - `image_type`: The type of the bzImage that we are building.
/// - `kernel_path`: The path to the kernel ELF.
/// - `setup_src`: The path to the setup crate.
/// - `setup_tmp_out_dir`: The path to the temporary output directory for the setup binary.
pub fn make_bzimage(
target_image_path: &Path,
image_type: BzImageType,
kernel_path: &Path,
setup_src: &Path,
setup_tmp_out_dir: &Path,
) {
pub fn make_bzimage(target_image_path: &Path, image_type: BzImageType, kernel_path: &Path) {
let setup = match image_type {
BzImageType::Legacy32 => {
let arch = setup_src
.join("x86_64-i386_pm-none.json")
let arch = PathBuf::from("../../setup/x86_64-i386_pm-none.json")
.canonicalize()
.unwrap();
build_setup_with_arch(setup_src, setup_tmp_out_dir, &SetupBuildArch::Other(arch))
}
BzImageType::Efi64 => {
build_setup_with_arch(setup_src, setup_tmp_out_dir, &SetupBuildArch::X86_64)
build_setup_with_arch(&SetupBuildArch::Other(arch))
}
BzImageType::Efi64 => build_setup_with_arch(&SetupBuildArch::X86_64),
};
let mut setup_elf = Vec::new();
@ -186,33 +176,24 @@ fn fill_legacy_header_fields(
/// Build the setup binary.
///
/// It will return the path to the built setup binary.
fn build_setup_with_arch(source_dir: &Path, tmp_out_dir: &Path, arch: &SetupBuildArch) -> PathBuf {
if !tmp_out_dir.exists() {
std::fs::create_dir_all(&tmp_out_dir).unwrap();
}
let tmp_out_dir = std::fs::canonicalize(tmp_out_dir).unwrap();
fn build_setup_with_arch(arch: &SetupBuildArch) -> PathBuf {
// Relocations are fewer in release mode. That's why the release mode is more stable than
// the debug mode.
let profile = "release";
let cargo = std::env::var("CARGO").unwrap();
let mut cmd = std::process::Command::new(cargo);
cmd.current_dir(source_dir);
let mut cmd = std::process::Command::new("cargo");
cmd.current_dir("../../setup");
cmd.arg("build");
if profile == "release" {
cmd.arg("--release");
}
cmd.arg("--package").arg("linux-bzimage-setup");
cmd.arg("--bin").arg("linux-bzimage-setup");
cmd.arg("--target").arg(match arch {
SetupBuildArch::X86_64 => "x86_64-unknown-none",
SetupBuildArch::Other(path) => path.to_str().unwrap(),
});
cmd.arg("-Zbuild-std=core,alloc,compiler_builtins");
cmd.arg("-Zbuild-std-features=compiler-builtins-mem");
// Specify the build target directory to avoid cargo running
// into a deadlock reading the workspace files.
cmd.arg("--target-dir").arg(tmp_out_dir.as_os_str());
cmd.env_remove("RUSTFLAGS");
cmd.env_remove("CARGO_ENCODED_RUSTFLAGS");
@ -231,7 +212,7 @@ fn build_setup_with_arch(source_dir: &Path, tmp_out_dir: &Path, arch: &SetupBuil
SetupBuildArch::Other(path) => path.file_stem().unwrap().to_str().unwrap(),
};
let setup_artifact = tmp_out_dir
let setup_artifact = PathBuf::from("../../setup/target")
.join(arch_name)
.join(profile)
.join("linux-bzimage-setup");

View File

@ -0,0 +1,2 @@
# The Rust build cache for the setup crate is placed here.
target/

View File

@ -3,6 +3,10 @@ name = "linux-bzimage-setup"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "linux-bzimage-setup"
path = "src/main.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -1,19 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
#![no_std]
#![no_main]
// The `export_name` attribute for the `aster_main` entrypoint requires the removal of safety check.
// Please be aware that the kernel is not allowed to introduce any other unsafe operations.
// #![forbid(unsafe_code)]
extern crate aster_frame;
use aster_frame::early_println;
#[export_name = "aster_main"]
pub fn main() -> ! {
aster_frame::init();
early_println!("[kernel] finish init aster_frame");
component::init_all(component::parse_metadata!()).unwrap();
aster_nix::init();
aster_nix::run_first_process();
}

3
osdk/.gitignore vendored
View File

@ -8,3 +8,6 @@ target/
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Integration test generated files
workspace_*

146
osdk/Cargo.lock generated
View File

@ -13,9 +13,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.11"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540"
dependencies = [
"anstyle",
"anstyle-parse",
@ -27,9 +27,9 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.4"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
@ -61,9 +61,9 @@ dependencies = [
[[package]]
name = "assert_cmd"
version = "2.0.13"
version = "2.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00ad3f3a942eee60335ab4342358c161ee296829e0d16ff42fc1d6cb07815467"
checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8"
dependencies = [
"anstyle",
"bstr",
@ -74,6 +74,12 @@ dependencies = [
"wait-timeout",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.2"
@ -91,6 +97,26 @@ dependencies = [
"serde",
]
[[package]]
name = "bytemuck"
version = "1.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "cargo-osdk"
version = "0.1.5"
@ -100,6 +126,7 @@ dependencies = [
"env_logger",
"indexmap",
"lazy_static",
"linux-bzimage-builder",
"log",
"regex",
"serde",
@ -110,9 +137,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.17"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80932e03c33999b9235edb8655bc9df3204adc9887c2f95b50cb1deb9fd54253"
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
dependencies = [
"clap_builder",
"clap_derive",
@ -120,9 +147,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.17"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6c0db58c659eef1c73e444d298c27322a1b52f6927d2ad470c0c0f96fa7b8fa"
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
dependencies = [
"anstream",
"anstyle",
@ -132,9 +159,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.4.7"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
dependencies = [
"heck",
"proc-macro2",
@ -144,9 +171,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.6.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
@ -168,9 +195,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "either"
version = "1.9.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "env_filter"
@ -184,9 +211,9 @@ dependencies = [
[[package]]
name = "env_logger"
version = "0.11.0"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eeb342678d785662fd2514be38c459bb925f02b68dd2a3e0f21d7ef82d979dd"
checksum = "6c012a26a7f605efc424dd53697843a72be7dc86ad2d01f7814337794a12231d"
dependencies = [
"anstream",
"anstyle",
@ -240,9 +267,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "indexmap"
version = "2.2.1"
version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
dependencies = [
"equivalent",
"hashbrown",
@ -262,9 +289,19 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.152"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "linux-bzimage-builder"
version = "0.1.0"
dependencies = [
"bitflags 1.3.2",
"bytemuck",
"serde",
"xmas-elf",
]
[[package]]
name = "linux-raw-sys"
@ -319,9 +356,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.76"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
@ -349,9 +386,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
dependencies = [
"aho-corasick",
"memchr",
@ -366,11 +403,11 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rustix"
version = "0.38.30"
version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [
"bitflags",
"bitflags 2.4.2",
"errno",
"libc",
"linux-raw-sys",
@ -379,24 +416,24 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.16"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "serde"
version = "1.0.195"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.195"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
@ -405,9 +442,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.111"
version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
dependencies = [
"itoa",
"ryu",
@ -425,15 +462,15 @@ dependencies = [
[[package]]
name = "strsim"
version = "0.10.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]]
name = "syn"
version = "2.0.48"
version = "2.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
dependencies = [
"proc-macro2",
"quote",
@ -448,9 +485,9 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
[[package]]
name = "toml"
version = "0.8.8"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
dependencies = [
"indexmap",
"serde",
@ -470,9 +507,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.21.0"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
dependencies = [
"indexmap",
"serde",
@ -583,9 +620,24 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.5.34"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16"
checksum = "7a4191c47f15cc3ec71fcb4913cb83d58def65dd3787610213c649283b5ce178"
dependencies = [
"memchr",
]
[[package]]
name = "xmas-elf"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42c49817e78342f7f30a181573d82ff55b88a35f86ccaf07fc64b3008f56d1c6"
dependencies = [
"zero",
]
[[package]]
name = "zero"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fe21bcc34ca7fe6dd56cc2cb1261ea59d6b93620215aefb5ea6032265527784"

View File

@ -13,6 +13,7 @@ clap = { version = "4.4.17", features = ["cargo", "derive"] }
env_logger = "0.11.0"
indexmap = "2.2.1"
lazy_static = "1.4.0"
linux-bzimage-builder = { path = "../framework/libs/linux-bzimage/builder" }
log = "0.4.20"
regex = "1.10.3"
serde = { version = "1.0.195", features = ["derive"] }

View File

@ -14,19 +14,15 @@ OSDK (short for Operating System Development Kit) is designed to simplify the de
Currenly, `cargo-osdk` only supports x86_64 ubuntu system.
To run a kernel with QEMU, `cargo-osdk` requires the following tools to be installed:
`cargo-osdk` requires the following tools to be installed:
- Rust >= 1.75.0
- gcc
- qemu-system-x86_64
- grub-mkrescue
- ovmf
- xorriso
- Gcc compiler
About how to install Rust, you can refer to the [official site](https://www.rust-lang.org/tools/install).
Other tools can be installed by
Gcc compiler can be installed by
```bash
apt install build-essential grub2-common qemu-system-x86 ovmf xorriso
apt install build-essential
```
#### Install
@ -47,6 +43,17 @@ cargo install --force cargo-osdk
Here we provide a simple demo to demonstrate how to create and run a simple kernel with `cargo-osdk`.
Suppose you are on a x86_64 ubuntu machine, to run a kernel with QEMU, the following tools should be installed:
- qemu-system-x86_64
- grub-mkrescue
- ovmf
- xorriso
If these tools are missing, they can be installed by
```bash
apt install grub2-common qemu-system-x86 ovmf xorriso
```
With `cargo-osdk`, a kernel project can be created by one command
```bash
cargo osdk new --kernel my-first-os
@ -97,17 +104,15 @@ ovmf = "/usr/bin/ovmf" # <7>
path = "/usr/bin/qemu-system-x86_64" # <8>
machine = "q35" # <9>
args = [ # <10>
"-enable-kvm",
"--enable-kvm",
"-m 2G",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off"
"--device virtio-keyboard-pci,disable-legacy=on,disable-modern=off"
]
[qemu.'cfg(feature="iommu")'] # <11>
path = "/usr/local/sbin/qemu-kvm" # <8>
machine = "q35" # <9>
args = [ # <10>
"-enable-kvm",
"-m 2G",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device intel-iommu,intremap=on,device-iotlb=on"
]
@ -139,7 +144,18 @@ Optional. Default is `q35`.
The allowed values are `q35` and `microvm`.
10. Additional arguments passed to QEMU.
Optional. The default value is empty.
Each argument should be in the form `KEY VALUE` (separated by space), or `KEY` if no value is required. Some keys can appear multiple times (e.g., `-device`, `-netdev`), while other keys can appear at most once. Certain keys, such as `-cpu` and `-machine`, are not allowed to be set here as they may conflict with the internal settings of `cargo-osdk`.
Each argument should be in the form `KEY VALUE` (separated by space), or `KEY` if no value is required. Some keys can appear multiple times (e.g., `--device`, `--netdev`), while other keys can appear at most once. Certain keys, such as `-cpu` and `-machine`, are not allowed to be set here as they may conflict with the internal settings of `cargo-osdk`.
11. Conditional QEMU settings.
Optional. The default value is empty.
Conditional QEMU settings allow for a condition to be specified after `qemu`. Currently, `cargo-osdk` only supports the condition `cfg(feature="FEATURE")`, which activates the QEMU settings only if the `FEATURE` is set. The `FEATURE` must be defined in the project's `Cargo.toml`. At most one conditional setting can be activated at a time. If multiple conditional settings can be activated simultaneously, `cargo-osdk` will report an error. In the future, `cargo-osdk` will support all possible conditions that [Rust conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html) supports.
### The framekernel architecture
The architecture divides the OS development into two distinct realms: the safe world and the unsafe world. In the safe world, only safe Rust code is allowed, while the unsafe world can tap into the power of the unsafe keyword. At the heart of the unsafe world lies `aster-frame`, a compact framework with limited functionalities. It encapsulates essential OS operations such as booting, physical memory management, context switching, and more.
With `aster-frame` as the foundation, higher-level OS functionalities like process management, file systems, network protocols, and even device drivers can be built upon it using only safe Rust. This segregation ensures that critical operations are handled securely in the unsafe realm while allowing for the development of complex and feature-rich OS components.
In addition to OS functionalities, `aster-frame` also provides development utilities, including kernel mode unit test support. The shared base of crates built on `aster-frame` allows for easy reuse and facilitates the creation of sophisticated operating systems with rich features.
Overall, this architectural approach promotes safety and modularity, empowering developers to build robust and advanced OS systems using Rust.

View File

@ -0,0 +1,12 @@
#![no_std]
#![no_main]
extern crate #TARGET_NAME#;
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
extern "Rust" {
pub fn __aster_panic_handler(info: &core::panic::PanicInfo) -> !;
}
unsafe { __aster_panic_handler(info); }
}

108
osdk/src/base_crate/mod.rs Normal file
View File

@ -0,0 +1,108 @@
// SPDX-License-Identifier: MPL-2.0
//! The base crate is the OSDK generated crate that is ultimately built by cargo.
//! It will depend on the kernel crate.
//!
use std::path::Path;
use std::process::Command;
use std::str::FromStr;
use std::{fs, process};
use crate::error::Errno;
use crate::error_msg;
pub fn new_base_crate(
base_crate_path: impl AsRef<Path>,
dep_crate_name: &str,
dep_crate_path: impl AsRef<Path>,
) {
if base_crate_path.as_ref().exists() {
std::fs::remove_dir_all(&base_crate_path).unwrap();
}
let mut cmd = Command::new("cargo");
cmd.arg("new").arg("--bin").arg(base_crate_path.as_ref());
cmd.arg("--vcs").arg("none");
if !cmd.status().unwrap().success() {
error_msg!(
"Failed to create base crate at: {:#?}",
base_crate_path.as_ref()
);
process::exit(Errno::CreateBaseCrate as _);
}
// Set the current directory to the target osdk directory
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(&base_crate_path).unwrap();
// Add linker.ld file
let linker_ld = include_str!("x86_64-custom.ld.template");
fs::write("x86_64-custom.ld", linker_ld).unwrap();
// Add target json file
let target_json = include_str!("x86_64-custom.json.template");
fs::write("x86_64-custom.json", target_json).unwrap();
// Overrite the main.rs file
let main_rs = include_str!("main.rs.template");
// Replace all occurence of `#TARGET_NAME#` with the `dep_crate_name`
let main_rs = main_rs.replace("#TARGET_NAME#", &dep_crate_name.replace("-", "_"));
fs::write("src/main.rs", main_rs).unwrap();
// Add dependencies to the Cargo.toml
add_manifest_dependency(dep_crate_name, dep_crate_path);
// Copy the manifest configurations from the target crate to the base crate
copy_manifest_configurations(base_crate_path);
// Get back to the original directory
std::env::set_current_dir(&original_dir).unwrap();
}
fn add_manifest_dependency(crate_name: &str, crate_path: impl AsRef<Path>) {
let mainfest_path = "Cargo.toml";
let mut manifest: toml::Table = {
let content = fs::read_to_string(mainfest_path).unwrap();
toml::from_str(&content).unwrap()
};
let dependencies = manifest.get_mut("dependencies").unwrap();
let dep = toml::Table::from_str(&format!(
"{} = {{ path = \"{}\"}}",
crate_name,
crate_path.as_ref().display()
))
.unwrap();
dependencies.as_table_mut().unwrap().extend(dep);
let content = toml::to_string(&manifest).unwrap();
fs::write(mainfest_path, content).unwrap();
}
fn copy_manifest_configurations(target_crate_path: impl AsRef<Path>) {
let target_manifest_path = target_crate_path.as_ref().join("Cargo.toml");
let manifest_path = "Cargo.toml";
let target_manifest: toml::Table = {
let content = fs::read_to_string(target_manifest_path).unwrap();
toml::from_str(&content).unwrap()
};
let mut manifest: toml::Table = {
let content = fs::read_to_string(manifest_path).unwrap();
toml::from_str(&content).unwrap()
};
// Copy the profile configurations
let profile = target_manifest.get("profile");
if let Some(profile) = profile {
manifest.insert("profile".to_string(), profile.clone());
}
let content = toml::to_string(&manifest).unwrap();
fs::write(manifest_path, content).unwrap();
}

View File

@ -11,6 +11,11 @@
"executables": true,
"linker-flavor": "ld.lld",
"linker": "rust-lld",
"pre-link-args": {
"ld.lld": [
"--script=x86_64-custom.ld"
]
},
"disable-redzone": true,
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float"
}

33
osdk/src/bin.rs Normal file
View File

@ -0,0 +1,33 @@
// SPDX-License-Identifier: MPL-2.0
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AsterBin {
pub path: PathBuf,
pub typ: AsterBinType,
pub version: String,
pub sha256sum: String,
pub stripped: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AsterBinType {
Elf(AsterElfMeta),
BzImage(AsterBzImageMeta),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AsterElfMeta {
pub has_linux_header: bool,
pub has_pvh_header: bool,
pub has_multiboot_header: bool,
pub has_multiboot2_header: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AsterBzImageMeta {
pub support_legacy32_boot: bool,
pub support_efi_boot: bool,
pub support_efi_handover: bool,
}

191
osdk/src/bundle.rs Normal file
View File

@ -0,0 +1,191 @@
// SPDX-License-Identifier: MPL-2.0
use std::path::{Path, PathBuf};
use std::process::Command;
use crate::bin::AsterBin;
use crate::cli::CargoArgs;
use crate::config_manager::{
boot::Boot,
qemu::{Qemu, QemuMachine},
RunConfig,
};
use crate::vm_image::AsterVmImage;
use crate::{error::Errno, error_msg};
/// The osdk bundle artifact that stores as `bundle` directory.
///
/// This `Bundle` struct is used to track a bundle on a filesystem. Every modification to the bundle
/// would result in file system writes. But the bundle will not be removed from the file system when
/// the `Bundle` is dropped.
pub struct Bundle {
manifest: BundleManifest,
path: PathBuf,
}
impl Bundle {
pub fn new(manifest: BundleManifest, path: impl AsRef<Path>) -> Self {
std::fs::create_dir_all(path.as_ref()).unwrap();
let created = Self {
manifest,
path: path.as_ref().to_path_buf(),
};
created.write_manifest_content();
created
}
// FIXME: the load function should be used when implementing build cache, but it is not
// implemented yet.
#[allow(dead_code)]
pub fn load(path: impl AsRef<Path>) -> Self {
let manifest_file_path = path.as_ref().join("bundle.toml");
let manifest_file_content = std::fs::read_to_string(&manifest_file_path).unwrap();
let manifest: BundleManifest = toml::from_str(&manifest_file_content).unwrap();
// TODO: check integrity of the loaded bundle.
Self {
manifest,
path: path.as_ref().to_path_buf(),
}
}
pub fn can_run_with_config(&self, config: &RunConfig) -> bool {
// TODO: This pairwise comparison will result in some false negatives. We may
// fix it by pondering upon each fields with more care.
self.manifest.kcmd_args == config.manifest.kcmd_args
&& self.manifest.initramfs == config.manifest.initramfs
&& self.manifest.boot == config.manifest.boot
&& self.manifest.qemu == config.manifest.qemu
&& self.manifest.cargo_args == config.cargo_args
}
pub fn run(&self, config: &RunConfig) {
if !self.can_run_with_config(config) {
error_msg!("The bundle is not compatible with the run configuration");
std::process::exit(Errno::RunBundle as _);
}
let mut qemu_cmd = Command::new(config.manifest.qemu.path.clone().unwrap());
// FIXME: Arguments like "-m 2G" sould be separated into "-m" and "2G". This
// is a dirty hack to make it work. Anything like space in the paths will
// break this.
for arg in &config.manifest.qemu.args {
for part in arg.split_whitespace() {
qemu_cmd.arg(part);
}
}
match config.manifest.qemu.machine {
QemuMachine::Microvm => {
qemu_cmd.arg("-machine").arg("microvm");
let Some(ref aster_bin) = self.manifest.aster_bin else {
error_msg!("Kernel ELF binary is required for Microvm");
std::process::exit(Errno::RunBundle as _);
};
qemu_cmd.arg("-kernel").arg(self.path.join(&aster_bin.path));
let Some(ref initramfs) = config.manifest.initramfs else {
error_msg!("Initramfs is required for Microvm");
std::process::exit(Errno::RunBundle as _);
};
qemu_cmd.arg("-initrd").arg(initramfs);
qemu_cmd
.arg("-append")
.arg(config.manifest.kcmd_args.join(" "));
}
QemuMachine::Q35 => {
qemu_cmd.arg("-machine").arg("q35,kernel-irqchip=split");
let Some(ref vm_image) = self.manifest.vm_image else {
error_msg!("VM image is required for QEMU booting");
std::process::exit(Errno::RunBundle as _);
};
qemu_cmd.arg("-cdrom").arg(self.path.join(&vm_image.path));
if let Some(ovmf) = &config.manifest.boot.ovmf {
qemu_cmd.arg("-drive").arg(format!(
"if=pflash,format=raw,unit=0,readonly=on,file={}",
ovmf.join("OVMF_CODE.fd").display()
));
qemu_cmd.arg("-drive").arg(format!(
"if=pflash,format=raw,unit=1,file={}",
ovmf.join("OVMF_VARS.fd").display()
));
}
}
};
qemu_cmd.arg("-cpu").arg("Icelake-Server,+x2apic");
for drive_file in &config.manifest.qemu.drive_files {
qemu_cmd.arg("-drive").arg(format!(
"file={},{}",
drive_file.path.display(),
drive_file.append,
));
}
let exit_status = qemu_cmd.status().unwrap();
if !exit_status.success() {
// FIXME: Exit code manipulation is not needed when using non-x86 QEMU
let qemu_exit_code = exit_status.code().unwrap();
let kernel_exit_code = qemu_exit_code >> 1;
match kernel_exit_code {
0x10 /*aster_frame::QemuExitCode::Success*/ => { std::process::exit(0); },
0x20 /*aster_frame::QemuExitCode::Failed*/ => { std::process::exit(1); },
_ /* unknown, e.g., a triple fault */ => { std::process::exit(2) },
}
}
}
pub fn add_vm_image(&mut self, vm_image: &AsterVmImage) {
if self.manifest.vm_image.is_some() {
panic!("vm_image already exists");
}
let file_name = vm_image.path.file_name().unwrap();
let copied_path = self.path.join(file_name);
std::fs::copy(&vm_image.path, &copied_path).unwrap();
self.manifest.vm_image = Some(AsterVmImage {
path: file_name.into(),
typ: vm_image.typ.clone(),
aster_version: vm_image.aster_version.clone(),
sha256sum: vm_image.sha256sum.clone(),
});
self.write_manifest_content();
}
pub fn add_aster_bin(&mut self, aster_bin: &AsterBin) {
if self.manifest.aster_bin.is_some() {
panic!("aster_bin already exists");
}
let file_name = aster_bin.path.file_name().unwrap();
let copied_path = self.path.join(file_name);
std::fs::copy(&aster_bin.path, &copied_path).unwrap();
self.manifest.aster_bin = Some(AsterBin {
path: file_name.into(),
typ: aster_bin.typ.clone(),
version: aster_bin.version.clone(),
sha256sum: aster_bin.sha256sum.clone(),
stripped: aster_bin.stripped.clone(),
});
self.write_manifest_content();
}
fn write_manifest_content(&self) {
let manifest_file_content = toml::to_string(&self.manifest).unwrap();
let manifest_file_path = self.path.join("bundle.toml");
std::fs::write(&manifest_file_path, manifest_file_content).unwrap();
}
}
/// The osdk bundle artifact manifest that stores as `bundle.toml`.
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BundleManifest {
#[serde(default)]
pub kcmd_args: Vec<String>,
#[serde(default)]
pub initramfs: Option<PathBuf>,
#[serde(default)]
pub aster_bin: Option<AsterBin>,
#[serde(default)]
pub vm_image: Option<AsterVmImage>,
#[serde(default)]
pub boot: Boot,
#[serde(default)]
pub qemu: Qemu,
#[serde(default)]
pub cargo_args: CargoArgs,
}

View File

@ -5,7 +5,10 @@ use std::path::PathBuf;
use clap::{crate_version, Args, Parser};
use crate::{
commands::{execute_check_command, execute_clippy_command, execute_new_command},
commands::{
execute_build_command, execute_check_command, execute_clippy_command, execute_new_command,
execute_run_command, execute_test_command,
},
config_manager::{
boot::{BootLoader, BootProtocol},
qemu::QemuMachine,
@ -24,21 +27,15 @@ pub fn main() {
OsdkSubcommand::New(args) => execute_new_command(args),
OsdkSubcommand::Build(build_args) => {
let build_config = BuildConfig::parse(build_args);
println!("{:?}", build_config);
// TODO: execute_build_command(build_config);
// todo!("execute build command");
execute_build_command(&build_config);
}
OsdkSubcommand::Run(run_args) => {
let run_config = RunConfig::parse(run_args);
println!("{:?}", run_config);
// TODO: execute_run_command(run_config);
// todo!("execute run command");
execute_run_command(&run_config);
}
OsdkSubcommand::Test(test_args) => {
let test_config = TestConfig::parse(test_args);
println!("{:?}", test_config);
// TODO: execute_test_command(test_config);
// todo!("execute test command");
execute_test_command(&test_config);
}
OsdkSubcommand::Check => execute_check_command(),
OsdkSubcommand::Clippy => execute_clippy_command(),
@ -114,20 +111,26 @@ pub struct TestArgs {
pub osdk_args: OsdkArgs,
}
#[derive(Debug, Args, Default)]
#[derive(Debug, Args, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct CargoArgs {
#[arg(
long,
help = "Build artifacts in release mode",
default_value = "false"
help = "The Cargo build profile (built-in candidates are 'debug', 'release' and 'dev')",
default_value = "dev"
)]
pub release: bool,
pub profile: String,
#[arg(long, value_name = "FEATURES", help = "List of features to activate")]
pub features: Vec<String>,
}
#[derive(Debug, Args)]
pub struct OsdkArgs {
#[arg(
long = "select",
help = "Select the specific configuration provided in the OSDK manifest",
value_name = "SELECTION"
)]
pub select: Option<String>,
#[arg(
long = "kcmd_args",
help = "Command line arguments for guest kernel",

View File

@ -0,0 +1,102 @@
// SPDX-License-Identifier: MPL-2.0
use linux_bzimage_builder::{make_bzimage, BzImageType};
use std::path::Path;
use std::process::Command;
use std::{
fs::OpenOptions,
io::{Seek, SeekFrom, Write},
};
use crate::bin::{AsterBin, AsterBinType, AsterBzImageMeta, AsterElfMeta};
use crate::config_manager::boot::BootProtocol;
use crate::utils::get_current_crate_info;
pub fn make_install_bzimage(
install_dir: impl AsRef<Path>,
aster_elf: &AsterBin,
protocol: &BootProtocol,
) -> AsterBin {
let target_name = get_current_crate_info().name;
let image_type = match protocol {
BootProtocol::LinuxLegacy32 => BzImageType::Legacy32,
BootProtocol::LinuxEfiHandover64 => BzImageType::Efi64,
_ => unreachable!(),
};
// Make the `bzImage`-compatible kernel image and place it in the boot directory.
let install_path = install_dir.as_ref().join(&target_name);
info!("Building bzImage");
make_bzimage(&install_path, image_type, &aster_elf.path);
AsterBin {
path: install_path,
typ: AsterBinType::BzImage(AsterBzImageMeta {
support_legacy32_boot: matches!(protocol, BootProtocol::LinuxLegacy32),
support_efi_boot: false,
support_efi_handover: matches!(protocol, BootProtocol::LinuxEfiHandover64),
}),
version: aster_elf.version.clone(),
sha256sum: "TODO".to_string(),
stripped: aster_elf.stripped,
}
}
pub fn strip_elf_for_qemu(install_dir: impl AsRef<Path>, elf: &AsterBin) -> AsterBin {
let stripped_elf_path = {
let elf_name = elf.path.file_name().unwrap().to_str().unwrap().to_string();
install_dir.as_ref().join(elf_name + ".stripped.elf")
};
// We use rust-strip to reduce the kernel image size.
let status = Command::new("rust-strip")
.arg(&elf.path)
.arg("-o")
.arg(stripped_elf_path.as_os_str())
.status();
match status {
Ok(status) => {
if !status.success() {
panic!("Failed to strip kernel elf.");
}
}
Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => panic!(
"`rust-strip` command not found. Please
try `cargo install cargo-binutils` and then rerun."
),
_ => panic!("Strip kernel elf failed, err:{:#?}", err),
},
}
// Because QEMU denies a x86_64 multiboot ELF file (GRUB2 accept it, btw),
// modify `em_machine` to pretend to be an x86 (32-bit) ELF image,
//
// https://github.com/qemu/qemu/blob/950c4e6c94b15cd0d8b63891dddd7a8dbf458e6a/hw/i386/multiboot.c#L197
// Set EM_386 (0x0003) to em_machine.
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(&stripped_elf_path)
.unwrap();
let bytes: [u8; 2] = [0x03, 0x00];
file.seek(SeekFrom::Start(18)).unwrap();
file.write_all(&bytes).unwrap();
file.flush().unwrap();
AsterBin {
path: stripped_elf_path,
typ: AsterBinType::Elf(AsterElfMeta {
has_linux_header: false,
has_pvh_header: false,
has_multiboot_header: true,
has_multiboot2_header: true,
}),
version: elf.version.clone(),
sha256sum: "TODO".to_string(),
stripped: true,
}
}

View File

@ -9,6 +9,6 @@ set timeout=#GRUB_TIMEOUT#
menuentry 'asterinas' {
#GRUB_CMD_KERNEL# #KERNEL# #KERNEL_COMMAND_LINE#
#GRUB_CMD_INITRAMFS# /boot/initramfs.cpio.gz
#GRUB_CMD_INITRAMFS#
boot
}

View File

@ -0,0 +1,157 @@
// SPDX-License-Identifier: MPL-2.0
use std::fs;
use std::path::{Path, PathBuf};
use crate::bin::AsterBin;
use crate::config_manager::{boot::BootProtocol, BuildConfig};
use crate::utils::get_current_crate_info;
use crate::vm_image::{AsterGrubIsoImageMeta, AsterVmImage, AsterVmImageType};
use super::bin::make_install_bzimage;
pub fn create_bootdev_image(
target_dir: impl AsRef<Path>,
aster_bin: &AsterBin,
initramfs_path: Option<impl AsRef<Path>>,
config: &BuildConfig,
) -> AsterVmImage {
let target_name = get_current_crate_info().name;
let iso_root = &target_dir.as_ref().join("iso_root");
let protocol = &config.manifest.boot.protocol;
// Clear or make the iso dir.
if iso_root.exists() {
fs::remove_dir_all(&iso_root).unwrap();
}
fs::create_dir_all(iso_root.join("boot").join("grub")).unwrap();
// Copy the initramfs to the boot directory.
if let Some(init_path) = &initramfs_path {
fs::copy(
init_path.as_ref().to_str().unwrap(),
iso_root.join("boot").join("initramfs.cpio.gz"),
)
.unwrap();
}
// Make the kernel image and place it in the boot directory.
match protocol {
BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => {
make_install_bzimage(&iso_root.join("boot"), aster_bin, protocol);
}
BootProtocol::Multiboot | BootProtocol::Multiboot2 => {
// Copy the kernel image to the boot directory.
let target_path = iso_root.join("boot").join(&target_name);
fs::copy(&aster_bin.path, &target_path).unwrap();
}
};
// Write the grub.cfg file
let initramfs_in_image = if initramfs_path.is_some() {
Some("/boot/initramfs.cpio.gz".to_string())
} else {
None
};
let grub_cfg = generate_grub_cfg(
&config.manifest.kcmd_args.join(" "),
true,
initramfs_in_image,
protocol,
);
let grub_cfg_path = iso_root.join("boot").join("grub").join("grub.cfg");
fs::write(&grub_cfg_path, grub_cfg).unwrap();
// Make the boot device CDROM image using `grub-mkrescue`.
let iso_path = &target_dir.as_ref().join(target_name.to_string() + ".iso");
let grub_mkrescue_bin = &config.manifest.boot.grub_mkrescue.clone().unwrap();
let mut grub_mkrescue_cmd = std::process::Command::new(grub_mkrescue_bin.as_os_str());
grub_mkrescue_cmd
.arg(iso_root.as_os_str())
.arg("-o")
.arg(iso_path);
if !grub_mkrescue_cmd.status().unwrap().success() {
panic!("Failed to run {:#?}.", grub_mkrescue_cmd);
}
AsterVmImage {
path: iso_path.clone(),
typ: AsterVmImageType::GrubIso(AsterGrubIsoImageMeta {
grub_version: get_grub_mkrescue_version(grub_mkrescue_bin),
}),
aster_version: aster_bin.version.clone(),
sha256sum: "TODO".to_string(),
}
}
fn generate_grub_cfg(
kcmdline: &str,
skip_grub_menu: bool,
initramfs_path: Option<String>,
protocol: &BootProtocol,
) -> String {
let target_name = get_current_crate_info().name;
let grub_cfg = include_str!("grub.cfg.template").to_string();
// Delete the first two lines that notes the file a template file.
let grub_cfg = grub_cfg.lines().skip(2).collect::<Vec<&str>>().join("\n");
// Set the timout style and timeout.
let grub_cfg = grub_cfg
.replace(
"#GRUB_TIMEOUT_STYLE#",
if skip_grub_menu { "hidden" } else { "menu" },
)
.replace("#GRUB_TIMEOUT#", if skip_grub_menu { "0" } else { "1" });
// Replace all occurrences of "#KERNEL_COMMAND_LINE#" with the desired value.
let grub_cfg = grub_cfg.replace("#KERNEL_COMMAND_LINE#", kcmdline);
// Replace the grub commands according to the protocol selected.
let aster_bin_path_on_device = PathBuf::from("/boot")
.join(&target_name)
.into_os_string()
.into_string()
.unwrap();
let grub_cfg = match protocol {
BootProtocol::Multiboot => grub_cfg
.replace("#GRUB_CMD_KERNEL#", "multiboot")
.replace("#KERNEL#", &aster_bin_path_on_device)
.replace(
"#GRUB_CMD_INITRAMFS#",
&if let Some(p) = &initramfs_path {
"module --nounzip ".to_owned() + p
} else {
"".to_owned()
},
),
BootProtocol::Multiboot2 => grub_cfg
.replace("#GRUB_CMD_KERNEL#", "multiboot2")
.replace("#KERNEL#", &aster_bin_path_on_device)
.replace(
"#GRUB_CMD_INITRAMFS#",
&if let Some(p) = &initramfs_path {
"module2 --nounzip ".to_owned() + p
} else {
"".to_owned()
},
),
BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => grub_cfg
.replace("#GRUB_CMD_KERNEL#", "linux")
.replace("#KERNEL#", &aster_bin_path_on_device)
.replace(
"#GRUB_CMD_INITRAMFS#",
&if let Some(p) = &initramfs_path {
"initrd ".to_owned() + p
} else {
"".to_owned()
},
),
};
grub_cfg
}
fn get_grub_mkrescue_version(grub_mkrescue: &PathBuf) -> String {
let mut cmd = std::process::Command::new(grub_mkrescue);
cmd.arg("--version");
let output = cmd.output().unwrap();
String::from_utf8(output.stdout).unwrap()
}

View File

@ -0,0 +1,134 @@
// SPDX-License-Identifier: MPL-2.0
mod bin;
mod grub;
use std::path::{Path, PathBuf};
use std::process;
use std::str::FromStr;
use bin::strip_elf_for_qemu;
use crate::base_crate::new_base_crate;
use crate::bin::{AsterBin, AsterBinType, AsterElfMeta};
use crate::bundle::{Bundle, BundleManifest};
use crate::cli::CargoArgs;
use crate::config_manager::{qemu::QemuMachine, BuildConfig};
use crate::utils::{get_current_crate_info, get_target_directory};
use crate::{error::Errno, error_msg};
use super::utils::{cargo, COMMON_CARGO_ARGS, DEFAULT_TARGET_RELPATH};
pub fn execute_build_command(config: &BuildConfig) {
let osdk_target_directory = get_target_directory().join(DEFAULT_TARGET_RELPATH);
if !osdk_target_directory.exists() {
std::fs::create_dir_all(&osdk_target_directory).unwrap();
}
let target_info = get_current_crate_info();
let bundle_path = osdk_target_directory.join(&target_info.name);
let _bundle = create_base_and_build(&bundle_path, &osdk_target_directory, &config);
}
pub fn create_base_and_build(
bundle_path: impl AsRef<Path>,
osdk_target_directory: impl AsRef<Path>,
config: &BuildConfig,
) -> Bundle {
let base_crate_path = osdk_target_directory.as_ref().join("base");
new_base_crate(
&base_crate_path,
&get_current_crate_info().name,
&get_current_crate_info().path,
);
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(&base_crate_path).unwrap();
let bundle = do_build(&bundle_path, &osdk_target_directory, &config);
std::env::set_current_dir(&original_dir).unwrap();
bundle
}
pub fn do_build(
bundle_path: impl AsRef<Path>,
osdk_target_directory: impl AsRef<Path>,
config: &BuildConfig,
) -> Bundle {
if let Some(ref initramfs) = config.manifest.initramfs {
if !initramfs.exists() {
error_msg!("initramfs file not found: {}", initramfs.display());
process::exit(Errno::BuildCrate as _);
}
};
let mut bundle = Bundle::new(
BundleManifest {
kcmd_args: config.manifest.kcmd_args.clone(),
initramfs: config.manifest.initramfs.clone(),
aster_bin: None,
vm_image: None,
boot: config.manifest.boot.clone(),
qemu: config.manifest.qemu.clone(),
cargo_args: config.cargo_args.clone(),
},
&bundle_path,
);
info!("Building kernel ELF");
let aster_elf = build_kernel_elf(&config.cargo_args);
if matches!(config.manifest.qemu.machine, QemuMachine::Microvm) {
let stripped_elf = strip_elf_for_qemu(&osdk_target_directory, &aster_elf);
bundle.add_aster_bin(&stripped_elf);
}
// TODO: A boot device is required if we use GRUB. Actually you can boot
// a multiboot kernel with Q35 machine directly without a bootloader.
// We are currently ignoring this case.
if matches!(config.manifest.qemu.machine, QemuMachine::Q35) {
info!("Building boot device image");
let bootdev_image = grub::create_bootdev_image(
&osdk_target_directory,
&aster_elf,
config.manifest.initramfs.as_ref(),
&config,
);
bundle.add_vm_image(&bootdev_image);
}
bundle
}
fn build_kernel_elf(args: &CargoArgs) -> AsterBin {
let target_directory = get_target_directory();
let target_json_path = PathBuf::from_str("x86_64-custom.json").unwrap();
let mut command = cargo();
command.arg("build").arg("--target").arg(&target_json_path);
command.args(COMMON_CARGO_ARGS);
command.arg("--profile=".to_string() + &args.profile);
let status = command.status().unwrap();
if !status.success() {
error_msg!("Cargo build failed");
process::exit(Errno::ExecuteCommand as _);
}
let aster_bin_path = PathBuf::from(target_directory)
.join(target_json_path.file_stem().unwrap().to_str().unwrap());
let aster_bin_path = if args.profile == "dev" {
aster_bin_path.join("debug")
} else {
aster_bin_path.join(&args.profile)
}
.join(get_current_crate_info().name);
AsterBin {
path: aster_bin_path,
typ: AsterBinType::Elf(AsterElfMeta {
has_linux_header: false,
has_pvh_header: false,
has_multiboot_header: true,
has_multiboot2_header: true,
}),
version: get_current_crate_info().version,
sha256sum: "TODO".to_string(),
stripped: false,
}
}

View File

@ -2,17 +2,17 @@
use std::process;
use crate::commands::utils::create_target_json;
use crate::error::Errno;
use crate::error_msg;
use super::utils::{cargo, COMMON_CARGO_ARGS};
use crate::{
commands::utils::create_target_json, error::Errno, error_msg, utils::get_cargo_metadata,
};
pub fn execute_check_command() {
let target_json_path = {
let metadata = get_cargo_metadata(None::<&str>, None::<&[&str]>);
let target_directory = metadata.get("target_directory").unwrap().as_str().unwrap();
create_target_json(target_directory)
};
let target_json_path = create_target_json();
let mut command = cargo();
command.arg("check").arg("--target").arg(target_json_path);

View File

@ -4,19 +4,15 @@ use std::process;
use super::utils::{cargo, COMMON_CARGO_ARGS};
use crate::{
commands::utils::create_target_json, error::Errno, error_msg, utils::get_cargo_metadata,
error_msg, commands::utils::create_target_json, error::Errno, error_msg, utils::get_cargo_metadata,
};
pub fn execute_clippy_command() {
let target_json_path = {
let metadata = get_cargo_metadata(None::<&str>, None::<&[&str]>);
let target_directory = metadata.get("target_directory").unwrap().as_str().unwrap();
create_target_json(target_directory)
};
let target_json_path = create_target_json();
let mut command = cargo();
command.arg("clippy").arg("-h");
info!("[Running] cargo clippy -h");
info!("Running `cargo clippy -h`");
let output = command.output().unwrap();
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
@ -28,6 +24,7 @@ pub fn execute_clippy_command() {
let mut command = cargo();
command.arg("clippy").arg("--target").arg(target_json_path);
command.args(COMMON_CARGO_ARGS);
// TODO: Add support for custom clippy args using OSDK commandline rather than hardcode it.
command.args(["--", "-D", "warnings"]);
let status = command.status().unwrap();
if !status.success() {

View File

@ -2,11 +2,15 @@
//! This module contains subcommands of cargo-osdk.
mod build;
mod check;
mod clippy;
mod new;
mod run;
mod test;
mod utils;
pub use self::{
check::execute_check_command, clippy::execute_clippy_command, new::execute_new_command,
run::execute_run_command, test::execute_test_command,
};

View File

@ -0,0 +1,22 @@
#![no_std]
#![no_main]
#![forbid(unsafe_code)]
#[macro_use]
extern crate ktest;
use aster_frame::prelude::*;
#[aster_main]
fn kernel_main() -> ! {
println!("Hello world from guest kernel!");
loop {}
}
#[cfg(ktest)]
mod test {
#[ktest]
fn trivial_test() {
assert_eq!(1 + 1, 2);
}
}

View File

@ -1,5 +1,9 @@
#![no_std]
#[macro_use]
extern crate ktest;
extern crate aster_frame;
#[cfg(ktest)]
mod tests {
#[ktest]

View File

@ -1,17 +1,18 @@
// SPDX-License-Identifier: MPL-2.0
use std::{fs, path::PathBuf, process, str::FromStr};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{fs, process};
use std::ffi::OsStr;
use crate::{
cli::NewArgs,
error::Errno,
error_msg,
utils::{cargo_new_lib, get_cargo_metadata, ASTER_FRAME_DEP},
};
use crate::cli::NewArgs;
use crate::error::Errno;
use crate::error_msg;
use crate::utils::{cargo_new_lib, get_cargo_metadata, ASTER_FRAME_DEP, KTEST_DEP};
pub fn execute_new_command(args: &NewArgs) {
cargo_new_lib(&args.crate_name);
let cargo_metadata = get_cargo_metadata(Some(&args.crate_name), None::<&[&str]>);
let cargo_metadata = get_cargo_metadata(Some(&args.crate_name), None::<&[&str]>).unwrap();
add_manifest_dependencies(&cargo_metadata, &args.crate_name);
create_osdk_manifest(&cargo_metadata);
if args.kernel {
@ -22,6 +23,14 @@ pub fn execute_new_command(args: &NewArgs) {
add_rust_toolchain(&cargo_metadata);
}
/// OSDK assumes that the toolchain used by the kernel should be same same as the toolchain
/// specified in the asterinas workspace.
macro_rules! aster_rust_toolchain {
() => {
include_str!("../../../../rust-toolchain.toml")
};
}
fn add_manifest_dependencies(cargo_metadata: &serde_json::Value, crate_name: &str) {
let mainfest_path = get_manifest_path(cargo_metadata, crate_name);
@ -34,6 +43,14 @@ fn add_manifest_dependencies(cargo_metadata: &serde_json::Value, crate_name: &st
let aster_frame_dep = toml::Table::from_str(ASTER_FRAME_DEP).unwrap();
dependencies.as_table_mut().unwrap().extend(aster_frame_dep);
let ktest_dep = toml::Table::from_str(KTEST_DEP).unwrap();
dependencies.as_table_mut().unwrap().extend(ktest_dep);
// If we created a workspace by `osdk new`, we should exclude the `base` crate from the workspace.
if get_cargo_metadata::<&Path, &OsStr>(None, None).is_none() {
let exclude = toml::Table::from_str(r#"exclude = ["target/osdk/base"]"#).unwrap();
manifest.insert("workspace".to_string(), toml::Value::Table(exclude));
}
let content = toml::to_string(&manifest).unwrap();
fs::write(mainfest_path, content).unwrap();
@ -51,20 +68,37 @@ fn create_osdk_manifest(cargo_metadata: &serde_json::Value) {
}
// Create `OSDK.toml` for the workspace
fs::write(osdk_manifest_path, "").unwrap();
// FIXME: we need ovmf for grub-efi, the user may not have it.
// The apt OVMF repo installs to `/usr/share/OVMF`
fs::write(osdk_manifest_path, r#"
[boot]
ovmf = "/usr/share/OVMF"
[qemu]
machine = "q35"
args = [
"--no-reboot",
"-m 2G",
"-nographic",
"-serial chardev:mux",
"-monitor chardev:mux",
"-chardev stdio,id=mux,mux=on,signal=off",
"-display none",
"-device isa-debug-exit,iobase=0xf4,iosize=0x04",
]
"#).unwrap();
}
/// Write the default content of `src/kernel.rs`, with contents in provided template.
fn write_kernel_template(cargo_metadata: &serde_json::Value, crate_name: &str) {
let src_path = get_src_path(cargo_metadata, crate_name);
let contents = include_str!("template/kernel.template");
let contents = include_str!("kernel.template");
fs::write(src_path, contents).unwrap();
}
/// Write the default content of `src/lib.rs`, with contents in provided template.
fn write_library_template(cargo_metadata: &serde_json::Value, crate_name: &str) {
let src_path = get_src_path(cargo_metadata, crate_name);
let contents = include_str!("template/lib.template");
let contents = include_str!("lib.template");
fs::write(src_path, contents).unwrap();
}
@ -83,7 +117,7 @@ fn add_rust_toolchain(cargo_metadata: &serde_json::Value) {
return;
}
let contents = include_str!("template/rust-toolchain.toml.template");
let contents = aster_rust_toolchain!();
fs::write(rust_toolchain_path, contents).unwrap();
}
@ -134,7 +168,7 @@ fn get_package_metadata<'a>(
fn check_rust_toolchain(toolchain: &toml::Table) {
let expected = {
let contents = include_str!("template/rust-toolchain.toml.template");
let contents = aster_rust_toolchain!();
toml::Table::from_str(contents).unwrap()
};

27
osdk/src/commands/run.rs Normal file
View File

@ -0,0 +1,27 @@
// SPDX-License-Identifier: MPL-2.0
use crate::config_manager::{BuildConfig, RunConfig};
use crate::utils::{get_current_crate_info, get_target_directory};
use super::build::create_base_and_build;
use super::utils::DEFAULT_TARGET_RELPATH;
pub fn execute_run_command(config: &RunConfig) {
let osdk_target_directory = get_target_directory().join(DEFAULT_TARGET_RELPATH);
let target_name = get_current_crate_info().name;
let default_bundle_directory = osdk_target_directory.join(target_name);
let required_build_config = BuildConfig {
manifest: config.manifest.clone(),
cargo_args: config.cargo_args.clone(),
};
// TODO: Check if the bundle is already built and compatible with the run configuration.
let bundle = create_base_and_build(
&default_bundle_directory,
&osdk_target_directory,
&required_build_config,
);
bundle.run(&config);
}

View File

@ -1,9 +0,0 @@
#![no_std]
#![no_main]
use aster_frame::prelude::*;
#[aster_main]
fn kernel_main() {
println!("Hello world from guest kernel!");
}

70
osdk/src/commands/test.rs Normal file
View File

@ -0,0 +1,70 @@
// SPDX-License-Identifier: MPL-2.0
use std::fs;
use crate::base_crate::new_base_crate;
use crate::config_manager::{BuildConfig, RunConfig, TestConfig};
use crate::utils::{get_current_crate_info, get_target_directory};
use super::build::do_build;
use super::utils::DEFAULT_TARGET_RELPATH;
pub fn execute_test_command(config: &TestConfig) {
let current_crate = get_current_crate_info();
let osdk_target_directory = get_target_directory().join(DEFAULT_TARGET_RELPATH);
let target_crate_dir = osdk_target_directory.join("base");
new_base_crate(&target_crate_dir, &current_crate.name, &current_crate.path);
let main_rs_path = target_crate_dir.join("src").join("main.rs");
let ktest_test_whitelist = match &config.test_name {
Some(name) => format!(r#"Some(&["{}"])"#, name),
None => format!(r#"None"#),
};
let mut ktest_crate_whitelist = vec![current_crate.name];
if let Some(name) = &config.test_name {
ktest_crate_whitelist.push(name.clone());
}
let ktest_static_var = format!(
r#"
#[no_mangle]
pub static KTEST_TEST_WHITELIST: Option<&[&str]> = {};
#[no_mangle]
pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?});
"#,
ktest_test_whitelist, ktest_crate_whitelist,
);
// Append the ktest static variable to the main.rs file
let mut main_rs_content = fs::read_to_string(&main_rs_path).unwrap();
main_rs_content.push_str(&ktest_static_var);
fs::write(&main_rs_path, main_rs_content).unwrap();
// Build the kernel with the given base crate
let target_name = get_current_crate_info().name;
let default_bundle_directory = osdk_target_directory.join(target_name);
let required_build_config = BuildConfig {
manifest: config.manifest.clone(),
cargo_args: config.cargo_args.clone(),
};
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(&target_crate_dir).unwrap();
// Add `--cfg ktest` to RUSTFLAGS
std::env::set_var("RUSTFLAGS", "--cfg ktest");
let bundle = do_build(
&default_bundle_directory,
&osdk_target_directory,
&required_build_config,
);
std::env::remove_var("RUSTFLAGS");
std::env::set_current_dir(&original_dir).unwrap();
let required_run_config = RunConfig {
manifest: required_build_config.manifest.clone(),
cargo_args: required_build_config.cargo_args.clone(),
};
bundle.run(&required_run_config);
}

View File

@ -6,17 +6,21 @@ use std::{
process::Command,
};
use crate::utils::get_target_directory;
pub const COMMON_CARGO_ARGS: &[&str] = &[
"-Zbuild-std=core,alloc,compiler_builtins",
"-Zbuild-std-features=compiler-builtins-mem",
];
pub const DEFAULT_TARGET_RELPATH: &str = "osdk";
pub fn cargo() -> Command {
Command::new("cargo")
}
pub fn create_target_json(target_directory: impl AsRef<Path>) -> PathBuf {
let target_osdk_dir = PathBuf::from(target_directory.as_ref()).join("osdk");
pub fn create_target_json() -> PathBuf {
let target_osdk_dir = get_target_directory().join(DEFAULT_TARGET_RELPATH);
fs::create_dir_all(&target_osdk_dir).unwrap();
let target_json_path = target_osdk_dir.join("x86_64-custom.json");
@ -24,7 +28,7 @@ pub fn create_target_json(target_directory: impl AsRef<Path>) -> PathBuf {
return target_json_path;
}
let contents = include_str!("template/x86_64-custom.json.template");
let contents = include_str!("../base_crate/x86_64-custom.json.template");
fs::write(&target_json_path, contents).unwrap();
target_json_path

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
use std::{path::PathBuf, process};
use std::path::{Path, PathBuf};
use std::process;
use regex::Regex;
use serde::Deserialize;
@ -12,7 +13,7 @@ use super::{
use crate::{error::Errno, error_msg};
/// The osdk manifest from configuration file and command line arguments.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct OsdkManifest {
pub kcmd_args: Vec<String>,
pub initramfs: Option<PathBuf>,
@ -21,7 +22,10 @@ pub struct OsdkManifest {
}
impl OsdkManifest {
pub fn from_toml_manifest<S: AsRef<str>>(toml_manifest: TomlManifest, features: &[S]) -> Self {
pub fn from_toml_manifest<S: AsRef<str>>(
toml_manifest: TomlManifest,
selection: Option<S>,
) -> Self {
let TomlManifest {
mut kcmd_args,
mut init_args,
@ -46,26 +50,26 @@ impl OsdkManifest {
let mut qemu_args = None;
let mut feature_enabled_args: Vec<_> = cfg
.into_iter()
.filter_map(|(cfg, args)| {
if features
.iter()
.any(|feature| cfg.contains(feature.as_ref()))
{
Some(args)
} else {
None
}
})
.collect();
let mut selected_args: Vec<_> = if let Some(sel) = selection {
cfg.into_iter()
.filter_map(|(cfg, args)| {
if cfg.contains(sel.as_ref()) {
Some(args)
} else {
None
}
})
.collect()
} else {
vec![]
};
if feature_enabled_args.len() > 1 {
error_msg!("Multiple features are conflict");
if selected_args.len() > 1 {
error_msg!("Multiple selections are not allowed");
process::exit(Errno::ParseMetadata as _);
} else if feature_enabled_args.len() == 1 {
qemu_args = Some(feature_enabled_args.remove(0));
} else if feature_enabled_args.is_empty() {
} else if selected_args.len() == 1 {
qemu_args = Some(selected_args.remove(0));
} else if selected_args.is_empty() {
qemu_args = Some(default);
}
@ -82,6 +86,38 @@ impl OsdkManifest {
qemu: qemu_args.unwrap(),
}
}
pub fn check_canonicalize_all_paths(&mut self, manifest_file_dir: impl AsRef<Path>) {
macro_rules! canonicalize_path {
($path:expr) => {{
let path = if $path.is_relative() {
manifest_file_dir.as_ref().join($path)
} else {
$path.clone()
};
path.canonicalize().unwrap_or_else(|_| {
error_msg!("File specified but not found: {:#?}", path);
process::exit(Errno::ParseMetadata as _);
})
}};
}
macro_rules! canonicalize_optional_path {
($path:expr) => {
if let Some(path_inner) = &$path {
Some(canonicalize_path!(path_inner))
} else {
None
}
};
}
self.initramfs = canonicalize_optional_path!(self.initramfs);
self.boot.grub_mkrescue = canonicalize_optional_path!(self.boot.grub_mkrescue);
self.boot.ovmf = canonicalize_optional_path!(self.boot.ovmf);
self.qemu.path = canonicalize_optional_path!(self.qemu.path);
for drive_file in &mut self.qemu.drive_files {
drive_file.path = canonicalize_path!(&drive_file.path);
}
}
}
/// The osdk manifest from configuration file `OSDK.toml`.
@ -111,12 +147,12 @@ fn check_args(arg_name: &str, args: &[String]) {
/// Check cfg that is in the form that we can accept
fn check_cfg(cfg: &str) {
if FEATURE_REGEX.captures(cfg).is_none() {
error_msg!("{} is not allowed to used after `qemu` in `OSDK.toml`. Currently we only allowed cfg like `cfg(feature=\"foo\")`", cfg);
if SELECT_REGEX.captures(cfg).is_none() {
error_msg!("{} is not allowed to used after `qemu` in `OSDK.toml`. Currently we only allow cfgs like `cfg(select=\"foo\")`", cfg);
process::exit(Errno::ParseMetadata as _);
}
}
lazy_static::lazy_static! {
pub static ref FEATURE_REGEX: Regex = Regex::new(r#"cfg\(feature="(?P<feature>\w+)"\)"#).unwrap();
pub static ref SELECT_REGEX: Regex = Regex::new(r#"cfg\(select="(?P<select>\w+)"\)"#).unwrap();
}

View File

@ -36,7 +36,7 @@ pub struct BuildConfig {
impl BuildConfig {
pub fn parse(args: &BuildArgs) -> Self {
let cargo_args = split_features(&args.cargo_args);
let mut manifest = load_osdk_manifest(&cargo_args);
let mut manifest = load_osdk_manifest(&cargo_args, args.osdk_args.select.as_ref());
apply_cli_args(&mut manifest, &args.osdk_args);
try_fill_system_configs(&mut manifest);
Self {
@ -56,7 +56,7 @@ pub struct RunConfig {
impl RunConfig {
pub fn parse(args: &RunArgs) -> Self {
let cargo_args = split_features(&args.cargo_args);
let mut manifest = load_osdk_manifest(&cargo_args);
let mut manifest = load_osdk_manifest(&cargo_args, args.osdk_args.select.as_ref());
apply_cli_args(&mut manifest, &args.osdk_args);
try_fill_system_configs(&mut manifest);
Self {
@ -77,7 +77,7 @@ pub struct TestConfig {
impl TestConfig {
pub fn parse(args: &TestArgs) -> Self {
let cargo_args = split_features(&args.cargo_args);
let mut manifest = load_osdk_manifest(&cargo_args);
let mut manifest = load_osdk_manifest(&cargo_args, args.osdk_args.select.as_ref());
apply_cli_args(&mut manifest, &args.osdk_args);
try_fill_system_configs(&mut manifest);
Self {
@ -88,17 +88,20 @@ impl TestConfig {
}
}
fn load_osdk_manifest(cargo_args: &CargoArgs) -> OsdkManifest {
let manifest_path = {
let feature_strings = get_feature_strings(cargo_args);
let cargo_metadata = get_cargo_metadata(None::<&str>, Some(&feature_strings));
let workspace_root = cargo_metadata
/// FIXME: I guess OSDK manifest is definitely NOT per workspace. It's per crate. When you cannot
/// find a manifest per crate, find it in the upper levels.
/// I don't bother to do it now, just fix the relpaths.
fn load_osdk_manifest<S: AsRef<str>>(cargo_args: &CargoArgs, selection: Option<S>) -> OsdkManifest {
let feature_strings = get_feature_strings(cargo_args);
let cargo_metadata = get_cargo_metadata(None::<&str>, Some(&feature_strings)).unwrap();
let workspace_root = PathBuf::from(
cargo_metadata
.get("workspace_root")
.unwrap()
.as_str()
.unwrap();
PathBuf::from(workspace_root).join("OSDK.toml")
};
.unwrap(),
);
let manifest_path = workspace_root.join("OSDK.toml");
let Ok(contents) = fs::read_to_string(&manifest_path) else {
error_msg!(
@ -108,8 +111,18 @@ fn load_osdk_manifest(cargo_args: &CargoArgs) -> OsdkManifest {
process::exit(Errno::GetMetadata as _);
};
let toml_manifest: TomlManifest = toml::from_str(&contents).unwrap();
OsdkManifest::from_toml_manifest(toml_manifest, &cargo_args.features)
let toml_manifest: TomlManifest = toml::from_str(&contents).unwrap_or_else(|err| {
error_msg!(
"Cannot parse TOML file, {}:\n{}:\n {}",
err.message(),
manifest_path.to_string_lossy().to_string(),
&contents[err.span().unwrap()],
);
process::exit(Errno::ParseMetadata as _);
});
let mut osdk_manifest = OsdkManifest::from_toml_manifest(toml_manifest, selection);
osdk_manifest.check_canonicalize_all_paths(workspace_root);
osdk_manifest
}
/// Split `features` in `cargo_args` to ensure each string contains exactly one feature.
@ -126,7 +139,7 @@ fn split_features(cargo_args: &CargoArgs) -> CargoArgs {
}
CargoArgs {
release: cargo_args.release,
profile: cargo_args.profile.clone(),
features,
}
}

View File

@ -16,13 +16,25 @@ pub struct Qemu {
/// The additional arguments for running qemu, except `-cpu` and `-machine`.
#[serde(default)]
pub args: Vec<String>,
/// The additional drive files
#[serde(default)]
pub drive_files: Vec<DriveFile>,
/// The `-machine` argument for running qemu.
#[serde(default)]
pub machine: QemuMachine,
/// The path of qemu.
#[serde(default)]
pub path: Option<PathBuf>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DriveFile {
#[serde(default)]
pub path: PathBuf,
#[serde(default)]
pub append: String,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
pub struct CfgQemu {
pub default: Qemu,
@ -44,6 +56,7 @@ impl<'de> Deserialize<'de> for CfgQemu {
Path,
Args,
Machine,
DriveFiles,
Cfg(String),
}
@ -58,7 +71,7 @@ impl<'de> Deserialize<'de> for CfgQemu {
type Value = Field;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`path`, `args`, `machine` or cfg")
formatter.write_str("`path`, `args`, `machine`, `drive_files` or cfg")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
@ -69,6 +82,7 @@ impl<'de> Deserialize<'de> for CfgQemu {
"args" => Ok(Field::Args),
"machine" => Ok(Field::Machine),
"path" => Ok(Field::Path),
"drive_files" => Ok(Field::DriveFiles),
v => Ok(Field::Cfg(v.to_string())),
}
}
@ -105,6 +119,9 @@ impl<'de> Deserialize<'de> for CfgQemu {
Field::Path => {
default.path = map.next_value()?;
}
Field::DriveFiles => {
default.drive_files = map.next_value()?;
}
Field::Cfg(cfg) => {
let qemu_args = map.next_value()?;
cfgs.insert(cfg, qemu_args);

View File

@ -8,6 +8,9 @@ pub enum Errno {
AddRustToolchain = 3,
ParseMetadata = 4,
ExecuteCommand = 5,
BuildCrate = 6,
RunBundle = 7,
CreateBaseCrate = 8,
}
/// Print error message to console

View File

@ -7,6 +7,9 @@ extern crate log;
#[macro_use]
extern crate serde;
mod base_crate;
mod bin;
mod bundle;
mod cli;
mod commands;
mod config_manager;
@ -14,6 +17,7 @@ mod error;
#[cfg(test)]
mod test;
mod utils;
mod vm_image;
fn main() {
// init logger

View File

@ -1,17 +1,13 @@
initramfs="./build/initramfs.cpio.gz"
[qemu]
path = "/usr/bin/qemu-system-x86_64"
args = ["-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
args = [
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
"-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
"-device virtio-serial-pci,disable-legacy=on,disable-modern=off"
]
[qemu.'cfg(feature="intel_tdx")']
path = "/usr/local/sbin/qemu-kvm"
[qemu.'cfg(select="intel_tdx")']
[qemu.'cfg(feature="iommu")']
path = "/usr/bin/qemu-system-x86_64"
[qemu.'cfg(select="iommu")']
args = [
"-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",

View File

@ -1,6 +1,5 @@
kcmd_args = ["init=/bin/busybox", "path=/usr/local/bin"]
init_args = ["sh", "-l"]
initramfs="./build/initramfs.cpio.gz"
[boot]
loader = "grub"
@ -9,7 +8,6 @@ grub-mkrescue = "/usr/bin/grub-mkrescue"
ovmf = "/usr/bin/ovmf"
[qemu]
path = "/usr/bin/qemu-system-x86_64"
machine = "q35"
args = [
"-enable-kvm",

View File

@ -23,9 +23,6 @@ fn deserialize_osdk_manifest() {
let content = include_str!("OSDK.toml.full");
let osdk_manifest: TomlManifest = toml::from_str(content).unwrap();
assert!(osdk_manifest.boot.grub_mkrescue.unwrap() == PathBuf::from("/usr/bin/grub-mkrescue"));
assert!(
osdk_manifest.qemu.default.path.unwrap() == PathBuf::from("/usr/bin/qemu-system-x86_64")
);
}
#[test]
@ -65,7 +62,7 @@ fn load_manifest_conditional() {
fs::write(path, contents).unwrap();
let cargo_args = CargoArgs {
release: true,
profile: "release".to_string(),
features: vec![String::from("iommu")],
};
cargo_osdk_build(PathBuf::from(workspace).join(kernel_name), &cargo_args);
@ -118,51 +115,39 @@ fn conditional_manifest() {
.cfg
.as_ref()
.unwrap()
.contains_key(&String::from("cfg(feature=\"intel_tdx\")")));
.contains_key(&String::from("cfg(select=\"intel_tdx\")")));
assert!(toml_manifest
.qemu
.cfg
.as_ref()
.unwrap()
.contains_key(&String::from("cfg(feature=\"iommu\")")));
.contains_key(&String::from("cfg(select=\"iommu\")")));
// No features
let features: &[&str] = &[];
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), features);
assert_eq!(
manifest.qemu.path,
Some(PathBuf::from("/usr/bin/qemu-system-x86_64"))
);
// Default selection
let selection: Option<&str> = None;
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), selection);
assert!(manifest.qemu.args.contains(&String::from(
"-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off"
)));
// Iommu features
let features: &[&str] = &["iommu"];
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), features);
assert_eq!(
manifest.qemu.path,
Some(PathBuf::from("/usr/bin/qemu-system-x86_64"))
);
// Iommu
let selection: Option<&str> = Some("iommu");
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), selection);
assert!(manifest
.qemu
.args
.contains(&String::from("-device ioh3420,id=pcie.0,chassis=1")));
// Tdx features
let features: &[&str] = &["intel_tdx"];
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), features);
assert_eq!(
manifest.qemu.path,
Some(PathBuf::from("/usr/local/sbin/qemu-kvm"))
);
// Tdx
let selection: Option<&str> = Some("intel_tdx");
let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), selection);
assert!(manifest.qemu.args.is_empty());
}
#[test]
fn extract_feature() {
let text = "cfg(feature=\"abc123_\")";
let captures = FEATURE_REGEX.captures(text).unwrap();
let feature = captures.name("feature").unwrap().as_str();
assert_eq!(feature, "abc123_");
fn extract_selection() {
let text = "cfg(select=\"abc123_\")";
let captures = SELECT_REGEX.captures(text).unwrap();
let selection = captures.name("select").unwrap().as_str();
assert_eq!(selection, "abc123_");
}

View File

@ -19,7 +19,12 @@ pub fn cargo_osdk<T: AsRef<OsStr>, I: IntoIterator<Item = T>>(args: I) -> Comman
}
pub fn assert_success(output: &Output) {
assert!(output.status.success());
assert!(
output.status.success(),
"Command output {:#?} seems failed, stderr:\n {}",
output,
String::from_utf8_lossy(&output.stderr)
);
}
pub fn assert_stdout_contains_msg(output: &Output, msg: &str) {
@ -37,7 +42,11 @@ pub fn create_workspace(workspace_name: &str, members: &[&str]) {
.iter()
.map(|member| toml::Value::String(member.to_string()))
.collect();
let exclude = toml::Value::Array(vec![toml::Value::String("target/osdk/base".to_string())]);
table.insert("members".to_string(), toml::Value::Array(members));
table.insert("exclude".to_string(), exclude);
table
};

View File

@ -1,13 +1,16 @@
// SPDX-License-Identifier: MPL-2.0
use std::{ffi::OsStr, path::Path, process::Command};
use std::{ffi::OsStr, path::{Path, PathBuf}, process::Command};
use crate::{error::Errno, error_msg};
// FIXME: Crates belonging to Asterinas require a different dependency format. The dependency
// should be specified using a relative path instead of a URL.
// TODO: The dependency should be corrected when this branch is merged.
pub const ASTER_FRAME_DEP: &str =
"aster-frame = { git = \"https://github.com/asterinas/asterinas\", rev = \"f2f991b\" }";
"aster-frame = { git = \"https://github.com/junyang-zh/asterinas\", branch = \"osdk\" }";
pub const KTEST_DEP: &str =
"ktest = { git = \"https://github.com/junyang-zh/asterinas\", branch = \"osdk\" }";
fn cargo() -> Command {
Command::new("cargo")
@ -24,10 +27,13 @@ pub fn cargo_new_lib(crate_name: &str) {
}
}
/// Get the Cargo metadata parsed from the standard output
/// of the invocation of Cargo. Return `None` if the command
/// fails or the `current_dir` is not in a Cargo workspace.
pub fn get_cargo_metadata<S1: AsRef<Path>, S2: AsRef<OsStr>>(
current_dir: Option<S1>,
cargo_args: Option<&[S2]>,
) -> serde_json::Value {
) -> Option<serde_json::Value> {
let mut command = cargo();
command.args(["metadata", "--no-deps", "--format-version", "1"]);
@ -42,13 +48,48 @@ pub fn get_cargo_metadata<S1: AsRef<Path>, S2: AsRef<OsStr>>(
let output = command.output().unwrap();
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
eprintln!("{}", &stderr);
error_msg!("Failed to get metadata for newly created crate");
std::process::exit(Errno::GetMetadata as _);
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
serde_json::from_str(&stdout).unwrap()
Some(serde_json::from_str(&stdout).unwrap())
}
pub fn get_target_directory() -> PathBuf {
let metadata = get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap();
metadata
.get("target_directory")
.unwrap()
.as_str()
.unwrap()
.into()
}
pub struct CrateInfo {
pub name: String,
pub version: String,
pub path: String,
}
pub fn get_current_crate_info() -> CrateInfo {
let metadata = get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap();
let default_members = metadata.get("workspace_default_members").unwrap();
assert_eq!(default_members.as_array().unwrap().len(), 1);
// The default member string here is in the form of "<crate_name> <crate_version> (path+file://<crate_path>)"
let default_member = default_members[0]
.as_str()
.unwrap()
.split(" ")
.collect::<Vec<&str>>();
let name = default_member[0].to_string();
let version = default_member[1].to_string();
let path = default_member[2]
.trim_start_matches("(path+file://")
.trim_end_matches(")")
.to_string();
CrateInfo {
name,
version,
path,
}
}

22
osdk/src/vm_image.rs Normal file
View File

@ -0,0 +1,22 @@
// SPDX-License-Identifier: MPL-2.0
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AsterVmImage {
pub path: PathBuf,
pub typ: AsterVmImageType,
pub aster_version: String,
pub sha256sum: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AsterVmImageType {
GrubIso(AsterGrubIsoImageMeta),
// TODO: add more vm image types such as qcow2, etc.
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AsterGrubIsoImageMeta {
pub grub_version: String,
}

View File

@ -1,12 +0,0 @@
[package]
name = "aster-runner"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.32"
clap = { version = "4.3.19", features = ["derive"] }
linux-bzimage-builder = { path = "../framework/libs/linux-bzimage/builder" }
rand = "0.8.5"
xmas-elf = "0.8.0"

View File

@ -1,70 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
//! Providing the utility to run the GDB scripts for the runner.
use std::{fs::OpenOptions, io::Write, path::PathBuf, process::Command};
use crate::qemu_grub_efi;
/// Run a GDB client.
///
/// If argument `gdb_grub` is set true, it will run GRUB's gdb script.
///
/// Make sure to set GRUB_PREFIX to the actual GRUB you are using.
///
/// When debugging grub, the OVMF firmware will load the grub kernel at an
/// address unknown at the moment. You should use the debug message from our
/// custom built OVMF firmware and read the entrypoint address
/// (often `0x0007E684000`). Then use the following GDB command to load symbols:
/// `dynamic_load_symbols ${ENTRY_ADDRESS}`.
/// During each run, the address is unlikely to change. But the address will
/// depend on the versions of grub or OVMF.
///
/// Also, do `set breakpoint pending on` when you want to break on GRUB modules.
pub fn run_gdb_client(path: &PathBuf, gdb_grub: bool) {
let path = std::fs::canonicalize(path).unwrap();
let mut gdb_cmd = Command::new("gdb");
// Set the architecture, otherwise GDB will complain about.
gdb_cmd.arg("-ex").arg("set arch i386:x86-64:intel");
let grub_script = "/tmp/aster-gdb-grub-script";
if gdb_grub {
let grub_dir = PathBuf::from(qemu_grub_efi::GRUB_PREFIX)
.join("lib")
.join("grub")
.join(qemu_grub_efi::GRUB_VERSION);
// Load symbols from GRUB using the provided grub gdb script.
// Read the contents from `gdb_grub` and
// replace the lines containing "target remote :1234".
gdb_cmd.current_dir(&grub_dir);
let grub_script_content = std::fs::read_to_string(grub_dir.join("gdb_grub")).unwrap();
let lines = grub_script_content.lines().collect::<Vec<_>>();
let mut f = OpenOptions::new()
.write(true)
.create(true)
.open(grub_script)
.unwrap();
for line in lines {
if line.contains("target remote :1234") {
// Connect to the GDB server.
writeln!(f, "target remote /tmp/aster-gdb-socket").unwrap();
} else {
writeln!(f, "{}", line).unwrap();
}
}
gdb_cmd.arg("-x").arg(grub_script);
} else {
// Load symbols from the kernel image.
gdb_cmd.arg("-ex").arg(format!("file {}", path.display()));
// Connect to the GDB server.
gdb_cmd
.arg("-ex")
.arg("target remote /tmp/aster-gdb-socket");
}
// Connect to the GDB server and run.
println!("running:{:#?}", gdb_cmd);
gdb_cmd.status().unwrap();
if gdb_grub {
// Clean the temporary script file then return.
std::fs::remove_file(grub_script).unwrap();
}
}

View File

@ -1,80 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
use std::{
fs::OpenOptions,
io::{Seek, SeekFrom, Write},
path::PathBuf,
process::Command,
};
pub const MACHINE_ARGS: &[&str] = &[
"-machine",
"microvm,pit=on,pic=off,rtc=on",
"-nodefaults",
"-no-user-config",
];
pub const DEVICE_ARGS: &[&str] = &[
"-device",
"virtio-blk-device,drive=x0",
"-device",
"virtio-keyboard-device",
"-device",
"virtio-net-device,netdev=net01",
"-device",
"virtio-serial-device",
"-device",
"virtconsole,chardev=mux",
];
pub fn create_bootdev_image(path: PathBuf) -> PathBuf {
let dir = path.parent().unwrap();
let name = path.file_name().unwrap().to_str().unwrap().to_string();
let elf_path = dir.join(name.clone()).to_str().unwrap().to_string();
let strip_elf_path = dir
.join(name.clone() + ".stripped.elf")
.to_str()
.unwrap()
.to_string();
// We use rust-strip to reduce the kernel image size.
let status = Command::new("rust-strip")
.arg(&elf_path)
.arg("-o")
.arg(&strip_elf_path)
.status();
match status {
Ok(status) => {
if !status.success() {
panic!("Failed to strip kernel elf.");
}
}
Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => panic!(
"Not find rust-strip command,
try `cargo install cargo-binutils` and then rerun."
),
_ => panic!("Strip kernel elf failed, err:{:#?}", err),
},
}
// Because QEMU denies a x86_64 multiboot ELF file (GRUB2 accept it, btw),
// modify `em_machine` to pretend to be an x86 (32-bit) ELF image,
//
// https://github.com/qemu/qemu/blob/950c4e6c94b15cd0d8b63891dddd7a8dbf458e6a/hw/i386/multiboot.c#L197
// Set EM_386 (0x0003) to em_machine.
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(&strip_elf_path)
.unwrap();
let bytes: [u8; 2] = [0x03, 0x00];
file.seek(SeekFrom::Start(18)).unwrap();
file.write_all(&bytes).unwrap();
file.flush().unwrap();
strip_elf_path.into()
}

View File

@ -1,4 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
pub mod microvm;
pub mod qemu_grub_efi;

View File

@ -1,194 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
use std::{
fs,
io::Read,
path::{Path, PathBuf},
};
use linux_bzimage_builder::{make_bzimage, BzImageType};
use crate::BootProtocol;
macro_rules! ovmf_prefix {
() => {
// There are 3 optional OVMF builds at your service in the dev image
"/root/ovmf/release/"
// "/root/ovmf/debug/"
// "/usr/share/OVMF/"
};
}
pub const MACHINE_ARGS: &[&str] = &[
"-machine",
"q35,kernel-irqchip=split",
"-drive",
concat!(
"if=pflash,format=raw,unit=0,readonly=on,file=",
ovmf_prefix!(),
"OVMF_CODE.fd"
),
"-drive",
concat!(
"if=pflash,format=raw,unit=1,file=",
ovmf_prefix!(),
"OVMF_VARS.fd"
),
];
pub const NOIOMMU_DEVICE_ARGS: &[&str] = &[
"-device",
"virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off",
"-device",
"virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
"-device",
"virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
"-device",
"virtio-serial-pci,disable-legacy=on,disable-modern=off",
"-device",
"virtconsole,chardev=mux",
];
pub const IOMMU_DEVICE_ARGS: &[&str] = &[
"-device",
"virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device",
"virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device",
"virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device",
"virtio-serial-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device",
"virtconsole,chardev=mux",
"-device",
"intel-iommu,intremap=on,device-iotlb=on",
"-device",
"ioh3420,id=pcie.0,chassis=1",
];
/// The default GRUB tools used.
pub const GRUB_PREFIX: &str = "/usr";
/// The GRUB version that defaults to use EFI handover. Which is the Debian APT version.
pub const GRUB_PREFIX_EFI_HANDOVER: &str = "/usr";
/// The GRUB version that uses Loadfile2 and has a fallback to use legacy boot. Which is the custom built upstream 2.12 verion.
pub const GRUB_PREFIX_EFI_AND_LEGACY: &str = "/usr/local/grub";
pub const GRUB_VERSION: &str = "x86_64-efi";
pub fn create_bootdev_image(
kernel_elf_path: PathBuf,
initramfs_path: PathBuf,
grub_cfg: String,
protocol: BootProtocol,
) -> PathBuf {
let target_dir = kernel_elf_path.parent().unwrap();
let iso_root = target_dir.join("iso_root");
// Clear or make the iso dir.
if iso_root.exists() {
fs::remove_dir_all(&iso_root).unwrap();
}
fs::create_dir_all(iso_root.join("boot").join("grub")).unwrap();
// Copy the initramfs to the boot directory.
fs::copy(
initramfs_path,
iso_root.join("boot").join("initramfs.cpio.gz"),
)
.unwrap();
let target_path = match protocol {
BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => {
let image_type = match protocol {
BootProtocol::LinuxLegacy32 => BzImageType::Legacy32,
BootProtocol::LinuxEfiHandover64 => BzImageType::Efi64,
_ => unreachable!(),
};
let setup_src = Path::new("framework/libs/linux-bzimage/setup");
let setup_out_dir = Path::new("target/linux-bzimage-setup");
// Make the `bzImage`-compatible kernel image and place it in the boot directory.
let target_path = iso_root.join("boot").join("asterinaz");
println!("[aster-runner] Building bzImage.");
make_bzimage(
&target_path,
image_type,
&kernel_elf_path.as_path(),
&setup_src,
&setup_out_dir,
);
target_path
}
BootProtocol::Multiboot | BootProtocol::Multiboot2 => {
// Copy the kernel image to the boot directory.
let target_path = iso_root.join("boot").join("atserinas");
fs::copy(&kernel_elf_path, &target_path).unwrap();
target_path
}
};
let target_name = target_path.file_name().unwrap().to_str().unwrap();
// Write the grub.cfg file
let grub_cfg_path = iso_root.join("boot").join("grub").join("grub.cfg");
fs::write(&grub_cfg_path, grub_cfg).unwrap();
// Make the boot device CDROM image.
let iso_path = target_dir.join(target_name.to_string() + ".iso");
let grub_mkrescue_bin = match protocol {
BootProtocol::LinuxLegacy32 => PathBuf::from(GRUB_PREFIX_EFI_AND_LEGACY),
BootProtocol::LinuxEfiHandover64 => PathBuf::from(GRUB_PREFIX_EFI_HANDOVER),
BootProtocol::Multiboot | BootProtocol::Multiboot2 => PathBuf::from(GRUB_PREFIX),
}
.join("bin")
.join("grub-mkrescue");
let mut cmd = std::process::Command::new(grub_mkrescue_bin.as_os_str());
cmd.arg("--output").arg(&iso_path).arg(iso_root.as_os_str());
if !cmd.status().unwrap().success() {
panic!("Failed to run `{:?}`.", cmd);
}
iso_path.into()
}
pub fn generate_grub_cfg(
template_filename: &str,
kcmdline: &str,
skip_grub_menu: bool,
protocol: BootProtocol,
) -> String {
let mut buffer = String::new();
// Read the contents of the file.
fs::File::open(template_filename)
.unwrap()
.read_to_string(&mut buffer)
.unwrap();
// Delete the first two lines that notes the file a template file.
let buffer = buffer.lines().skip(2).collect::<Vec<&str>>().join("\n");
// Set the timout style and timeout.
let buffer = buffer
.replace(
"#GRUB_TIMEOUT_STYLE#",
if skip_grub_menu { "hidden" } else { "menu" },
)
.replace("#GRUB_TIMEOUT#", if skip_grub_menu { "0" } else { "1" });
// Replace all occurrences of "#KERNEL_COMMAND_LINE#" with the desired value.
let buffer = buffer.replace("#KERNEL_COMMAND_LINE#", kcmdline);
// Replace the grub commands according to the protocol selected.
let buffer = match protocol {
BootProtocol::Multiboot => buffer
.replace("#GRUB_CMD_KERNEL#", "multiboot")
.replace("#KERNEL#", "/boot/atserinas")
.replace("#GRUB_CMD_INITRAMFS#", "module --nounzip"),
BootProtocol::Multiboot2 => buffer
.replace("#GRUB_CMD_KERNEL#", "multiboot2")
.replace("#KERNEL#", "/boot/atserinas")
.replace("#GRUB_CMD_INITRAMFS#", "module2 --nounzip"),
BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => buffer
.replace("#GRUB_CMD_KERNEL#", "linux")
.replace("#KERNEL#", "/boot/asterinaz")
.replace("#GRUB_CMD_INITRAMFS#", "initrd"),
};
buffer
}

View File

@ -1,234 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
//! aster-runner is the Asterinas runner script to ease the pain of running
//! and testing Asterinas inside a QEMU VM. It should be built and run as the
//! cargo runner: https://doc.rust-lang.org/cargo/reference/config.html
//!
//! The runner will generate the filesystem image for starting Asterinas. If
//! we should use the runner in the default mode, which invokes QEMU with
//! a GRUB boot device image, the runner would be responsible for generating
//! the appropriate kernel image and the boot device image. It also supports
//! to directly boot the kernel image without GRUB using the QEMU microvm
//! machine type.
//!
pub mod gdb;
pub mod machine;
use std::{
path::{Path, PathBuf},
process::Command,
};
use clap::{Parser, ValueEnum};
use crate::machine::{microvm, qemu_grub_efi};
#[derive(Debug, Clone, Copy, PartialEq, ValueEnum)]
enum BootMethod {
QemuGrub,
Microvm,
}
#[derive(Debug, Clone, Copy, PartialEq, ValueEnum)]
pub enum BootProtocol {
Multiboot,
Multiboot2,
LinuxLegacy32,
LinuxEfiHandover64,
}
/// The CLI of this runner.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
// Positional arguments.
/// The Asterinas binary path.
path: PathBuf,
/// Provide the kernel commandline, which specifies
/// the init process.
kcmdline: String,
// Optional arguments.
/// Boot method. Can be one of the following items:
/// - `qemu-grub`;
/// - `microvm`.
#[arg(long, value_enum, default_value_t = BootMethod::QemuGrub)]
boot_method: BootMethod,
/// Boot protocol. Can be one of the following items:
/// - `multiboot`;
/// - `multiboot2`;
/// - `linux-legacy32`;
/// - `linux-efi-handover64`.
#[arg(long, value_enum, default_value_t = BootProtocol::Multiboot2)]
boot_protocol: BootProtocol,
/// Enable KVM when running QEMU.
#[arg(long, default_value_t = false)]
enable_kvm: bool,
/// Emulate Intel IOMMU by QEMU.
#[arg(long, default_value_t = false)]
emulate_iommu: bool,
/// Run QEMU as a GDB server.
#[arg(long, default_value_t = false)]
halt_for_gdb: bool,
/// Boot without displaying the GRUB menu.
#[arg(long, default_value_t = false)]
skip_grub_menu: bool,
/// Run a GDB client instead of running the kernel.
#[arg(long, default_value_t = false)]
run_gdb_client: bool,
}
pub const COMMON_ARGS: &[&str] = &[
"--no-reboot",
"-cpu",
"Icelake-Server,+x2apic",
"-m",
"2G",
"-nographic", // TODO: figure out why grub can't shown up without it
"-serial",
"chardev:mux",
"-monitor",
"chardev:mux",
"-chardev",
"stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display",
"none",
"-device",
"isa-debug-exit,iobase=0xf4,iosize=0x04",
"-object",
"filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
];
pub fn random_hostfwd_ports() -> (u16, u16) {
let start = 32768u16;
let end = 61000u16;
let port1 = rand::random::<u16>() % (end - 1 - start) + start;
let port2 = rand::random::<u16>() % (end - port1) + port1;
(port1, port2)
}
pub const GDB_ARGS: &[&str] = &[
"-chardev",
"socket,path=/tmp/aster-gdb-socket,server=on,wait=off,id=gdb0",
"-gdb",
"chardev:gdb0",
"-S",
];
fn main() {
let args = Args::parse();
if args.run_gdb_client {
let gdb_grub = args.boot_method == BootMethod::QemuGrub;
// You should comment out the next line if you want to debug grub instead
// of the kernel because this argument is not exposed by runner CLI.
let gdb_grub = gdb_grub && false;
gdb::run_gdb_client(&args.path, gdb_grub);
return;
}
let mut qemu_cmd = Command::new("qemu-system-x86_64");
qemu_cmd.args(COMMON_ARGS);
qemu_cmd.arg("-netdev");
let (port1, port2) = random_hostfwd_ports();
qemu_cmd.arg(format!(
"user,id=net01,hostfwd=tcp::{}-:22,hostfwd=tcp::{}-:8080",
port1, port2
));
println!(
"[aster-runner] Binding host ports to guest ports: ({} -> {}); ({} -> {}).",
port1, 22, port2, 8080
);
if args.halt_for_gdb {
if args.enable_kvm {
println!("[aster-runner] Can't enable KVM when running QEMU as a GDB server. Abort.");
return;
}
qemu_cmd.args(GDB_ARGS);
}
if args.enable_kvm {
qemu_cmd.arg("-enable-kvm");
}
// Add machine-specific arguments
if args.boot_method == BootMethod::QemuGrub {
qemu_cmd.args(qemu_grub_efi::MACHINE_ARGS);
} else if args.boot_method == BootMethod::Microvm {
qemu_cmd.args(microvm::MACHINE_ARGS);
}
// Add device arguments
if args.boot_method == BootMethod::Microvm {
qemu_cmd.args(microvm::DEVICE_ARGS);
} else if args.emulate_iommu {
qemu_cmd.args(qemu_grub_efi::IOMMU_DEVICE_ARGS);
} else {
qemu_cmd.args(qemu_grub_efi::NOIOMMU_DEVICE_ARGS);
}
// TODO: Add arguments to the runner CLI tool so that the user can specify
// a list of disk drives, each of which may be in a different FS format.
let ext2_image = get_fs_image(&PathBuf::from("regression/build/ext2.img"), 0);
qemu_cmd.arg("-drive");
qemu_cmd.arg(ext2_image);
if args.boot_method == BootMethod::Microvm {
let image = microvm::create_bootdev_image(args.path);
qemu_cmd.arg("-kernel");
qemu_cmd.arg(image.as_os_str());
qemu_cmd.arg("-append");
qemu_cmd.arg(&args.kcmdline);
qemu_cmd.arg("-initrd");
qemu_cmd.arg("regression/build/initramfs.cpio.gz");
} else if args.boot_method == BootMethod::QemuGrub {
let grub_cfg = qemu_grub_efi::generate_grub_cfg(
"runner/grub/grub.cfg.template",
&args.kcmdline,
args.skip_grub_menu,
args.boot_protocol,
);
let initramfs_path = PathBuf::from("regression/build/initramfs.cpio.gz");
let bootdev_image = qemu_grub_efi::create_bootdev_image(
args.path,
initramfs_path,
grub_cfg,
args.boot_protocol,
);
qemu_cmd.arg("-cdrom");
qemu_cmd.arg(bootdev_image.as_os_str());
}
println!("[aster-runner] Running: {:#?}", qemu_cmd);
let exit_status = qemu_cmd.status().unwrap();
if !exit_status.success() {
// FIXME: Exit code manipulation is not needed when using non-x86 QEMU
let qemu_exit_code = exit_status.code().unwrap();
let kernel_exit_code = qemu_exit_code >> 1;
match kernel_exit_code {
0x10 /*aster_frame::QemuExitCode::Success*/ => { std::process::exit(0); },
0x20 /*aster_frame::QemuExitCode::Failed*/ => { std::process::exit(1); },
_ /* unknown, e.g., a triple fault */ => { std::process::exit(2) },
}
}
}
pub fn get_fs_image(path: &Path, drive_id: u32) -> String {
if !path.exists() {
panic!("can not find the fs image")
}
format!(
"file={},if=none,format=raw,id=x{}",
path.to_string_lossy(),
drive_id
)
}

View File

@ -43,6 +43,7 @@ extern crate alloc;
extern crate lru;
#[macro_use]
extern crate controlled;
#[cfg(ktest)]
#[macro_use]
extern crate ktest;
#[macro_use]

View File

@ -579,7 +579,7 @@ pub fn current() -> Arc<Process> {
}
}
#[if_cfg_ktest]
#[cfg(ktest)]
mod test {
use super::*;

View File

@ -133,7 +133,7 @@ impl<R> VmarChildOptions<R> {
}
}
#[if_cfg_ktest]
#[cfg(ktest)]
mod test {
use aster_frame::vm::VmIo;
use aster_rights::Full;

View File

@ -515,7 +515,7 @@ impl VmoChildType for VmoSliceChild {}
pub struct VmoCowChild;
impl VmoChildType for VmoCowChild {}
#[if_cfg_ktest]
#[cfg(ktest)]
mod test {
use aster_frame::vm::VmIo;
use aster_rights::Full;

View File

@ -5,8 +5,6 @@
use core::ops::Mul;
use ktest::if_cfg_ktest;
/// A `Coeff` is used to do a fraction multiplication operation with an unsigned integer.
/// It can achieve accurate and efficient calculation and avoid numeric overflow at the same time.
///
@ -127,7 +125,7 @@ impl Mul<u32> for Coeff {
}
}
#[if_cfg_ktest]
#[cfg(ktest)]
mod test {
use ktest::ktest;

View File

@ -1,2 +1,2 @@
[workspace]
members = ["foo", "bar"]
members = ["foo1", "bar1"]

View File

@ -1,10 +1,10 @@
[components]
foo = { name = "foo" }
bar = { name = "bar" }
foo1 = { name = "foo1" }
bar1 = { name = "bar1" }
[whitelist]
[whitelist.foo.foo_add]
bar = true
[whitelist.foo1.foo_add]
bar1 = true
[whitelist.foo.FOO_ITEM]
bar = true
[whitelist.foo1.FOO_ITEM]
bar1 = true

View File

@ -1,5 +0,0 @@
pub static BAR: &'static usize = &foo::FOO_ITEM;
pub fn add(left: usize, right: usize) -> usize {
foo::foo_add(left, right)
}

View File

@ -1,9 +1,9 @@
[package]
name = "bar"
name = "bar1"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
foo = {path = "../foo"}
foo1 = {path = "../foo1"}

View File

@ -0,0 +1,5 @@
pub static BAR: &'static usize = &foo1::FOO_ITEM;
pub fn add(left: usize, right: usize) -> usize {
foo1::foo_add(left, right)
}

View File

@ -11,7 +11,7 @@ mod test_utils;
fn trait_method() {
let stderr = run_cargo_component_cmd!();
assert!(stderr.contains("access controlled entry point is disallowed"));
assert!(stderr.contains("access foo::Foo::method in bar"));
assert!(stderr.contains("access foo::FooTrait::trait_associate_fn in bar"));
assert!(stderr.contains("access foo::FooTrait::trait_method in bar"));
assert!(stderr.contains("access foo2::Foo::method in bar2"));
assert!(stderr.contains("access foo2::FooTrait::trait_associate_fn in bar2"));
assert!(stderr.contains("access foo2::FooTrait::trait_method in bar2"));
}

View File

@ -1,2 +1,2 @@
[workspace]
members = ["foo", "bar"]
members = ["foo2", "bar2"]

View File

@ -1,19 +1,19 @@
[components]
foo = { name = "foo" }
bar = { name = "bar" }
foo2 = { name = "foo2" }
bar2 = { name = "bar2" }
[whitelist]
[whitelist.foo.FooTrait.trait_method]
bar = false
[whitelist.foo2.FooTrait.trait_method]
bar2 = false
[whitelist.foo.FooTrait.trait_associate_fn]
bar = false
[whitelist.foo2.FooTrait.trait_associate_fn]
bar2 = false
[whitelist.foo.Foo.method]
bar = false
[whitelist.foo2.Foo.method]
bar2 = false
[whitelist.foo.ObjectSafeTrait.get]
bar = true
[whitelist.foo2.ObjectSafeTrait.get]
bar2 = true
[whitelist.foo.Foo.associate_fn]
bar = true
[whitelist.foo2.Foo.associate_fn]
bar2 = true

View File

@ -1,9 +1,9 @@
[package]
name = "bar"
name = "bar2"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
foo = {path = "../foo"}
foo2 = {path = "../foo2"}

View File

@ -1,4 +1,4 @@
use foo::{ObjectSafeTrait, Foo, FooTrait};
use foo2::{ObjectSafeTrait, Foo, FooTrait};
pub fn method() {
let foo_struct = Foo::associate_fn();

View File

@ -12,6 +12,6 @@ mod test_utils;
fn violate_policy() {
let stderr = run_cargo_component_cmd!();
assert!(stderr.contains("access controlled entry point is disallowed"));
assert!(stderr.contains("access foo::foo_add in bar"));
assert!(stderr.contains("access foo::FOO_ITEM in bar"));
assert!(stderr.contains("access foo3::foo_add in bar3"));
assert!(stderr.contains("access foo3::FOO_ITEM in bar3"));
}

View File

@ -1,2 +1,2 @@
[workspace]
members = ["foo", "bar"]
members = ["foo3", "bar3"]

View File

@ -1,5 +1,5 @@
[components]
foo = { name = "foo" }
bar = { name = "bar" }
foo3 = { name = "foo3" }
bar3 = { name = "bar3" }
[whitelist]

View File

@ -1,5 +0,0 @@
pub static BAR: &'static usize = &foo::FOO_ITEM;
pub fn add(left: usize, right: usize) -> usize {
foo::foo_add(left, right)
}

View File

@ -1,9 +1,9 @@
[package]
name = "bar"
name = "bar3"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
foo = {path = "../foo"}
foo3 = {path = "../foo3"}

View File

@ -0,0 +1,5 @@
pub static BAR: &'static usize = &foo3::FOO_ITEM;
pub fn add(left: usize, right: usize) -> usize {
foo3::foo_add(left, right)
}

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
use std::{collections::HashMap, fs::File, io::Read, ops::Add, process::Command, str::FromStr};
use std::{collections::HashMap, fs::File, io::Read, ops::Add, path::PathBuf, process::Command, str::FromStr};
use json::JsonValue;
use proc_macro2::{Group, TokenStream};
@ -11,6 +11,7 @@ use crate::COMPONENT_FILE_NAME;
#[derive(Debug)]
pub struct ComponentInfo {
name: String,
/// The absolute path to the component
path: String,
priority: u16,
}
@ -87,13 +88,14 @@ pub fn component_generate() -> Vec<ComponentInfo> {
};
let component_info = ComponentInfo {
name: package["name"].as_str().unwrap().to_string(),
path: path.to_owned(),
path: PathBuf::from(&workspace_root).join(path).to_str().unwrap().to_string(),
priority: *mapping
.get(&package["name"].as_str().unwrap().to_string())
.unwrap(),
};
components_info.push(component_info)
}
components_info
}

Some files were not shown because too many files have changed in this diff Show More