Bring both EFI and legacy to test

This commit is contained in:
Zhang Junyang
2023-12-13 20:50:25 +08:00
committed by Tate, Hongliang Tian
parent 487e0cdd15
commit 432f0c34b0
9 changed files with 111 additions and 79 deletions

View File

@ -28,22 +28,23 @@ jobs:
id: 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 BOOT_METHOD=microvm RELEASE_MODE=1
- name: Boot Test (Linux Boot Protocol) - name: Boot Test (Linux Legacy 32-bit Boot Protocol)
id: boot_test_linux id: boot_test_linux_legacy32
run: make run AUTO_TEST=boot ENABLE_KVM=0 BOOT_PROTOCOL=linux RELEASE_MODE=1 run: make run AUTO_TEST=boot ENABLE_KVM=0 BOOT_PROTOCOL=linux-legacy32 RELEASE_MODE=1
- name: Syscall Test (MicroVM) - name: Boot Test (Linux EFI Handover Boot Protocol)
id: syscall_test_microvm id: syscall_test_linux_efi_handover64
run: make run AUTO_TEST=syscall ENABLE_KVM=0 BOOT_METHOD=microvm RELEASE_MODE=1 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 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) - name: Syscall Test at Ext2 (MicroVM)
id: 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 BOOT_METHOD=microvm RELEASE_MODE=1
- name: Regression Test (Linux Boot Protocol) - name: Regression Test (MicroVM)
id: regression_test_linux 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

View File

@ -10,7 +10,3 @@ bytemuck = { version = "1.14.0", features = ["derive"] }
bitflags = "1.3" bitflags = "1.3"
serde = { version = "1.0.192", features = ["derive"] } serde = { version = "1.0.192", features = ["derive"] }
xmas-elf = "0.9.1" xmas-elf = "0.9.1"
[features]
default = ["trojan64"]
trojan64 = []

View File

@ -3,7 +3,7 @@
//! This crate is responsible for building the bzImage. It contains methods to build //! This crate is responsible for building the bzImage. It contains methods to build
//! the wrapper binary and methods to build the bzImage. //! 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 //! 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. //! and concatenate it to the ELF file to make the bzImage.
//! //!
@ -15,7 +15,7 @@ mod pe_header;
use std::{ use std::{
fs::File, fs::File,
io::{Read, Write}, io::{Read, Seek, SeekFrom, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@ -29,7 +29,7 @@ use mapping::{WrapperFileOffset, WrapperVA};
/// ///
/// Interstingly, the resulting binary should be the same as the memory /// Interstingly, the resulting binary should be the same as the memory
/// dump of the kernel setup header when it's loaded by the bootloader. /// dump of the kernel setup header when it's loaded by the bootloader.
fn trojan_to_flat_binary(elf_file: &[u8]) -> Vec<u8> { fn wrapper_to_flat_binary(elf_file: &[u8]) -> Vec<u8> {
let elf = xmas_elf::ElfFile::new(&elf_file).unwrap(); let elf = xmas_elf::ElfFile::new(&elf_file).unwrap();
let mut bin = Vec::<u8>::new(); let mut bin = Vec::<u8>::new();
@ -72,7 +72,7 @@ fn fill_header_field(header: &mut [u8], offset: usize, value: &[u8]) {
fn fill_legacy_header_fields( fn fill_legacy_header_fields(
header: &mut [u8], header: &mut [u8],
kernel_len: usize, kernel_len: usize,
trojan_len: usize, wrapper_len: usize,
payload_offset: WrapperVA, payload_offset: WrapperVA,
) { ) {
fill_header_field( fill_header_field(
@ -90,29 +90,44 @@ fn fill_legacy_header_fields(
fill_header_field( fill_header_field(
header, header,
0x260, /* init_size */ 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) { /// The type of the bzImage that we are building through `make_bzimage`.
#[cfg(feature = "trojan64")] ///
let wrapper = build_trojan_with_arch(trojan_src, trojan_out, &TrojanBuildArch::X86_64); /// Currently, Legacy32 and Efi64 are mutually exclusive.
pub enum BzImageType {
Legacy32,
Efi64,
}
#[cfg(not(feature = "trojan64"))] pub fn make_bzimage(
let wrapper = { image_path: &Path,
let arch = trojan_src kernel_path: &Path,
.join("x86_64-i386_pm-none.json") image_type: BzImageType,
.canonicalize() wrapper_src: &Path,
.unwrap(); wrapper_out: &Path,
build_trojan_with_arch(trojan_src, trojan_out, &TrojanBuildArch::Other(arch)) ) {
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) File::open(wrapper)
.unwrap() .unwrap()
.read_to_end(&mut trojan_elf) .read_to_end(&mut wrapper_elf)
.unwrap(); .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. // Pad the header with 8-byte alignment.
wrapper.resize((wrapper.len() + 7) & !7, 0x00); 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(); .unwrap();
let payload = kernel; let payload = kernel;
let trojan_len = wrapper.len(); let wrapper_len = wrapper.len();
let payload_len = payload.len(); let payload_len = payload.len();
let payload_offset = WrapperFileOffset::from(trojan_len); let payload_offset = WrapperFileOffset::from(wrapper_len);
fill_legacy_header_fields(&mut wrapper, payload_len, trojan_len, payload_offset.into()); 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(&wrapper).unwrap();
kernel_image.write_all(&payload).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 if matches!(image_type, BzImageType::Efi64) {
// start of the file without overwriting the Linux boot header. // Write the PE/COFF header to the start of the file.
let pe_header = pe_header::make_pe_coff_header(&trojan_elf, image_size); // Since the Linux boot header starts at 0x1f1, we can write the PE/COFF header directly to the
assert!( // start of the file without overwriting the Linux boot header.
pe_header.header_at_zero.len() <= 0x1f1, let pe_header = pe_header::make_pe_coff_header(&wrapper_elf, image_size);
"PE/COFF header is too large" 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.seek(SeekFrom::Start(0)).unwrap();
kernel_image.write_all(&pe_header.header_at_zero).unwrap(); kernel_image.write_all(&pe_header.header_at_zero).unwrap();
kernel_image 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. enum WrapperBuildArch {
// 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 {
X86_64, X86_64,
Other(PathBuf), Other(PathBuf),
} }
/// Build the trojan binary. /// Build the wrapper binary.
/// ///
/// It will return the path to the built trojan binary. /// It will return the path to the built wrapper binary.
fn build_trojan_with_arch(source_dir: &Path, out_dir: &Path, arch: &TrojanBuildArch) -> PathBuf { fn build_wrapper_with_arch(source_dir: &Path, out_dir: &Path, arch: &WrapperBuildArch) -> PathBuf {
if !out_dir.exists() { if !out_dir.exists() {
std::fs::create_dir_all(&out_dir).unwrap(); 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("--package").arg("aster-boot-wrapper");
cmd.arg("--target").arg(match arch { cmd.arg("--target").arg(match arch {
TrojanBuildArch::X86_64 => "x86_64-unknown-none", WrapperBuildArch::X86_64 => "x86_64-unknown-none",
TrojanBuildArch::Other(path) => path.to_str().unwrap(), WrapperBuildArch::Other(path) => path.to_str().unwrap(),
}); });
cmd.arg("-Zbuild-std=core,alloc,compiler_builtins"); cmd.arg("-Zbuild-std=core,alloc,compiler_builtins");
cmd.arg("-Zbuild-std-features=compiler-builtins-mem"); 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. // Get the path to the wrapper binary.
let arch_name = match arch { let arch_name = match arch {
TrojanBuildArch::X86_64 => "x86_64-unknown-none", WrapperBuildArch::X86_64 => "x86_64-unknown-none",
TrojanBuildArch::Other(path) => path.file_stem().unwrap().to_str().unwrap(), WrapperBuildArch::Other(path) => path.file_stem().unwrap().to_str().unwrap(),
}; };
let trojan_artifact = out_dir let wrapper_artifact = out_dir
.join(arch_name) .join(arch_name)
.join(profile) .join(profile)
.join("aster-boot-wrapper"); .join("aster-boot-wrapper");
trojan_artifact.to_owned() wrapper_artifact.to_owned()
} }

View File

@ -18,7 +18,7 @@ pub fn load_elf(file: &[u8]) {
for ph in elf.program_iter() { for ph in elf.program_iter() {
let ProgramHeader::Ph64(program) = ph else { 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 { if program.get_type().unwrap() == xmas_elf::program::Type::Load {
load_segment(&elf, program); load_segment(&elf, program);

View File

@ -122,10 +122,10 @@ fn efi_phase_runtime(
unsafe { unsafe {
use crate::console::{print_hex, print_str}; use crate::console::{print_hex, print_str};
print_str("[EFI stub] Entering Jinux entrypoint at "); print_str("[EFI stub] Entering Asterinas entrypoint at ");
print_hex(super::JINUX_ENTRY_POINT as u64); print_hex(super::ASTER_ENTRY_POINT as u64);
print_str("\n"); 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) }
} }

View File

@ -8,7 +8,7 @@ global_asm!(include_str!("setup.S"));
use crate::console::{print_hex, print_str}; 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"] #[export_name = "_trojan_entry_32"]
extern "cdecl" fn trojan_entry(boot_params_ptr: u32) -> ! { 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); crate::loader::load_elf(payload);
// Safety: the entrypoint and the ptr is valid. // 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; pub const ASTER_ENTRY_POINT: u32 = 0x8001000;

View File

@ -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. /// 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 /// 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 /// address unknown at the moment. You should use the debug message from our
/// custom built OVMF firmware and read the entrypoint address /// custom built OVMF firmware and read the entrypoint address

View File

@ -1,4 +1,4 @@
use aster_boot_wrapper_builder::make_bzimage; use aster_boot_wrapper_builder::{make_bzimage, BzImageType};
use std::{ use std::{
fs, fs,
@ -64,9 +64,13 @@ pub const IOMMU_DEVICE_ARGS: &[&str] = &[
"ioh3420,id=pcie.0,chassis=1", "ioh3420,id=pcie.0,chassis=1",
]; ];
// To test legacy boot, use the following: /// The default GRUB tools used.
// pub const GRUB_PREFIX: &str = "/usr/local/grub";
pub const GRUB_PREFIX: &str = "/usr"; 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 const GRUB_VERSION: &str = "x86_64-efi";
pub fn create_bootdev_image( pub fn create_bootdev_image(
@ -92,17 +96,23 @@ pub fn create_bootdev_image(
.unwrap(); .unwrap();
let target_path = match protocol { let target_path = match protocol {
BootProtocol::Linux => { BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => {
let trojan_src = Path::new("framework/libs/boot-wrapper/wrapper"); let image_type = match protocol {
let trojan_out = Path::new("target/aster-boot-wrapper"); 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. // Make the `bzImage`-compatible kernel image and place it in the boot directory.
let target_path = iso_root.join("boot").join("asterinaz"); let target_path = iso_root.join("boot").join("asterinaz");
println!("[aster-runner] Building bzImage."); println!("[aster-runner] Building bzImage.");
make_bzimage( make_bzimage(
&target_path, &target_path,
&aster_path.as_path(), &aster_path.as_path(),
&trojan_src, image_type,
&trojan_out, &wrapper_src,
&wrapper_out,
); );
target_path target_path
} }
@ -121,7 +131,13 @@ pub fn create_bootdev_image(
// Make the boot device CDROM image. // Make the boot device CDROM image.
let iso_path = target_dir.join(target_name.to_string() + ".iso"); 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()); 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()); cmd.arg("--output").arg(&iso_path).arg(iso_root.as_os_str());
if !cmd.status().unwrap().success() { if !cmd.status().unwrap().success() {
@ -166,7 +182,7 @@ pub fn generate_grub_cfg(
.replace("#GRUB_CMD_KERNEL#", "multiboot2") .replace("#GRUB_CMD_KERNEL#", "multiboot2")
.replace("#KERNEL#", "/boot/atserinas") .replace("#KERNEL#", "/boot/atserinas")
.replace("#GRUB_CMD_INITRAMFS#", "module2 --nounzip"), .replace("#GRUB_CMD_INITRAMFS#", "module2 --nounzip"),
BootProtocol::Linux => buffer BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => buffer
.replace("#GRUB_CMD_KERNEL#", "linux") .replace("#GRUB_CMD_KERNEL#", "linux")
.replace("#KERNEL#", "/boot/asterinaz") .replace("#KERNEL#", "/boot/asterinaz")
.replace("#GRUB_CMD_INITRAMFS#", "initrd"), .replace("#GRUB_CMD_INITRAMFS#", "initrd"),

View File

@ -32,7 +32,8 @@ enum BootMethod {
pub enum BootProtocol { pub enum BootProtocol {
Multiboot, Multiboot,
Multiboot2, Multiboot2,
Linux, LinuxLegacy32,
LinuxEfiHandover64,
} }
/// The CLI of this runner. /// The CLI of this runner.
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -56,7 +57,8 @@ struct Args {
/// Boot protocol. Can be one of the following items: /// Boot protocol. Can be one of the following items:
/// - `multiboot`; /// - `multiboot`;
/// - `multiboot2`; /// - `multiboot2`;
/// - `linux`. /// - `linux-legacy32`;
/// - `linux-efi-handover64`.
#[arg(long, value_enum, default_value_t = BootProtocol::Multiboot2)] #[arg(long, value_enum, default_value_t = BootProtocol::Multiboot2)]
boot_protocol: BootProtocol, boot_protocol: BootProtocol,