diff --git a/Cargo.lock b/Cargo.lock index d953a7984..c289ea55f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -417,6 +417,26 @@ dependencies = [ "spin 0.9.8", ] +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +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 2.0.29", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -734,12 +754,6 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - [[package]] name = "hash32" version = "0.2.1" @@ -1252,9 +1266,23 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.183" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] [[package]] name = "serde_spanned" @@ -1667,6 +1695,15 @@ dependencies = [ "zero", ] +[[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" diff --git a/Cargo.toml b/Cargo.toml index 895bc690b..1686aafdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,8 @@ members = [ "framework/aster-frame", "framework/aster-frame/src/arch/x86/boot/linux_boot/setup", "framework/libs/align_ext", + "framework/libs/boot-trojan/builder", + "framework/libs/boot-trojan/trojan", "framework/libs/ktest", "framework/libs/tdx-guest", "services/comps/block", diff --git a/Makefile b/Makefile index b29c8e921..2d8800766 100644 --- a/Makefile +++ b/Makefile @@ -44,10 +44,6 @@ CARGO_KRUN_ARGS += -- '$(KERNEL_CMDLINE) -- $(INIT_CMDLINE)' CARGO_KRUN_ARGS += --boot-method="$(BOOT_METHOD)" CARGO_KRUN_ARGS += --boot-protocol="$(BOOT_PROTOCOL)" -ifeq ($(RELEASE_MODE), 1) -CARGO_KRUN_ARGS += --release-mode -endif - ifeq ($(EMULATE_IOMMU), 1) CARGO_KRUN_ARGS += --emulate-iommu endif @@ -89,6 +85,7 @@ export CARGO := cargo USERMODE_TESTABLE := \ runner \ framework/libs/align_ext \ + framework/libs/boot-trojan/builder \ framework/libs/ktest \ framework/libs/ktest-proc-macro \ services/libs/cpio-decoder \ diff --git a/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/linker.ld b/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/linker.ld deleted file mode 100644 index 0103ae18b..000000000 --- a/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/linker.ld +++ /dev/null @@ -1,32 +0,0 @@ -ENTRY(start_of_setup32) -OUTPUT_ARCH(i386:x86) -OUTPUT_FORMAT(elf32-i386) - -SETUP32_LMA = 0x100000; - -SECTIONS -{ - . = SETUP32_LMA - 0x1000; - .header : { KEEP(*(.header)) } - - . = SETUP32_LMA; - .header.text : { KEEP(*(.header)) } - .stack : { KEEP(*(.stack)) } - - .text : { *(.text .text.*) } - .rodata : { *(.rodata .rodata.*) } - - .data : { *(.data .data.*) } - .bss : { - __bss = .; - *(.bss .bss.*) *(COMMON) - __bss_end = .; - } - - .eh_frame : { - *(.eh_frame .eh_frame.*) - } - .eh_frame_hdr : { - *(.eh_frame_hdr .eh_frame_hdr.*) - } -} diff --git a/framework/libs/boot-trojan/builder/Cargo.toml b/framework/libs/boot-trojan/builder/Cargo.toml new file mode 100644 index 000000000..093fa5124 --- /dev/null +++ b/framework/libs/boot-trojan/builder/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "aster-boot-trojan-builder" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bytemuck = { version = "1.14.0", features = ["derive"] } +bitflags = "1.3" +serde = { version = "1.0.192", features = ["derive"] } +xmas-elf = "0.9.1" diff --git a/framework/libs/boot-trojan/builder/src/lib.rs b/framework/libs/boot-trojan/builder/src/lib.rs new file mode 100644 index 000000000..c9cb14987 --- /dev/null +++ b/framework/libs/boot-trojan/builder/src/lib.rs @@ -0,0 +1,165 @@ +mod mapping; +mod pe_header; + +use std::{ + error::Error, + fs::File, + io::{Read, Seek, Write}, + path::Path, +}; + +use xmas_elf::program::{ProgramHeader, SegmentData}; + +use mapping::{TrojanFileOffset, TrojanVA}; + +/// We need a flat binary which satisfies PA delta == File delta, and objcopy +/// does not satisfy us well, so we should parse the ELF and do our own +/// objcopy job. +/// +/// 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 { + let elf = xmas_elf::ElfFile::new(&elf_file).unwrap(); + let mut bin = Vec::::new(); + + for ph in elf.program_iter() { + let ProgramHeader::Ph32(program) = ph else { + panic!("Unexpected program header type"); + }; + if program.get_type().unwrap() == xmas_elf::program::Type::Load { + let SegmentData::Undefined(header_data) = program.get_data(&elf).unwrap() else { + panic!("Unexpected segment data type"); + }; + let dst_file_offset = usize::from(TrojanFileOffset::from(TrojanVA::from( + program.virtual_addr as usize, + ))); + let dst_file_length = program.file_size as usize; + if bin.len() < dst_file_offset + dst_file_length { + bin.resize(dst_file_offset + dst_file_length, 0); + } + let dest_slice = bin[dst_file_offset..dst_file_offset + dst_file_length].as_mut(); + dest_slice.copy_from_slice(header_data); + } + } + + bin +} + +/// This function sould be used when generating the Linux x86 Boot setup header. +/// Some fields in the Linux x86 Boot setup header should be filled after assembled. +/// And the filled fields must have the bytes with values of 0xAB. See +/// `framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/header.S` for more +/// info on this mechanism. +fn fill_header_field(header: &mut [u8], offset: usize, value: &[u8]) { + let size = value.len(); + assert_eq!( + &header[offset..offset + size], + vec![0xABu8; size].as_slice(), + "The field {:#x} to be filled must be marked with 0xAB", + offset + ); + header[offset..offset + size].copy_from_slice(value); +} + +fn fill_legacy_header_fields( + header: &mut [u8], + kernel_len: usize, + header_len: usize, + payload_offset: TrojanVA, +) { + fill_header_field( + header, + 0x248, /* payload_offset */ + &(usize::from(payload_offset) as u32).to_le_bytes(), + ); + + fill_header_field( + header, + 0x24C, /* payload_length */ + &(kernel_len as u32).to_le_bytes(), + ); + + fill_header_field( + header, + 0x260, /* init_size */ + &((header_len + kernel_len) as u32).to_le_bytes(), + ); +} + +pub fn make_bzimage(path: &Path, kernel_path: &Path, header_path: &Path) -> std::io::Result<()> { + let mut header_elf_file = Vec::new(); + File::open(header_path)?.read_to_end(&mut header_elf_file)?; + let mut header = trojan_to_flat_binary(&header_elf_file); + // Pad the Linux boot header to let the payload starts with 8-byte alignment. + header.resize((header.len() + 7) & !7, 0x00); + + let mut kernel = Vec::new(); + File::open(kernel_path)?.read_to_end(&mut kernel)?; + let payload = kernel; + + let header_len = header.len(); + let payload_len = payload.len(); + let payload_offset = TrojanFileOffset::from(header_len); + fill_legacy_header_fields(&mut header, payload_len, header_len, payload_offset.into()); + + let mut kernel_image = File::create(path)?; + kernel_image.write_all(&header)?; + kernel_image.write_all(&payload)?; + + let image_size = header_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(&header_elf_file, image_size); + assert!( + pe_header.header_at_zero.len() <= 0x1f1, + "PE/COFF header is too large" + ); + + // FIXME: Oops, EFI hanover stucks, so I removed the pe header to let grub go through the legacy path. + kernel_image.seek(std::io::SeekFrom::Start(0))?; + // kernel_image.write_all(&pe_header.header_at_zero)?; + kernel_image.seek(std::io::SeekFrom::Start( + usize::from(pe_header.relocs.0) as u64 + ))?; + // kernel_image.write_all(&pe_header.relocs.1)?; + + Ok(()) +} + +pub fn build_linux_setup_header_from_trojan( + source_dir: &Path, + out_dir: &Path, +) -> Result<(), Box> { + // Build the setup header to ELF. + let target_json = source_dir.join("x86_64-i386_protected_mode.json"); + + let cargo = std::env::var("CARGO").unwrap(); + let mut cmd = std::process::Command::new(cargo); + cmd.arg("install").arg("aster-boot-trojan"); + cmd.arg("--debug"); + cmd.arg("--locked"); + cmd.arg("--path").arg(source_dir.to_str().unwrap()); + cmd.arg("--target").arg(target_json.as_os_str()); + cmd.arg("-Zbuild-std=core,compiler_builtins"); + cmd.arg("-Zbuild-std-features=compiler-builtins-mem"); + // Specify the installation root. + cmd.arg("--root").arg(out_dir.as_os_str()); + // Specify the build target directory to avoid cargo running + // into a deadlock reading the workspace files. + cmd.arg("--target-dir").arg(out_dir.as_os_str()); + cmd.env_remove("RUSTFLAGS"); + cmd.env_remove("CARGO_ENCODED_RUSTFLAGS"); + let output = cmd.output()?; + if !output.status.success() { + std::io::stdout().write_all(&output.stdout).unwrap(); + std::io::stderr().write_all(&output.stderr).unwrap(); + return Err(format!( + "Failed to build linux x86 setup header:\n\tcommand `{:?}`\n\treturned {}", + cmd, output.status + ) + .into()); + } + + Ok(()) +} diff --git a/framework/libs/boot-trojan/builder/src/mapping.rs b/framework/libs/boot-trojan/builder/src/mapping.rs new file mode 100644 index 000000000..9ff85f88d --- /dev/null +++ b/framework/libs/boot-trojan/builder/src/mapping.rs @@ -0,0 +1,76 @@ +//! In the trojan, VA - SETUP32_LMA == FileOffset - LEGACY_SETUP_SEC_SIZE. +//! And the addresses are specified in the ELF file. + +use std::{cmp::PartialOrd, convert::From, ops::Sub}; + +// We chose the legacy setup sections to be 7 so that the setup header +// is page-aligned and the legacy setup section size would be 0x1000. +pub const LEGACY_SETUP_SECS: usize = 7; +pub const LEGACY_SETUP_SEC_SIZE: usize = 0x200 * (LEGACY_SETUP_SECS + 1); +pub const SETUP32_LMA: usize = 0x100000; + +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] +pub struct TrojanVA { + addr: usize, +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] +pub struct TrojanFileOffset { + offset: usize, +} + +impl From for TrojanVA { + fn from(addr: usize) -> Self { + Self { addr } + } +} + +impl From for usize { + fn from(va: TrojanVA) -> Self { + va.addr + } +} + +impl Sub for TrojanVA { + type Output = usize; + + fn sub(self, rhs: Self) -> Self::Output { + self.addr - rhs.addr + } +} + +impl From for TrojanFileOffset { + fn from(offset: usize) -> Self { + Self { offset } + } +} + +impl From for usize { + fn from(offset: TrojanFileOffset) -> Self { + offset.offset + } +} + +impl Sub for TrojanFileOffset { + type Output = usize; + + fn sub(self, rhs: Self) -> Self::Output { + self.offset - rhs.offset + } +} + +impl From for TrojanFileOffset { + fn from(va: TrojanVA) -> Self { + Self { + offset: va.addr + LEGACY_SETUP_SEC_SIZE - SETUP32_LMA, + } + } +} + +impl From for TrojanVA { + fn from(offset: TrojanFileOffset) -> Self { + Self { + addr: offset.offset + SETUP32_LMA - LEGACY_SETUP_SEC_SIZE, + } + } +} diff --git a/framework/libs/boot-trojan/builder/src/pe_header.rs b/framework/libs/boot-trojan/builder/src/pe_header.rs new file mode 100644 index 000000000..6bc640c80 --- /dev/null +++ b/framework/libs/boot-trojan/builder/src/pe_header.rs @@ -0,0 +1,353 @@ +//! Big zImage PE/COFF header generation. +//! +//! The definition of the PE/COFF header is in the Microsoft PE/COFF specification: +//! https://learn.microsoft.com/en-us/windows/win32/debug/pe-format +//! +//! The reference to the Linux PE header definition: +//! https://github.com/torvalds/linux/blob/master/include/linux/pe.h + +use bytemuck::{Pod, Zeroable}; +use serde::Serialize; + +use std::{mem::size_of, ops::Range}; + +use crate::mapping::{TrojanFileOffset, TrojanVA, LEGACY_SETUP_SEC_SIZE, SETUP32_LMA}; + +// The MS-DOS header. +const MZ_MAGIC: u16 = 0x5a4d; // "MZ" + +// The `magic` field in PE header. +const PE_MAGIC: u32 = 0x00004550; + +// The `machine` field choices in PE header. Not exhaustive. +#[derive(Serialize, Clone, Copy)] +#[repr(u16)] +enum PeMachineType { + Amd64 = 0x8664, +} + +// The `flags` field choices in PE header. +bitflags::bitflags! { + struct PeFlags: u16 { + const RELOCS_STRIPPED = 1; + const EXECUTABLE_IMAGE = 1 << 1; + const LINE_NUMS_STRIPPED = 1 << 2; + const LOCAL_SYMS_STRIPPED = 1 << 3; + const AGGRESIVE_WS_TRIM = 1 << 4; + const LARGE_ADDRESS_AWARE = 1 << 5; + const SIXTEEN_BIT_MACHINE = 1 << 6; + const BYTES_REVERSED_LO = 1 << 7; + const THIRTY_TWO_BIT_MACHINE = 1 << 8; + const DEBUG_STRIPPED = 1 << 9; + const REMOVABLE_RUN_FROM_SWAP = 1 << 10; + const NET_RUN_FROM_SWAP = 1 << 11; + const SYSTEM = 1 << 12; + const DLL = 1 << 13; + const UP_SYSTEM_ONLY = 1 << 14; + } +} + +#[derive(Zeroable, Pod, Serialize, Clone, Copy)] +#[repr(C, packed)] +struct PeHdr { + magic: u32, // PE magic + machine: u16, // machine type + sections: u16, // number of sections + timestamp: u32, // time_t + symbol_table: u32, // symbol table offset + symbols: u32, // number of symbols + opt_hdr_size: u16, // size of optional header + flags: u16, // flags +} + +// The `magic` field in the PE32+ optional header. +const PE32PLUS_OPT_HDR_MAGIC: u16 = 0x020b; + +// The `subsys` field choices in the PE32+ optional header. Not exhaustive. +#[derive(Serialize, Clone, Copy)] +#[repr(u16)] +enum PeImageSubsystem { + EfiApplication = 10, +} + +#[derive(Zeroable, Pod, Serialize, Clone, Copy)] +#[repr(C, packed)] +struct Pe32PlusOptHdr { + magic: u16, // file type + ld_major: u8, // linker major version + ld_minor: u8, // linker minor version + text_size: u32, // size of text section(s) + data_size: u32, // size of data section(s) + bss_size: u32, // size of bss section(s) + entry_point: u32, // file offset of entry point + code_base: u32, // relative code addr in ram + image_base: u64, // preferred load address + section_align: u32, // alignment in bytes + file_align: u32, // file alignment in bytes + os_major: u16, // major OS version + os_minor: u16, // minor OS version + image_major: u16, // major image version + image_minor: u16, // minor image version + subsys_major: u16, // major subsystem version + subsys_minor: u16, // minor subsystem version + win32_version: u32, // reserved, must be 0 + image_size: u32, // image size + header_size: u32, // header size rounded up to file_align + csum: u32, // checksum + subsys: u16, // subsystem + dll_flags: u16, // more flags! + stack_size_req: u64, // amt of stack requested + stack_size: u64, // amt of stack required + heap_size_req: u64, // amt of heap requested + heap_size: u64, // amt of heap required + loader_flags: u32, // reserved, must be 0 + data_dirs: u32, // number of data dir entries +} + +// The `flags` field choices in the PE section header. +// Excluding the alignment flags, which is not bitflags. +bitflags::bitflags! { + struct PeSectionHdrFlags: u32 { + const CNT_CODE = 1 << 5; + const CNT_INITIALIZED_DATA = 1 << 6; + const CNT_UNINITIALIZED_DATA = 1 << 7; + const LNK_INFO = 1 << 9; + const LNK_REMOVE = 1 << 11; + const LNK_COMDAT = 1 << 12; + const GPREL = 1 << 15; + const MEM_PURGEABLE = 1 << 16; + const LNK_NRELOC_OVFL = 1 << 24; + const MEM_DISCARDABLE = 1 << 25; + const MEM_NOT_CACHED = 1 << 26; + const MEM_NOT_PAGED = 1 << 27; + const MEM_SHARED = 1 << 28; + const MEM_EXECUTE = 1 << 29; + const MEM_READ = 1 << 30; + const MEM_WRITE = 1 << 31; + } +} + +// The `flags` field choices in the PE section header. +#[derive(Serialize, Clone, Copy)] +#[repr(u32)] +enum PeSectionHdrFlagsAlign { + _1Bytes = 0x00100000, + _2Bytes = 0x00200000, + _4Bytes = 0x00300000, + _8Bytes = 0x00400000, + _16Bytes = 0x00500000, + _32Bytes = 0x00600000, + _64Bytes = 0x00700000, + _128Bytes = 0x00800000, + _256Bytes = 0x00900000, + _512Bytes = 0x00A00000, + _1024Bytes = 0x00B00000, + _2048Bytes = 0x00C00000, + _4096Bytes = 0x00D00000, + _8192Bytes = 0x00E00000, +} + +#[derive(Zeroable, Pod, Serialize, Clone, Copy)] +#[repr(C, packed)] +struct PeSectionHdr { + name: [u8; 8], // name or "/12\0" string tbl offset + virtual_size: u32, // size of loaded section in RAM + virtual_address: u32, // relative virtual address + raw_data_size: u32, // size of the section + data_addr: u32, // file pointer to first page of sec + relocs: u32, // file pointer to relocation entries + line_numbers: u32, // line numbers! + num_relocs: u16, // number of relocations + num_lin_numbers: u16, // srsly. + flags: u32, +} + +struct TrojanSectionAddrInfo { + pub setup: Range, + pub text: Range, +} + +impl TrojanSectionAddrInfo { + fn from(elf: &xmas_elf::ElfFile) -> Self { + let mut setup_start = None; + let mut setup_end = None; + let mut text_start = None; + let mut text_end = None; + for section in elf.section_iter() { + match elf.get_shstr(section.name()) { + Ok(s) => { + if s == ".setup" { + setup_start = Some(section.address() as usize); + setup_end = Some(section.address() as usize + section.size() as usize); + } else if s == ".text" { + text_start = Some(section.address() as usize); + text_end = Some(section.address() as usize + section.size() as usize); + } + } + Err(e) => { + panic!("Error: {:#?}", e); + } + } + } + assert!( + matches!(setup_start, Some(SETUP32_LMA)), + "setup_start: {:#x?}", + setup_start + ); + Self { + setup: TrojanVA::from(setup_start.unwrap())..TrojanVA::from(setup_end.unwrap()), + text: TrojanVA::from(text_start.unwrap())..TrojanVA::from(text_end.unwrap()), + } + } + + fn setup_virt_size(&self) -> usize { + self.setup.end - self.setup.start + } + + fn setup_file_size(&self) -> usize { + TrojanFileOffset::from(self.setup.end) - TrojanFileOffset::from(self.setup.start) + } + + fn text_virt_size(&self) -> usize { + self.text.end - self.text.start + } + + fn text_file_size(&self) -> usize { + TrojanFileOffset::from(self.text.end) - TrojanFileOffset::from(self.text.start) + } +} + +pub struct TrojanPeCoffHeaderBuf { + pub header_at_zero: Vec, + pub relocs: (TrojanFileOffset, Vec), +} + +pub(crate) fn make_pe_coff_header(setup_elf: &[u8], image_size: usize) -> TrojanPeCoffHeaderBuf { + let elf = xmas_elf::ElfFile::new(setup_elf).unwrap(); + let mut bin = Vec::::new(); + + // PE header + let pe_hdr = PeHdr { + magic: PE_MAGIC, + machine: PeMachineType::Amd64 as u16, + sections: 3, // please set this field according to the number of sections added in the pe header + timestamp: 0, + symbol_table: 0, + symbols: 1, // I don't know why, Linux header.S says it's 1 + opt_hdr_size: size_of::() as u16, + flags: (PeFlags::EXECUTABLE_IMAGE | PeFlags::DEBUG_STRIPPED | PeFlags::LINE_NUMS_STRIPPED) + .bits, + }; + + let elf_text_hdr = elf.find_section_by_name(".text").unwrap(); + + // PE32+ optional header + let pe_opt_hdr = Pe32PlusOptHdr { + magic: PE32PLUS_OPT_HDR_MAGIC, + ld_major: 0x02, // there's no linker to this extent, we do linking by ourselves + ld_minor: 0x14, + text_size: elf_text_hdr.size() as u32, + data_size: 0, // data size is irrelevant + bss_size: 0, // bss size is irrelevant + entry_point: elf.header.pt2.entry_point() as u32, + code_base: elf_text_hdr.address() as u32, + image_base: SETUP32_LMA as u64 - LEGACY_SETUP_SEC_SIZE as u64, + section_align: 0x20, + file_align: 0x20, + os_major: 0, + os_minor: 0, + image_major: 0x3, // see linux/pe.h for more info + image_minor: 0, + subsys_major: 0, + subsys_minor: 0, + win32_version: 0, + image_size: image_size as u32, + header_size: LEGACY_SETUP_SEC_SIZE as u32, + csum: 0, + subsys: PeImageSubsystem::EfiApplication as u16, + dll_flags: 0, + stack_size_req: 0, + stack_size: 0, + heap_size_req: 0, + heap_size: 0, + loader_flags: 0, + data_dirs: 0, + }; + + let addr_info = TrojanSectionAddrInfo::from(&elf); + + // PE section headers + let pe_setup_hdr = PeSectionHdr { + name: [b'.', b's', b'e', b't', b'u', b'p', 0, 0], + virtual_size: addr_info.setup_virt_size() as u32, + virtual_address: usize::from(addr_info.setup.start) as u32, + raw_data_size: addr_info.setup_file_size() as u32, + data_addr: usize::from(TrojanFileOffset::from(addr_info.setup.start)) as u32, + relocs: 0, + line_numbers: 0, + num_relocs: 0, + num_lin_numbers: 0, + flags: (PeSectionHdrFlags::CNT_CODE + | PeSectionHdrFlags::MEM_READ + | PeSectionHdrFlags::MEM_EXECUTE + | PeSectionHdrFlags::MEM_DISCARDABLE) + .bits + | PeSectionHdrFlagsAlign::_16Bytes as u32, + }; + // The EFI application loader requires a relocation section + let reloc_offset = TrojanFileOffset::from(0x500); // must be after the legacy header and before 0x1000 + let relocs = vec![]; + let pe_reloc_hdr = PeSectionHdr { + name: [b'.', b'r', b'e', b'l', b'o', b'c', 0, 0], + virtual_size: relocs.len() as u32, + virtual_address: usize::from(TrojanVA::from(reloc_offset)) as u32, + raw_data_size: relocs.len() as u32, + data_addr: usize::from(reloc_offset) as u32, + relocs: 0, + line_numbers: 0, + num_relocs: 0, + num_lin_numbers: 0, + flags: (PeSectionHdrFlags::CNT_INITIALIZED_DATA + | PeSectionHdrFlags::MEM_READ + | PeSectionHdrFlags::MEM_DISCARDABLE) + .bits + | PeSectionHdrFlagsAlign::_1Bytes as u32, + }; + let pe_text_hdr = PeSectionHdr { + name: [b'.', b't', b'e', b'x', b't', 0, 0, 0], + virtual_size: addr_info.text_virt_size() as u32, + virtual_address: usize::from(addr_info.text.start) as u32, + raw_data_size: addr_info.text_file_size() as u32, + data_addr: usize::from(TrojanFileOffset::from(addr_info.text.start)) as u32, + relocs: 0, + line_numbers: 0, + num_relocs: 0, + num_lin_numbers: 0, + flags: (PeSectionHdrFlags::CNT_CODE + | PeSectionHdrFlags::MEM_READ + | PeSectionHdrFlags::MEM_EXECUTE) + .bits + | PeSectionHdrFlagsAlign::_16Bytes as u32, + }; + + // Write the MS-DOS header + bin.extend_from_slice(&MZ_MAGIC.to_le_bytes()); + // Write the MS-DOS stub at 0x3c + bin.extend_from_slice(&[0x0; 0x3c - 0x2]); + // Write the PE header offset, the header is right after the offset field + bin.extend_from_slice(&(0x3cu32 + size_of::() as u32).to_le_bytes()); + + // Write the PE header + bin.extend_from_slice(bytemuck::bytes_of(&pe_hdr)); + // Write the PE32+ optional header + bin.extend_from_slice(bytemuck::bytes_of(&pe_opt_hdr)); + // Write the PE section headers + bin.extend_from_slice(bytemuck::bytes_of(&pe_setup_hdr)); + bin.extend_from_slice(bytemuck::bytes_of(&pe_reloc_hdr)); + bin.extend_from_slice(bytemuck::bytes_of(&pe_text_hdr)); + + TrojanPeCoffHeaderBuf { + header_at_zero: bin, + relocs: (reloc_offset, relocs), + } +} diff --git a/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/Cargo.toml b/framework/libs/boot-trojan/trojan/Cargo.toml similarity index 82% rename from framework/aster-frame/src/arch/x86/boot/linux_boot/setup/Cargo.toml rename to framework/libs/boot-trojan/trojan/Cargo.toml index 41ba76b24..2ae39d42e 100644 --- a/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/Cargo.toml +++ b/framework/libs/boot-trojan/trojan/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "aster-frame-x86-boot-linux-setup" +name = "aster-boot-trojan" version = "0.1.0" edition = "2021" diff --git a/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/build.rs b/framework/libs/boot-trojan/trojan/build.rs similarity index 100% rename from framework/aster-frame/src/arch/x86/boot/linux_boot/setup/build.rs rename to framework/libs/boot-trojan/trojan/build.rs diff --git a/framework/libs/boot-trojan/trojan/linker.ld b/framework/libs/boot-trojan/trojan/linker.ld new file mode 100644 index 000000000..9271fe72e --- /dev/null +++ b/framework/libs/boot-trojan/trojan/linker.ld @@ -0,0 +1,52 @@ +ENTRY(start_of_setup64) +OUTPUT_ARCH(i386:x86) +OUTPUT_FORMAT(elf32-i386) + +SETUP32_LMA = 0x100000; +BOOTSECT_SIZE = 0x1000; +BOOTSECT_START = SETUP32_LMA - BOOTSECT_SIZE; + +SECTIONS +{ + . = BOOTSECT_START; + .header : { KEEP(*(.header)) } + + . = SETUP32_LMA; + .setup : { + PROVIDE(__setup_start = .); + KEEP(*(.header)) + PROVIDE(__setup_end = .); + } + .stack : { KEEP(*(.stack)) } + + .text : { + PROVIDE(__text_start = .); + *(.text .text.*) + PROVIDE(__text_end = .); + } + .rodata : { *(.rodata .rodata.*) } + + .data : { *(.data .data.*) } + .bss : { + PROVIDE(__bss_start = .); + *(.bss .bss.*) *(COMMON) + PROVIDE(__bss_end = .); + } + + .eh_frame : { + *(.eh_frame .eh_frame.*) + } + .eh_frame_hdr : { + *(.eh_frame_hdr .eh_frame_hdr.*) + } + + .symtab : { + *(.symtab .symtab.*) + } + .strtab : { + *(.strtab .strtab.*) + } + .shstrtab : { + *(.shstrtab .shstrtab.*) + } +} diff --git a/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/boot_params.rs b/framework/libs/boot-trojan/trojan/src/boot_params.rs similarity index 100% rename from framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/boot_params.rs rename to framework/libs/boot-trojan/trojan/src/boot_params.rs diff --git a/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/console.rs b/framework/libs/boot-trojan/trojan/src/console.rs similarity index 100% rename from framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/console.rs rename to framework/libs/boot-trojan/trojan/src/console.rs diff --git a/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/header.S b/framework/libs/boot-trojan/trojan/src/header.S similarity index 65% rename from framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/header.S rename to framework/libs/boot-trojan/trojan/src/header.S index 563a15e50..6b99161b8 100644 --- a/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/header.S +++ b/framework/libs/boot-trojan/trojan/src/header.S @@ -2,22 +2,19 @@ // See https://www.kernel.org/doc/html/v5.6/x86/boot.html for // more information on the Linux x86 Boot Protocol. -.section ".header", "a" - -// The Linux x86 Boot Protocol header. -// // Some of the fields filled with a 0xab* values should be filled // by the runner, which is the only tool after building and can // access the info of the payload. // Asterinas will use only a few of these fields, and some of them // are filled by the loader and will be read by Asterinas. +.section ".header", "a" CODE32_START = 0x100000 - +SETUP_SECTS = 7 # so that the legacy setup could occupy a page .code16 .org 0x01f1 hdr_start: -setup_sects: .byte 7 # so that the legacy setup could occupy a page +setup_sects: .byte SETUP_SECTS root_flags: .word 1 syssize: .long 0 ram_size: .word 0 @@ -46,47 +43,15 @@ initrd_addr_max: .long 0x7fffffff kernel_alignment: .long 0x1000000 relocatable_kernel: .byte 0 min_alignment: .byte 0x10 -xloadflags: .word 0 # none of the flags supported +xloadflags: .word 0b01111 # all handover protocols except kexec cmdline_size: .long 4096-1 hardware_subarch: .long 0 hardware_subarch_data: .quad 0 payload_offset: .long 0xabababab # at 0x248/4, to be filled by the runner payload_length: .long 0xabababab # at 0x24c/4, to be filled by the runner setup_data: .quad 0 -pref_address: .quad 0 +pref_address: .quad CODE32_START - 0x200 * (SETUP_SECTS + 1); init_size: .long 0xabababab # at 0x260/4, to be filled by the runner -handover_offset: .long 0 +handover_offset: .long start_of_setup32 kernel_info_offset: .long 0 hdr_end: -// End of header. - -// 32-bit setup code starts here, and will be loaded at CODE32_START. -.section ".header.text", "ax" -.code32 -.global start_of_setup32 -start_of_setup32: - mov eax, offset stack_bottom - mov esp, eax - mov eax, offset halt - push eax # the return address - mov ebp, esp - add ebp, -4 - push ebp - mov ebp, esp - - .extern _rust_setup_entry - push esi # the boot_params pointer - call _rust_setup_entry - - // Unreachable here. -halt: - hlt - jmp halt - -// A small stack for the setup code. -.section ".stack", "aw" -SETUP_STACK_SIZE = 0x1000 - .align 16 - stack_top: - .skip SETUP_STACK_SIZE - stack_bottom: diff --git a/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/loader.rs b/framework/libs/boot-trojan/trojan/src/loader.rs similarity index 100% rename from framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/loader.rs rename to framework/libs/boot-trojan/trojan/src/loader.rs diff --git a/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/main.rs b/framework/libs/boot-trojan/trojan/src/main.rs similarity index 97% rename from framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/main.rs rename to framework/libs/boot-trojan/trojan/src/main.rs index aa621aa65..e46360c98 100644 --- a/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/main.rs +++ b/framework/libs/boot-trojan/trojan/src/main.rs @@ -9,6 +9,8 @@ use core::arch::{asm, global_asm}; global_asm!(include_str!("header.S")); +global_asm!(include_str!("setup.S")); + unsafe fn call_aster_entrypoint(entrypoint: u32, boot_params_ptr: u32) -> ! { asm!("mov esi, {}", in(reg) boot_params_ptr); asm!("mov eax, {}", in(reg) entrypoint); diff --git a/framework/libs/boot-trojan/trojan/src/setup.S b/framework/libs/boot-trojan/trojan/src/setup.S new file mode 100644 index 000000000..a9a820a17 --- /dev/null +++ b/framework/libs/boot-trojan/trojan/src/setup.S @@ -0,0 +1,39 @@ +// 32-bit setup code starts here, and will be loaded at CODE32_START. +.section ".setup", "ax" +.code32 +.global start_of_setup32 +start_of_setup32: + mov eax, offset stack_bottom + mov esp, eax + mov eax, offset halt + push eax # the return address + mov ebp, esp + add ebp, -4 + push ebp + mov ebp, esp + + .extern _rust_setup_entry + push esi # the boot_params pointer + call _rust_setup_entry + + // Unreachable here. +halt: + hlt + jmp halt + +.code64 +.global start_of_setup64 +.org 0x200 +start_of_setup64: + // Unreachable here. +halt64: + hlt + jmp halt64 + +// A small stack for the legacy setup code. +.section ".stack", "aw" +SETUP_STACK_SIZE = 0x1000 + .align 16 + stack_top: + .skip SETUP_STACK_SIZE + stack_bottom: diff --git a/framework/aster-frame/src/arch/x86/boot/linux_boot/setup/x86_64-i386_protected_mode.json b/framework/libs/boot-trojan/trojan/x86_64-i386_protected_mode.json similarity index 100% rename from framework/aster-frame/src/arch/x86/boot/linux_boot/setup/x86_64-i386_protected_mode.json rename to framework/libs/boot-trojan/trojan/x86_64-i386_protected_mode.json diff --git a/runner/Cargo.toml b/runner/Cargo.toml index 778bddfb3..f43589656 100644 --- a/runner/Cargo.toml +++ b/runner/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" [dependencies] anyhow = "1.0.32" clap = { version = "4.3.19", features = ["derive"] } -glob = "0.3.1" +aster-boot-trojan-builder = { path = "../framework/libs/boot-trojan/builder" } rand = "0.8.5" xmas-elf = "0.8.0" diff --git a/runner/src/machine/qemu_grub_efi/mod.rs b/runner/src/machine/qemu_grub_efi.rs similarity index 85% rename from runner/src/machine/qemu_grub_efi/mod.rs rename to runner/src/machine/qemu_grub_efi.rs index d88bd0694..096d44348 100644 --- a/runner/src/machine/qemu_grub_efi/mod.rs +++ b/runner/src/machine/qemu_grub_efi.rs @@ -1,4 +1,4 @@ -mod linux_boot; +use aster_boot_trojan_builder::{build_linux_setup_header_from_trojan, make_bzimage}; use std::{ fs, @@ -8,8 +8,6 @@ use std::{ use crate::BootProtocol; -use glob::glob; - macro_rules! ovmf_prefix { () => { // There are 3 optional OVMF builds at your service in the dev image @@ -70,13 +68,12 @@ pub const GRUB_PREFIX: &str = "/usr/local/grub"; pub const GRUB_VERSION: &str = "x86_64-efi"; pub fn create_bootdev_image( - atser_path: PathBuf, + aster_path: PathBuf, initramfs_path: PathBuf, grub_cfg: String, protocol: BootProtocol, - release_mode: bool, ) -> PathBuf { - let target_dir = atser_path.parent().unwrap(); + let target_dir = aster_path.parent().unwrap(); let iso_root = target_dir.join("iso_root"); // Clear or make the iso dir. @@ -94,26 +91,22 @@ pub fn create_bootdev_image( let target_path = match protocol { BootProtocol::Linux => { - // Find the setup header in the build script output directory. - let bs_out_dir = if release_mode { - glob("target/x86_64-custom/release/build/aster-frame-*").unwrap() - } else { - glob("target/x86_64-custom/debug/build/aster-frame-*").unwrap() - }; - let header_path = Path::new(bs_out_dir.into_iter().next().unwrap().unwrap().as_path()) - .join("out") - .join("bin") - .join("aster-frame-x86-boot-linux-setup"); + let trojan_install_dir = Path::new("target/-boot-trojan"); + build_linux_setup_header_from_trojan( + Path::new("framework/libs/boot-trojan/trojan"), + trojan_install_dir, + ) + .unwrap(); + let header_path = trojan_install_dir.join("bin").join("aster-boot-trojan"); // Make the `bzImage`-compatible kernel image and place it in the boot directory. let target_path = iso_root.join("boot").join("asterinaz"); - linux_boot::make_bzimage(&target_path, &atser_path.as_path(), &header_path.as_path()) - .unwrap(); + make_bzimage(&target_path, &aster_path.as_path(), &header_path.as_path()).unwrap(); 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(&atser_path, &target_path).unwrap(); + fs::copy(&aster_path, &target_path).unwrap(); target_path } }; diff --git a/runner/src/machine/qemu_grub_efi/linux_boot.rs b/runner/src/machine/qemu_grub_efi/linux_boot.rs deleted file mode 100644 index 3a1ea0b2f..000000000 --- a/runner/src/machine/qemu_grub_efi/linux_boot.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::{ - fs::File, - io::{Read, Write}, - path::Path, -}; - -use xmas_elf::program::{ProgramHeader, SegmentData}; - -// We chose the legacy setup sections to be 7 so that the setup header -// is page-aligned and the legacy setup section size would be 0x1000. -const LEGACY_SETUP_SECS: usize = 7; -const LEGACY_SETUP_SEC_SIZE: usize = 0x200 * (LEGACY_SETUP_SECS + 1); -const SETUP32_LMA: usize = 0x100000; - -/// We need a binary which satisfies `LMA == File_Offset`, and objcopy -/// does not satisfy us well, so we should parse the ELF and do our own -/// objcopy job. -/// -/// 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 header_to_raw_binary(elf_file: &[u8]) -> Vec { - let elf = xmas_elf::ElfFile::new(&elf_file).unwrap(); - let mut bin = Vec::::new(); - - for ph in elf.program_iter() { - let ProgramHeader::Ph32(program) = ph else { - panic!("Unexpected program header type"); - }; - if program.get_type().unwrap() == xmas_elf::program::Type::Load { - let SegmentData::Undefined(header_data) = program.get_data(&elf).unwrap() else { - panic!("Unexpected segment data type"); - }; - let dst_file_offset = - program.virtual_addr as usize + LEGACY_SETUP_SEC_SIZE - SETUP32_LMA; - let dst_file_length = program.file_size as usize; - if bin.len() < dst_file_offset + dst_file_length { - bin.resize(dst_file_offset + dst_file_length, 0); - } - let dest_slice = bin[dst_file_offset..dst_file_offset + dst_file_length].as_mut(); - dest_slice.copy_from_slice(header_data); - } - } - - bin -} - -/// This function sould be used when generating the Linux x86 Boot setup header. -/// Some fields in the Linux x86 Boot setup header should be filled after assembled. -/// And the filled fields must have the bytes with values of 0xAB. See -/// `framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/header.S` for more -/// info on this mechanism. -fn fill_header_field(header: &mut [u8], offset: usize, value: &[u8]) { - let size = value.len(); - assert_eq!( - &header[offset..offset + size], - vec![0xABu8; size].as_slice() - ); - header[offset..offset + size].copy_from_slice(value); -} - -pub fn make_bzimage(path: &Path, kernel_path: &Path, header_path: &Path) -> std::io::Result<()> { - let mut header = Vec::new(); - File::open(header_path)?.read_to_end(&mut header)?; - let mut header = header_to_raw_binary(&header); - // Pad the header to let the payload starts with 8-byte alignment. - header.resize((header.len() + 7) & !7, 0x00); - - let mut kernel = Vec::new(); - File::open(kernel_path)?.read_to_end(&mut kernel)?; - - let header_len = header.len(); - let kernel_len = kernel.len(); - - let payload_offset = header_len - LEGACY_SETUP_SEC_SIZE + SETUP32_LMA; - fill_header_field( - &mut header, - 0x248, /* payload_offset */ - &(payload_offset as u32).to_le_bytes(), - ); - - fill_header_field( - &mut header, - 0x24C, /* payload_length */ - &(kernel_len as u32).to_le_bytes(), - ); - - fill_header_field( - &mut header, - 0x260, /* init_size */ - &((header_len + kernel_len) as u32).to_le_bytes(), - ); - - let mut kernel_image = File::create(path)?; - kernel_image.write_all(&header)?; - kernel_image.write_all(&kernel)?; - - Ok(()) -} diff --git a/runner/src/main.rs b/runner/src/main.rs index bca0d69e6..9f97bc16a 100644 --- a/runner/src/main.rs +++ b/runner/src/main.rs @@ -79,10 +79,6 @@ struct Args { /// Run a GDB client instead of running the kernel. #[arg(long, default_value_t = false)] run_gdb_client: bool, - - /// Run in the release mode. - #[arg(long, default_value_t = false)] - release_mode: bool, } pub const COMMON_ARGS: &[&str] = &[ @@ -201,7 +197,6 @@ fn main() { initramfs_path, grub_cfg, args.boot_protocol, - args.release_mode, ); qemu_cmd.arg("-cdrom"); qemu_cmd.arg(bootdev_image.as_os_str());