diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 215b23a63..c181ce25a 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -28,22 +28,23 @@ jobs: id: boot_test_microvm run: make run AUTO_TEST=boot ENABLE_KVM=0 BOOT_METHOD=microvm RELEASE_MODE=1 - - name: Boot Test (Linux Boot Protocol) - id: boot_test_linux - run: make run AUTO_TEST=boot ENABLE_KVM=0 BOOT_PROTOCOL=linux RELEASE_MODE=1 + - name: Boot Test (Linux Legacy 32-bit Boot Protocol) + id: boot_test_linux_legacy32 + run: make run AUTO_TEST=boot ENABLE_KVM=0 BOOT_PROTOCOL=linux-legacy32 RELEASE_MODE=1 - - name: Syscall Test (MicroVM) - id: syscall_test_microvm - run: make run AUTO_TEST=syscall ENABLE_KVM=0 BOOT_METHOD=microvm RELEASE_MODE=1 + - name: Boot Test (Linux EFI Handover Boot Protocol) + id: syscall_test_linux_efi_handover64 + run: make run AUTO_TEST=boot ENABLE_KVM=0 BOOT_PROTOCOL=linux-efi-handover64 RELEASE_MODE=1 - - name: Syscall Test at Ext2 (Linux Boot Protocol) + - name: Syscall Test at Ext2 (Linux EFI Handover Boot Protocol) id: syscall_test_at_ext2_linux - run: make run AUTO_TEST=syscall SYSCALL_TEST_DIR=/ext2 ENABLE_KVM=0 BOOT_PROTOCOL=linux RELEASE_MODE=1 + run: make run AUTO_TEST=syscall SYSCALL_TEST_DIR=/ext2 ENABLE_KVM=0 BOOT_PROTOCOL=linux-efi-handover64 RELEASE_MODE=1 - 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 - - name: Regression Test (Linux Boot Protocol) + - name: Regression Test (MicroVM) id: regression_test_linux - run: make run AUTO_TEST=regression ENABLE_KVM=0 BOOT_PROTOCOL=linux RELEASE_MODE=1 + run: make run AUTO_TEST=regression ENABLE_KVM=0 BOOT_METHOD=microvm RELEASE_MODE=1 + \ No newline at end of file diff --git a/framework/libs/boot-wrapper/builder/Cargo.toml b/framework/libs/boot-wrapper/builder/Cargo.toml index 85f960142..fed0d19ea 100644 --- a/framework/libs/boot-wrapper/builder/Cargo.toml +++ b/framework/libs/boot-wrapper/builder/Cargo.toml @@ -10,7 +10,3 @@ bytemuck = { version = "1.14.0", features = ["derive"] } bitflags = "1.3" serde = { version = "1.0.192", features = ["derive"] } xmas-elf = "0.9.1" - -[features] -default = ["trojan64"] -trojan64 = [] diff --git a/framework/libs/boot-wrapper/builder/src/lib.rs b/framework/libs/boot-wrapper/builder/src/lib.rs index d2fb72b38..a320c41b6 100644 --- a/framework/libs/boot-wrapper/builder/src/lib.rs +++ b/framework/libs/boot-wrapper/builder/src/lib.rs @@ -3,7 +3,7 @@ //! This crate is responsible for building the bzImage. It contains methods to build //! the wrapper binary and methods to build the bzImage. //! -//! We should build the jinux kernel as a ELF file, and feed it to the builder to +//! We should build the asterinas kernel as a ELF file, and feed it to the builder to //! generate the bzImage. The builder will generate the PE/COFF header for the wrapper //! and concatenate it to the ELF file to make the bzImage. //! @@ -15,7 +15,7 @@ mod pe_header; use std::{ fs::File, - io::{Read, Write}, + io::{Read, Seek, SeekFrom, Write}, path::{Path, PathBuf}, }; @@ -29,7 +29,7 @@ use mapping::{WrapperFileOffset, WrapperVA}; /// /// Interstingly, the resulting binary should be the same as the memory /// dump of the kernel setup header when it's loaded by the bootloader. -fn trojan_to_flat_binary(elf_file: &[u8]) -> Vec { +fn wrapper_to_flat_binary(elf_file: &[u8]) -> Vec { let elf = xmas_elf::ElfFile::new(&elf_file).unwrap(); let mut bin = Vec::::new(); @@ -72,7 +72,7 @@ fn fill_header_field(header: &mut [u8], offset: usize, value: &[u8]) { fn fill_legacy_header_fields( header: &mut [u8], kernel_len: usize, - trojan_len: usize, + wrapper_len: usize, payload_offset: WrapperVA, ) { fill_header_field( @@ -90,29 +90,44 @@ fn fill_legacy_header_fields( fill_header_field( header, 0x260, /* init_size */ - &((trojan_len + kernel_len) as u32).to_le_bytes(), + &((wrapper_len + kernel_len) as u32).to_le_bytes(), ); } -pub fn make_bzimage(path: &Path, kernel_path: &Path, trojan_src: &Path, trojan_out: &Path) { - #[cfg(feature = "trojan64")] - let wrapper = build_trojan_with_arch(trojan_src, trojan_out, &TrojanBuildArch::X86_64); +/// The type of the bzImage that we are building through `make_bzimage`. +/// +/// Currently, Legacy32 and Efi64 are mutually exclusive. +pub enum BzImageType { + Legacy32, + Efi64, +} - #[cfg(not(feature = "trojan64"))] - let wrapper = { - let arch = trojan_src - .join("x86_64-i386_pm-none.json") - .canonicalize() - .unwrap(); - build_trojan_with_arch(trojan_src, trojan_out, &TrojanBuildArch::Other(arch)) +pub fn make_bzimage( + image_path: &Path, + kernel_path: &Path, + image_type: BzImageType, + wrapper_src: &Path, + wrapper_out: &Path, +) { + let wrapper = match image_type { + BzImageType::Legacy32 => { + let arch = wrapper_src + .join("x86_64-i386_pm-none.json") + .canonicalize() + .unwrap(); + build_wrapper_with_arch(wrapper_src, wrapper_out, &WrapperBuildArch::Other(arch)) + } + BzImageType::Efi64 => { + build_wrapper_with_arch(wrapper_src, wrapper_out, &WrapperBuildArch::X86_64) + } }; - let mut trojan_elf = Vec::new(); + let mut wrapper_elf = Vec::new(); File::open(wrapper) .unwrap() - .read_to_end(&mut trojan_elf) + .read_to_end(&mut wrapper_elf) .unwrap(); - let mut wrapper = trojan_to_flat_binary(&trojan_elf); + let mut wrapper = wrapper_to_flat_binary(&wrapper_elf); // Pad the header with 8-byte alignment. wrapper.resize((wrapper.len() + 7) & !7, 0x00); @@ -123,28 +138,32 @@ pub fn make_bzimage(path: &Path, kernel_path: &Path, trojan_src: &Path, trojan_o .unwrap(); let payload = kernel; - let trojan_len = wrapper.len(); + let wrapper_len = wrapper.len(); let payload_len = payload.len(); - let payload_offset = WrapperFileOffset::from(trojan_len); - fill_legacy_header_fields(&mut wrapper, payload_len, trojan_len, payload_offset.into()); + let payload_offset = WrapperFileOffset::from(wrapper_len); + fill_legacy_header_fields( + &mut wrapper, + payload_len, + wrapper_len, + payload_offset.into(), + ); - let mut kernel_image = File::create(path).unwrap(); + let mut kernel_image = File::create(image_path).unwrap(); kernel_image.write_all(&wrapper).unwrap(); kernel_image.write_all(&payload).unwrap(); - let image_size = trojan_len + payload_len; + let image_size = wrapper_len + payload_len; - // Since the Linux boot header starts at 0x1f1, we can write the PE/COFF header directly to the - // start of the file without overwriting the Linux boot header. - let pe_header = pe_header::make_pe_coff_header(&trojan_elf, image_size); - assert!( - pe_header.header_at_zero.len() <= 0x1f1, - "PE/COFF header is too large" - ); + if matches!(image_type, BzImageType::Efi64) { + // Write the PE/COFF header to the start of the file. + // Since the Linux boot header starts at 0x1f1, we can write the PE/COFF header directly to the + // start of the file without overwriting the Linux boot header. + let pe_header = pe_header::make_pe_coff_header(&wrapper_elf, image_size); + assert!( + pe_header.header_at_zero.len() <= 0x1f1, + "PE/COFF header is too large" + ); - #[cfg(feature = "trojan64")] - { - use std::io::{Seek, SeekFrom}; kernel_image.seek(SeekFrom::Start(0)).unwrap(); kernel_image.write_all(&pe_header.header_at_zero).unwrap(); kernel_image @@ -154,19 +173,15 @@ pub fn make_bzimage(path: &Path, kernel_path: &Path, trojan_src: &Path, trojan_o } } -// We need a custom target file for i386 but not for x86_64. -// The compiler may warn us the X86_64 enum variant is not constructed -// when we are building for i386, but we can ignore it. -#[allow(dead_code)] -enum TrojanBuildArch { +enum WrapperBuildArch { X86_64, Other(PathBuf), } -/// Build the trojan binary. +/// Build the wrapper binary. /// -/// It will return the path to the built trojan binary. -fn build_trojan_with_arch(source_dir: &Path, out_dir: &Path, arch: &TrojanBuildArch) -> PathBuf { +/// It will return the path to the built wrapper binary. +fn build_wrapper_with_arch(source_dir: &Path, out_dir: &Path, arch: &WrapperBuildArch) -> PathBuf { if !out_dir.exists() { std::fs::create_dir_all(&out_dir).unwrap(); } @@ -185,8 +200,8 @@ fn build_trojan_with_arch(source_dir: &Path, out_dir: &Path, arch: &TrojanBuildA } cmd.arg("--package").arg("aster-boot-wrapper"); cmd.arg("--target").arg(match arch { - TrojanBuildArch::X86_64 => "x86_64-unknown-none", - TrojanBuildArch::Other(path) => path.to_str().unwrap(), + WrapperBuildArch::X86_64 => "x86_64-unknown-none", + WrapperBuildArch::Other(path) => path.to_str().unwrap(), }); cmd.arg("-Zbuild-std=core,alloc,compiler_builtins"); cmd.arg("-Zbuild-std-features=compiler-builtins-mem"); @@ -207,14 +222,14 @@ fn build_trojan_with_arch(source_dir: &Path, out_dir: &Path, arch: &TrojanBuildA // Get the path to the wrapper binary. let arch_name = match arch { - TrojanBuildArch::X86_64 => "x86_64-unknown-none", - TrojanBuildArch::Other(path) => path.file_stem().unwrap().to_str().unwrap(), + WrapperBuildArch::X86_64 => "x86_64-unknown-none", + WrapperBuildArch::Other(path) => path.file_stem().unwrap().to_str().unwrap(), }; - let trojan_artifact = out_dir + let wrapper_artifact = out_dir .join(arch_name) .join(profile) .join("aster-boot-wrapper"); - trojan_artifact.to_owned() + wrapper_artifact.to_owned() } diff --git a/framework/libs/boot-wrapper/wrapper/src/loader.rs b/framework/libs/boot-wrapper/wrapper/src/loader.rs index 4344ef20b..fde674456 100644 --- a/framework/libs/boot-wrapper/wrapper/src/loader.rs +++ b/framework/libs/boot-wrapper/wrapper/src/loader.rs @@ -18,7 +18,7 @@ pub fn load_elf(file: &[u8]) { for ph in elf.program_iter() { let ProgramHeader::Ph64(program) = ph else { - panic!("[setup] Unexpected program header type! Jinux should be 64-bit ELF binary."); + panic!("[setup] Unexpected program header type! Asterinas should be 64-bit ELF binary."); }; if program.get_type().unwrap() == xmas_elf::program::Type::Load { load_segment(&elf, program); diff --git a/framework/libs/boot-wrapper/wrapper/src/x86/amd64_efi/efi.rs b/framework/libs/boot-wrapper/wrapper/src/x86/amd64_efi/efi.rs index d2ee4e56c..91364d5c7 100644 --- a/framework/libs/boot-wrapper/wrapper/src/x86/amd64_efi/efi.rs +++ b/framework/libs/boot-wrapper/wrapper/src/x86/amd64_efi/efi.rs @@ -122,10 +122,10 @@ fn efi_phase_runtime( unsafe { use crate::console::{print_hex, print_str}; - print_str("[EFI stub] Entering Jinux entrypoint at "); - print_hex(super::JINUX_ENTRY_POINT as u64); + print_str("[EFI stub] Entering Asterinas entrypoint at "); + print_hex(super::ASTER_ENTRY_POINT as u64); print_str("\n"); } - unsafe { super::call_jinux_entrypoint(super::JINUX_ENTRY_POINT, boot_params_ptr as u64) } + unsafe { super::call_aster_entrypoint(super::ASTER_ENTRY_POINT, boot_params_ptr as u64) } } diff --git a/framework/libs/boot-wrapper/wrapper/src/x86/legacy_i386/mod.rs b/framework/libs/boot-wrapper/wrapper/src/x86/legacy_i386/mod.rs index 38b528b8a..b75541470 100644 --- a/framework/libs/boot-wrapper/wrapper/src/x86/legacy_i386/mod.rs +++ b/framework/libs/boot-wrapper/wrapper/src/x86/legacy_i386/mod.rs @@ -8,7 +8,7 @@ global_asm!(include_str!("setup.S")); use crate::console::{print_hex, print_str}; -pub const JINUX_ENTRY_POINT: u32 = 0x8001000; +pub const ASTER_ENTRY_POINT: u32 = 0x8001000; #[export_name = "_trojan_entry_32"] extern "cdecl" fn trojan_entry(boot_params_ptr: u32) -> ! { @@ -29,7 +29,7 @@ extern "cdecl" fn trojan_entry(boot_params_ptr: u32) -> ! { crate::loader::load_elf(payload); // Safety: the entrypoint and the ptr is valid. - unsafe { call_jinux_entrypoint(JINUX_ENTRY_POINT, boot_params_ptr.try_into().unwrap()) }; + unsafe { call_aster_entrypoint(ASTER_ENTRY_POINT, boot_params_ptr.try_into().unwrap()) }; } pub const ASTER_ENTRY_POINT: u32 = 0x8001000; diff --git a/runner/src/gdb.rs b/runner/src/gdb.rs index 5b9e75edd..209680b4b 100644 --- a/runner/src/gdb.rs +++ b/runner/src/gdb.rs @@ -7,6 +7,8 @@ use std::{fs::OpenOptions, io::Write, path::PathBuf, process::Command}; /// /// 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 diff --git a/runner/src/machine/qemu_grub_efi.rs b/runner/src/machine/qemu_grub_efi.rs index 9e5fc07ef..948b4a152 100644 --- a/runner/src/machine/qemu_grub_efi.rs +++ b/runner/src/machine/qemu_grub_efi.rs @@ -1,4 +1,4 @@ -use aster_boot_wrapper_builder::make_bzimage; +use aster_boot_wrapper_builder::{make_bzimage, BzImageType}; use std::{ fs, @@ -64,9 +64,13 @@ pub const IOMMU_DEVICE_ARGS: &[&str] = &[ "ioh3420,id=pcie.0,chassis=1", ]; -// To test legacy boot, use the following: -// pub const GRUB_PREFIX: &str = "/usr/local/grub"; +/// 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( @@ -92,17 +96,23 @@ pub fn create_bootdev_image( .unwrap(); let target_path = match protocol { - BootProtocol::Linux => { - let trojan_src = Path::new("framework/libs/boot-wrapper/wrapper"); - let trojan_out = Path::new("target/aster-boot-wrapper"); + BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => { + let image_type = match protocol { + BootProtocol::LinuxLegacy32 => BzImageType::Legacy32, + BootProtocol::LinuxEfiHandover64 => BzImageType::Efi64, + _ => unreachable!(), + }; + let wrapper_src = Path::new("framework/libs/boot-wrapper/wrapper"); + let wrapper_out = Path::new("target/aster-boot-wrapper"); // 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, &aster_path.as_path(), - &trojan_src, - &trojan_out, + image_type, + &wrapper_src, + &wrapper_out, ); target_path } @@ -121,7 +131,13 @@ pub fn create_bootdev_image( // Make the boot device CDROM image. let iso_path = target_dir.join(target_name.to_string() + ".iso"); - let grub_mkrescue_bin = PathBuf::from(GRUB_PREFIX).join("bin").join("grub-mkrescue"); + 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() { @@ -166,7 +182,7 @@ pub fn generate_grub_cfg( .replace("#GRUB_CMD_KERNEL#", "multiboot2") .replace("#KERNEL#", "/boot/atserinas") .replace("#GRUB_CMD_INITRAMFS#", "module2 --nounzip"), - BootProtocol::Linux => buffer + BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => buffer .replace("#GRUB_CMD_KERNEL#", "linux") .replace("#KERNEL#", "/boot/asterinaz") .replace("#GRUB_CMD_INITRAMFS#", "initrd"), diff --git a/runner/src/main.rs b/runner/src/main.rs index 9f97bc16a..78be09ff3 100644 --- a/runner/src/main.rs +++ b/runner/src/main.rs @@ -32,7 +32,8 @@ enum BootMethod { pub enum BootProtocol { Multiboot, Multiboot2, - Linux, + LinuxLegacy32, + LinuxEfiHandover64, } /// The CLI of this runner. #[derive(Parser, Debug)] @@ -56,7 +57,8 @@ struct Args { /// Boot protocol. Can be one of the following items: /// - `multiboot`; /// - `multiboot2`; - /// - `linux`. + /// - `linux-legacy32`; + /// - `linux-efi-handover64`. #[arg(long, value_enum, default_value_t = BootProtocol::Multiboot2)] boot_protocol: BootProtocol,