mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-10 13:56:48 +00:00
Include the 100 lines kernel in CI
This commit is contained in:
parent
cd2b305fa8
commit
72e726295f
@ -7,23 +7,7 @@ we will show a new kernel in about 100 lines of safe Rust.
|
|||||||
Our new kernel will be able to run the following Hello World program.
|
Our new kernel will be able to run the following Hello World program.
|
||||||
|
|
||||||
```s
|
```s
|
||||||
.global _start # entry point
|
{{#include ../../../osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/hello.S}}
|
||||||
.section .text # code section
|
|
||||||
_start:
|
|
||||||
mov $1, %rax # syscall number of write
|
|
||||||
mov $1, %rdi # stdout
|
|
||||||
mov $message, %rsi # address of message
|
|
||||||
mov $message_end, %rdx
|
|
||||||
sub %rsi, %rdx # calculate message len
|
|
||||||
syscall
|
|
||||||
mov $60, %rax # syscall number of exit, move it to rax
|
|
||||||
mov $0, %rdi # exit code, move it to rdi
|
|
||||||
syscall
|
|
||||||
|
|
||||||
.section .rodata # read only data section
|
|
||||||
message:
|
|
||||||
.ascii "Hello, world\n"
|
|
||||||
message_end:
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The assembly program above can be compiled with the following command.
|
The assembly program above can be compiled with the following command.
|
||||||
@ -42,131 +26,5 @@ Comments are added
|
|||||||
to highlight how the APIs of Asterinas OSTD enable safe kernel development.
|
to highlight how the APIs of Asterinas OSTD enable safe kernel development.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#![no_std]
|
{{#include ../../../osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs}}
|
||||||
|
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
use align_ext::AlignExt;
|
|
||||||
use core::str;
|
|
||||||
|
|
||||||
use alloc::sync::Arc;
|
|
||||||
use alloc::vec;
|
|
||||||
|
|
||||||
use ostd::cpu::UserContext;
|
|
||||||
use ostd::prelude::*;
|
|
||||||
use ostd::task::{Task, TaskOptions};
|
|
||||||
use ostd::user::{ReturnReason, UserMode, UserSpace};
|
|
||||||
use ostd::mm::{PageFlags, PAGE_SIZE, Vaddr, FrameAllocOptions, VmIo, VmMapOptions, VmSpace};
|
|
||||||
|
|
||||||
/// The kernel's boot and initialization process is managed by Asterinas OSTD.
|
|
||||||
/// After the process is done, the kernel's execution environment
|
|
||||||
/// (e.g., stack, heap, tasks) will be ready for use and the entry function
|
|
||||||
/// labeled as `#[ostd::main]` will be called.
|
|
||||||
#[ostd::main]
|
|
||||||
pub fn main() {
|
|
||||||
let program_binary = include_bytes!("../hello_world");
|
|
||||||
let user_space = create_user_space(program_binary);
|
|
||||||
let user_task = create_user_task(Arc::new(user_space));
|
|
||||||
user_task.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_user_space(program: &[u8]) -> UserSpace {
|
|
||||||
let user_pages = {
|
|
||||||
let nframes = program.len().align_up(PAGE_SIZE) / PAGE_SIZE;
|
|
||||||
let vm_frames = FrameAllocOptions::new(nframes).alloc().unwrap();
|
|
||||||
// Phyiscal memory pages can be only accessed
|
|
||||||
// via the Frame abstraction.
|
|
||||||
vm_frames.write_bytes(0, program).unwrap();
|
|
||||||
vm_frames
|
|
||||||
};
|
|
||||||
let user_address_space = {
|
|
||||||
const MAP_ADDR: Vaddr = 0x0040_0000; // The map addr for statically-linked executable
|
|
||||||
|
|
||||||
// The page table of the user space can be
|
|
||||||
// created and manipulated safely through
|
|
||||||
// the VmSpace abstraction.
|
|
||||||
let vm_space = VmSpace::new();
|
|
||||||
let mut options = VmMapOptions::new();
|
|
||||||
options.addr(Some(MAP_ADDR)).flags(PageFlags::RWX);
|
|
||||||
vm_space.map(user_pages, &options).unwrap();
|
|
||||||
vm_space
|
|
||||||
};
|
|
||||||
let user_cpu_state = {
|
|
||||||
const ENTRY_POINT: Vaddr = 0x0040_1000; // The entry point for statically-linked executable
|
|
||||||
|
|
||||||
// The user-space CPU states can be initialized
|
|
||||||
// to arbitrary values via the UserContext
|
|
||||||
// abstraction.
|
|
||||||
let mut user_cpu_state = UserContext::default();
|
|
||||||
user_cpu_state.set_rip(ENTRY_POINT);
|
|
||||||
user_cpu_state
|
|
||||||
};
|
|
||||||
UserSpace::new(user_address_space, user_cpu_state)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_user_task(user_space: Arc<UserSpace>) -> Arc<Task> {
|
|
||||||
fn user_task() {
|
|
||||||
let current = Task::current();
|
|
||||||
// Switching between user-kernel space is
|
|
||||||
// performed via the UserMode abstraction.
|
|
||||||
let mut user_mode = {
|
|
||||||
let user_space = current.user_space().unwrap();
|
|
||||||
UserMode::new(user_space)
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// The execute method returns when system
|
|
||||||
// calls or CPU exceptions occur or some
|
|
||||||
// events specified by the kernel occur.
|
|
||||||
let return_reason = user_mode.execute(|| false);
|
|
||||||
|
|
||||||
// The CPU registers of the user space
|
|
||||||
// can be accessed and manipulated via
|
|
||||||
// the `UserContext` abstraction.
|
|
||||||
let user_context = user_mode.context_mut();
|
|
||||||
if ReturnReason::UserSyscall == return_reason {
|
|
||||||
handle_syscall(user_context, current.user_space().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kernel tasks are managed by OSTD,
|
|
||||||
// while scheduling algorithms for them can be
|
|
||||||
// determined by the users of OSTD.
|
|
||||||
TaskOptions::new(user_task)
|
|
||||||
.user_space(Some(user_space))
|
|
||||||
.data(0)
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_syscall(user_context: &mut UserContext, user_space: &UserSpace) {
|
|
||||||
const SYS_WRITE: usize = 1;
|
|
||||||
const SYS_EXIT: usize = 60;
|
|
||||||
|
|
||||||
match user_context.rax() {
|
|
||||||
SYS_WRITE => {
|
|
||||||
// Access the user-space CPU registers safely.
|
|
||||||
let (_, buf_addr, buf_len) =
|
|
||||||
(user_context.rdi(), user_context.rsi(), user_context.rdx());
|
|
||||||
let buf = {
|
|
||||||
let mut buf = vec![0u8; buf_len];
|
|
||||||
// Copy data from the user space without
|
|
||||||
// unsafe pointer dereferencing.
|
|
||||||
user_space
|
|
||||||
.vm_space()
|
|
||||||
.read_bytes(buf_addr, &mut buf)
|
|
||||||
.unwrap();
|
|
||||||
buf
|
|
||||||
};
|
|
||||||
// Use the console for output safely.
|
|
||||||
println!("{}", str::from_utf8(&buf).unwrap());
|
|
||||||
// Manipulate the user-space CPU registers safely.
|
|
||||||
user_context.set_rax(buf_len);
|
|
||||||
}
|
|
||||||
SYS_EXIT => Task::current().exit(),
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -158,7 +158,9 @@ fn install_setup_with_arch(
|
|||||||
cmd.arg("--force");
|
cmd.arg("--force");
|
||||||
cmd.arg("--root").arg(install_dir.as_ref());
|
cmd.arg("--root").arg(install_dir.as_ref());
|
||||||
cmd.arg("--git").arg(crate::util::ASTER_GIT_LINK);
|
cmd.arg("--git").arg(crate::util::ASTER_GIT_LINK);
|
||||||
cmd.arg("--tag").arg(crate::util::ASTER_GIT_TAG);
|
// FIXME: Uses a fixed tag instaed of relies on remote branch
|
||||||
|
cmd.arg("--tag").arg("v0.5.1");
|
||||||
|
// cmd.arg("--tag").arg(crate::util::ASTER_GIT_TAG);
|
||||||
cmd.arg("--target").arg(match arch {
|
cmd.arg("--target").arg(match arch {
|
||||||
SetupInstallArch::X86_64 => "x86_64-unknown-none",
|
SetupInstallArch::X86_64 => "x86_64-unknown-none",
|
||||||
SetupInstallArch::Other(path) => path.to_str().unwrap(),
|
SetupInstallArch::Other(path) => path.to_str().unwrap(),
|
||||||
|
@ -5,3 +5,4 @@
|
|||||||
mod create_os_projects;
|
mod create_os_projects;
|
||||||
mod test_and_run_projects;
|
mod test_and_run_projects;
|
||||||
mod work_in_workspace;
|
mod work_in_workspace;
|
||||||
|
mod write_a_kernel_in_100_lines;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
|
||||||
fs::{self, OpenOptions},
|
fs::{self, OpenOptions},
|
||||||
io::Write,
|
io::Write,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
@ -21,7 +20,6 @@ fn work_in_workspace() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fs::create_dir_all(&workspace_dir).unwrap();
|
fs::create_dir_all(&workspace_dir).unwrap();
|
||||||
env::set_current_dir(&workspace_dir).unwrap();
|
|
||||||
|
|
||||||
let workspace_toml = include_str!("work_in_workspace_templates/Cargo.toml");
|
let workspace_toml = include_str!("work_in_workspace_templates/Cargo.toml");
|
||||||
fs::write(workspace_dir.join("Cargo.toml"), workspace_toml).unwrap();
|
fs::write(workspace_dir.join("Cargo.toml"), workspace_toml).unwrap();
|
||||||
@ -29,8 +27,14 @@ fn work_in_workspace() {
|
|||||||
// Create a kernel project and a library project
|
// Create a kernel project and a library project
|
||||||
let kernel = "myos";
|
let kernel = "myos";
|
||||||
let module = "mylib";
|
let module = "mylib";
|
||||||
cargo_osdk(&["new", "--kernel", kernel]).ok().unwrap();
|
cargo_osdk(&["new", "--kernel", kernel])
|
||||||
cargo_osdk(&["new", module]).ok().unwrap();
|
.current_dir(&workspace_dir)
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
cargo_osdk(&["new", module])
|
||||||
|
.current_dir(&workspace_dir)
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Add a test function to mylib/src/lib.rs
|
// Add a test function to mylib/src/lib.rs
|
||||||
let module_src_path = workspace_dir.join(module).join("src").join("lib.rs");
|
let module_src_path = workspace_dir.join(module).join("src").join("lib.rs");
|
||||||
@ -75,13 +79,22 @@ fn work_in_workspace() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Run subcommand build & run
|
// Run subcommand build & run
|
||||||
cargo_osdk(&["build"]).ok().unwrap();
|
cargo_osdk(&["build"])
|
||||||
let output = cargo_osdk(&["run"]).output().unwrap();
|
.current_dir(&workspace_dir)
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
let output = cargo_osdk(&["run"])
|
||||||
|
.current_dir(&workspace_dir)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
assert!(stdout.contains("The available memory is"));
|
assert!(stdout.contains("The available memory is"));
|
||||||
|
|
||||||
// Run subcommand test
|
// Run subcommand test
|
||||||
cargo_osdk(&["test"]).ok().unwrap();
|
cargo_osdk(&["test"])
|
||||||
|
.current_dir(&workspace_dir)
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Remove the directory
|
// Remove the directory
|
||||||
fs::remove_dir_all(&workspace_dir).unwrap();
|
fs::remove_dir_all(&workspace_dir).unwrap();
|
||||||
|
65
osdk/tests/examples_in_book/write_a_kernel_in_100_lines.rs
Normal file
65
osdk/tests/examples_in_book/write_a_kernel_in_100_lines.rs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use std::{fs, path::PathBuf, process::Command};
|
||||||
|
|
||||||
|
use assert_cmd::output::OutputOkExt;
|
||||||
|
|
||||||
|
use crate::util::{cargo_osdk, depends_on_local_ostd};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_a_kernel_in_100_lines() {
|
||||||
|
let workdir = "/tmp";
|
||||||
|
let os_name = "kernel_in_100_lines";
|
||||||
|
|
||||||
|
let os_dir = PathBuf::from(workdir).join(os_name);
|
||||||
|
|
||||||
|
if os_dir.exists() {
|
||||||
|
fs::remove_dir_all(&os_dir).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new kernel project
|
||||||
|
cargo_osdk(&["new", "--kernel", os_name])
|
||||||
|
.current_dir(&workdir)
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Depends on local OSTD
|
||||||
|
let manifest_path = os_dir.join("Cargo.toml");
|
||||||
|
depends_on_local_ostd(manifest_path);
|
||||||
|
|
||||||
|
// Copies the kernel content
|
||||||
|
let kernel_contents = include_str!("write_a_kernel_in_100_lines_templates/lib.rs");
|
||||||
|
fs::write(os_dir.join("src").join("lib.rs"), kernel_contents).unwrap();
|
||||||
|
|
||||||
|
// Copies and compiles the user program
|
||||||
|
let user_program_contents = include_str!("write_a_kernel_in_100_lines_templates/hello.S");
|
||||||
|
fs::write(os_dir.join("hello.S"), user_program_contents).unwrap();
|
||||||
|
Command::new("gcc")
|
||||||
|
.args(&["-static", "-nostdlib", "hello.S", "-o", "hello"])
|
||||||
|
.current_dir(&os_dir)
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Adds align ext as the dependency
|
||||||
|
let file_contents = fs::read_to_string(os_dir.join("Cargo.toml")).unwrap();
|
||||||
|
let mut manifest: toml::Table = toml::from_str(&file_contents).unwrap();
|
||||||
|
let dependencies = manifest
|
||||||
|
.get_mut("dependencies")
|
||||||
|
.unwrap()
|
||||||
|
.as_table_mut()
|
||||||
|
.unwrap();
|
||||||
|
dependencies.insert(
|
||||||
|
"align_ext".to_string(),
|
||||||
|
toml::Value::String("0.1.0".to_string()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let new_file_content = manifest.to_string();
|
||||||
|
fs::write(os_dir.join("Cargo.toml"), new_file_content).unwrap();
|
||||||
|
|
||||||
|
// Runs the kernel
|
||||||
|
let output = cargo_osdk(&["run"]).current_dir(&os_dir).ok().unwrap();
|
||||||
|
let stdout = std::str::from_utf8(&output.stdout).unwrap();
|
||||||
|
println!("stdout = {}", stdout);
|
||||||
|
|
||||||
|
fs::remove_dir_all(&os_dir).unwrap();
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
.global _start # entry point
|
||||||
|
.section .text # code section
|
||||||
|
_start:
|
||||||
|
mov $1, %rax # syscall number of write
|
||||||
|
mov $1, %rdi # stdout
|
||||||
|
mov $message, %rsi # address of message
|
||||||
|
mov $message_end, %rdx
|
||||||
|
sub %rsi, %rdx # calculate message len
|
||||||
|
syscall
|
||||||
|
mov $60, %rax # syscall number of exit, move it to rax
|
||||||
|
mov $0, %rdi # exit code, move it to rdi
|
||||||
|
syscall
|
||||||
|
|
||||||
|
.section .rodata # read only data section
|
||||||
|
message:
|
||||||
|
.ascii "Hello, world\n"
|
||||||
|
message_end:
|
@ -0,0 +1,132 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
use align_ext::AlignExt;
|
||||||
|
use core::str;
|
||||||
|
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
use alloc::vec;
|
||||||
|
|
||||||
|
use ostd::arch::qemu::{exit_qemu, QemuExitCode};
|
||||||
|
use ostd::cpu::UserContext;
|
||||||
|
use ostd::mm::{
|
||||||
|
FrameAllocOptions, PageFlags, Vaddr, VmIo, VmMapOptions, VmSpace, VmWriter, PAGE_SIZE,
|
||||||
|
};
|
||||||
|
use ostd::prelude::*;
|
||||||
|
use ostd::task::{Task, TaskOptions};
|
||||||
|
use ostd::user::{ReturnReason, UserMode, UserSpace};
|
||||||
|
|
||||||
|
/// The kernel's boot and initialization process is managed by OSTD.
|
||||||
|
/// After the process is done, the kernel's execution environment
|
||||||
|
/// (e.g., stack, heap, tasks) will be ready for use and the entry function
|
||||||
|
/// labeled as `#[ostd::main]` will be called.
|
||||||
|
#[ostd::main]
|
||||||
|
pub fn main() {
|
||||||
|
let program_binary = include_bytes!("../hello");
|
||||||
|
let user_space = create_user_space(program_binary);
|
||||||
|
let user_task = create_user_task(Arc::new(user_space));
|
||||||
|
user_task.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_user_space(program: &[u8]) -> UserSpace {
|
||||||
|
let user_pages = {
|
||||||
|
let nframes = program.len().align_up(PAGE_SIZE) / PAGE_SIZE;
|
||||||
|
let vm_frames = FrameAllocOptions::new(nframes).alloc().unwrap();
|
||||||
|
// Phyiscal memory pages can be only accessed
|
||||||
|
// via the Frame abstraction.
|
||||||
|
vm_frames.write_bytes(0, program).unwrap();
|
||||||
|
vm_frames
|
||||||
|
};
|
||||||
|
let user_address_space = {
|
||||||
|
const MAP_ADDR: Vaddr = 0x0040_0000; // The map addr for statically-linked executable
|
||||||
|
|
||||||
|
// The page table of the user space can be
|
||||||
|
// created and manipulated safely through
|
||||||
|
// the VmSpace abstraction.
|
||||||
|
let vm_space = VmSpace::new();
|
||||||
|
let mut options = VmMapOptions::new();
|
||||||
|
options.addr(Some(MAP_ADDR)).flags(PageFlags::RWX);
|
||||||
|
vm_space.map(user_pages, &options).unwrap();
|
||||||
|
Arc::new(vm_space)
|
||||||
|
};
|
||||||
|
let user_cpu_state = {
|
||||||
|
const ENTRY_POINT: Vaddr = 0x0040_1000; // The entry point for statically-linked executable
|
||||||
|
|
||||||
|
// The user-space CPU states can be initialized
|
||||||
|
// to arbitrary values via the UserContext
|
||||||
|
// abstraction.
|
||||||
|
let mut user_cpu_state = UserContext::default();
|
||||||
|
user_cpu_state.set_rip(ENTRY_POINT);
|
||||||
|
user_cpu_state
|
||||||
|
};
|
||||||
|
UserSpace::new(user_address_space, user_cpu_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_user_task(user_space: Arc<UserSpace>) -> Arc<Task> {
|
||||||
|
fn user_task() {
|
||||||
|
let current = Task::current();
|
||||||
|
// Switching between user-kernel space is
|
||||||
|
// performed via the UserMode abstraction.
|
||||||
|
let mut user_mode = {
|
||||||
|
let user_space = current.user_space().unwrap();
|
||||||
|
UserMode::new(user_space)
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// The execute method returns when system
|
||||||
|
// calls or CPU exceptions occur or some
|
||||||
|
// events specified by the kernel occur.
|
||||||
|
let return_reason = user_mode.execute(|| false);
|
||||||
|
|
||||||
|
// The CPU registers of the user space
|
||||||
|
// can be accessed and manipulated via
|
||||||
|
// the `UserContext` abstraction.
|
||||||
|
let user_context = user_mode.context_mut();
|
||||||
|
if ReturnReason::UserSyscall == return_reason {
|
||||||
|
handle_syscall(user_context, current.user_space().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kernel tasks are managed by the Framework,
|
||||||
|
// while scheduling algorithms for them can be
|
||||||
|
// determined by the users of the Framework.
|
||||||
|
TaskOptions::new(user_task)
|
||||||
|
.user_space(Some(user_space))
|
||||||
|
.data(0)
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_syscall(user_context: &mut UserContext, user_space: &UserSpace) {
|
||||||
|
const SYS_WRITE: usize = 1;
|
||||||
|
const SYS_EXIT: usize = 60;
|
||||||
|
|
||||||
|
match user_context.rax() {
|
||||||
|
SYS_WRITE => {
|
||||||
|
// Access the user-space CPU registers safely.
|
||||||
|
let (_, buf_addr, buf_len) =
|
||||||
|
(user_context.rdi(), user_context.rsi(), user_context.rdx());
|
||||||
|
let buf = {
|
||||||
|
let mut buf = vec![0u8; buf_len];
|
||||||
|
// Copy data from the user space without
|
||||||
|
// unsafe pointer dereferencing.
|
||||||
|
let current_vm_space = user_space.vm_space();
|
||||||
|
let mut reader = current_vm_space.reader(buf_addr, buf_len).unwrap();
|
||||||
|
reader
|
||||||
|
.read_fallible(&mut VmWriter::from(&mut buf as &mut [u8]))
|
||||||
|
.unwrap();
|
||||||
|
buf
|
||||||
|
};
|
||||||
|
// Use the console for output safely.
|
||||||
|
println!("{}", str::from_utf8(&buf).unwrap());
|
||||||
|
// Manipulate the user-space CPU registers safely.
|
||||||
|
user_context.set_rax(buf_len);
|
||||||
|
}
|
||||||
|
SYS_EXIT => exit_qemu(QemuExitCode::Success),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user