diff --git a/Cargo.lock b/Cargo.lock index c289ea55f..4dfee7289 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -956,6 +956,10 @@ dependencies = [ "rle-decode-fast", ] +[[package]] +name = "linux_boot_params" +version = "0.1.0" + [[package]] name = "lock_api" version = "0.4.10" @@ -968,9 +972,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "lru" @@ -1028,7 +1032,7 @@ dependencies = [ "derive_more", "log", "ptr_meta", - "uefi-raw", + "uefi-raw 0.3.0", ] [[package]] @@ -1496,6 +1500,41 @@ dependencies = [ "x86", ] +[[package]] +name = "ucs2" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad643914094137d475641b6bab89462505316ec2ce70907ad20102d28a79ab8" +dependencies = [ + "bit_field", +] + +[[package]] +name = "uefi" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ead9f748a4646479b850add36b527113a80e80a7e0f44d7b0334291850dcc5" +dependencies = [ + "bitflags 2.3.3", + "log", + "ptr_meta", + "ucs2", + "uefi-macros", + "uefi-raw 0.5.0", + "uguid", +] + +[[package]] +name = "uefi-macros" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26a7b1c2c808c3db854a54d5215e3f7e7aaf5dcfbce095598cba6af29895695d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "uefi-raw" version = "0.3.0" @@ -1508,10 +1547,32 @@ dependencies = [ ] [[package]] -name = "uguid" -version = "2.0.1" +name = "uefi-raw" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16dfbd255defbd727b3a30e8950695d2e6d045841ee250ff0f1f7ced17917f8d" +checksum = "864ac69eadd877bfb34e7814be1928122ed0057d9f975169a56ee496aa7bdfd7" +dependencies = [ + "bitflags 2.3.3", + "ptr_meta", + "uguid", +] + +[[package]] +name = "uefi-services" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a79fcb420624743c895bad0f9480fbc2f64e7c8d8611fb1ada6bdd799942feb4" +dependencies = [ + "cfg-if", + "log", + "uefi", +] + +[[package]] +name = "uguid" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ef516f0806c5f61da6aa95125d0eb2d91cc95b2df426c06bde8be657282aee5" [[package]] name = "unicode-ident" diff --git a/Makefile b/Makefile index 2d8800766..0e0f5aa63 100644 --- a/Makefile +++ b/Makefile @@ -86,6 +86,7 @@ USERMODE_TESTABLE := \ runner \ framework/libs/align_ext \ framework/libs/boot-trojan/builder \ + framework/libs/boot-trojan/linux-boot-params \ framework/libs/ktest \ framework/libs/ktest-proc-macro \ services/libs/cpio-decoder \ diff --git a/framework/aster-frame/Cargo.toml b/framework/aster-frame/Cargo.toml index 827dfcef2..c55519c3d 100644 --- a/framework/aster-frame/Cargo.toml +++ b/framework/aster-frame/Cargo.toml @@ -10,6 +10,7 @@ align_ext = { path = "../libs/align_ext" } bit_field = "0.10.1" bitflags = "1.3" bitvec = { version = "1.0", default-features = false, features = ["alloc"] } +linux_boot_params = { path = "../libs/boot-trojan/linux-boot-params" } buddy_system_allocator = "0.9.0" cfg-if = "1.0" gimli = { version = "0.28", default-features = false, features = ["read-core"] } diff --git a/framework/aster-frame/src/arch/x86/boot/linux_boot/boot_params.rs b/framework/aster-frame/src/arch/x86/boot/linux_boot/boot_params.rs deleted file mode 100644 index b4a07971c..000000000 --- a/framework/aster-frame/src/arch/x86/boot/linux_boot/boot_params.rs +++ /dev/null @@ -1,291 +0,0 @@ -//! The Linux Boot Protocol boot_params module. -//! -//! The bootloader will deliver the address of the `BootParams` struct -//! as the argument of the kernel entrypoint. So we must define a Linux -//! ABI compatible struct in Rust, despite that most of the fields are -//! currently not needed by Asterinas. -//! - -#[derive(Copy, Clone, Debug)] -#[repr(C, packed)] -pub(super) struct ScreenInfo { - pub(super) orig_x: u8, /* 0x00 */ - pub(super) orig_y: u8, /* 0x01 */ - pub(super) ext_mem_k: u16, /* 0x02 */ - pub(super) orig_video_page: u16, /* 0x04 */ - pub(super) orig_video_mode: u8, /* 0x06 */ - pub(super) orig_video_cols: u8, /* 0x07 */ - pub(super) flags: u8, /* 0x08 */ - pub(super) unused2: u8, /* 0x09 */ - pub(super) orig_video_ega_bx: u16, /* 0x0a */ - pub(super) unused3: u16, /* 0x0c */ - pub(super) orig_video_lines: u8, /* 0x0e */ - pub(super) orig_video_is_vga: u8, /* 0x0f */ - pub(super) orig_video_points: u16, /* 0x10 */ - - /* VESA graphic mode -- linear frame buffer */ - pub(super) lfb_width: u16, /* 0x12 */ - pub(super) lfb_height: u16, /* 0x14 */ - pub(super) lfb_depth: u16, /* 0x16 */ - pub(super) lfb_base: u32, /* 0x18 */ - pub(super) lfb_size: u32, /* 0x1c */ - pub(super) cl_magic: u16, - pub(super) cl_offset: u16, /* 0x20 */ - pub(super) lfb_linelength: u16, /* 0x24 */ - pub(super) red_size: u8, /* 0x26 */ - pub(super) red_pos: u8, /* 0x27 */ - pub(super) green_size: u8, /* 0x28 */ - pub(super) green_pos: u8, /* 0x29 */ - pub(super) blue_size: u8, /* 0x2a */ - pub(super) blue_pos: u8, /* 0x2b */ - pub(super) rsvd_size: u8, /* 0x2c */ - pub(super) rsvd_pos: u8, /* 0x2d */ - pub(super) vesapm_seg: u16, /* 0x2e */ - pub(super) vesapm_off: u16, /* 0x30 */ - pub(super) pages: u16, /* 0x32 */ - pub(super) vesa_attributes: u16, /* 0x34 */ - pub(super) capabilities: u32, /* 0x36 */ - pub(super) ext_lfb_base: u32, /* 0x3a */ - pub(super) _reserved: [u8; 2], /* 0x3e */ -} - -#[derive(Copy, Clone, Debug)] -#[repr(C, packed)] -pub(super) struct ApmBiosInfo { - pub(super) version: u16, - pub(super) cseg: u16, - pub(super) offset: u32, - pub(super) cseg_16: u16, - pub(super) dseg: u16, - pub(super) flags: u16, - pub(super) cseg_len: u16, - pub(super) cseg_16_len: u16, - pub(super) dseg_len: u16, -} - -#[derive(Copy, Clone, Debug)] -#[repr(C, packed)] -pub(super) struct IstInfo { - pub(super) signature: u32, - pub(super) command: u32, - pub(super) event: u32, - pub(super) perf_level: u32, -} - -#[derive(Copy, Clone, Debug)] -#[repr(C, packed)] -pub(super) struct SysDescTable { - pub(super) length: u16, - pub(super) table: [u8; 14], -} - -#[derive(Copy, Clone, Debug)] -#[repr(C, packed)] -pub(super) struct OlpcOfwHeader { - pub(super) ofw_magic: u32, /* OFW signature */ - pub(super) ofw_version: u32, - pub(super) cif_handler: u32, /* callback into OFW */ - pub(super) irq_desc_table: u32, -} - -#[derive(Copy, Clone, Debug)] -#[repr(C)] -pub(super) struct EdidInfo { - pub(super) dummy: [u8; 128], -} - -#[derive(Copy, Clone, Debug)] -#[repr(C)] -pub(super) struct EfiInfo { - pub(super) efi_loader_signature: u32, - pub(super) efi_systab: u32, - pub(super) efi_memdesc_size: u32, - pub(super) efi_memdesc_version: u32, - pub(super) efi_memmap: u32, - pub(super) efi_memmap_size: u32, - pub(super) efi_systab_hi: u32, - pub(super) efi_memmap_hi: u32, -} - -/// Magic stored in SetupHeader.header. -pub(super) const LINUX_BOOT_HEADER_MAGIC: u32 = 0x53726448; - -/// Linux Boot Protocol Header. -/// -/// Originally defined in the linux source tree: -/// `linux/arch/x86/include/uapi/asm/bootparam.h` -#[derive(Copy, Clone, Debug)] -#[repr(C, packed)] -pub(super) struct SetupHeader { - pub(super) setup_sects: u8, - pub(super) root_flags: u16, - pub(super) syssize: u32, - pub(super) ram_size: u16, - pub(super) vid_mode: u16, - pub(super) root_dev: u16, - pub(super) boot_flag: u16, - pub(super) jump: u16, - pub(super) header: u32, - pub(super) version: u16, - pub(super) realmode_swtch: u32, - pub(super) start_sys_seg: u16, - pub(super) kernel_version: u16, - pub(super) type_of_loader: u8, - pub(super) loadflags: u8, - pub(super) setup_move_size: u16, - pub(super) code32_start: u32, - pub(super) ramdisk_image: u32, - pub(super) ramdisk_size: u32, - pub(super) bootsect_kludge: u32, - pub(super) heap_end_ptr: u16, - pub(super) ext_loader_ver: u8, - pub(super) ext_loader_type: u8, - pub(super) cmd_line_ptr: u32, - pub(super) initrd_addr_max: u32, - pub(super) kernel_alignment: u32, - pub(super) relocatable_kernel: u8, - pub(super) min_alignment: u8, - pub(super) xloadflags: u16, - pub(super) cmdline_size: u32, - pub(super) hardware_subarch: u32, - pub(super) hardware_subarch_data: u64, - pub(super) payload_offset: u32, - pub(super) payload_length: u32, - pub(super) setup_data: u64, - pub(super) pref_address: u64, - pub(super) init_size: u32, - pub(super) handover_offset: u32, - pub(super) kernel_info_offset: u32, -} - -/// The E820 types known to the kernel. -/// -/// Originally defined in the linux source tree: -/// `linux/arch/x86/include/asm/e820/types.h` -#[derive(Copy, Clone, Debug)] -#[repr(u32)] -pub(super) enum E820Type { - Ram = 1, - Reserved = 2, - Acpi = 3, - Nvs = 4, - Unusable = 5, - Pmem = 7, - /* - * This is a non-standardized way to represent ADR or - * NVDIMM regions that persist over a reboot. - * - * The kernel will ignore their special capabilities - * unless the CONFIG_X86_PMEM_LEGACY=y option is set. - * - * ( Note that older platforms also used 6 for the same - * type of memory, but newer versions switched to 12 as - * 6 was assigned differently. Some time they will learn... ) - */ - Pram = 12, - /* - * Special-purpose memory is indicated to the system via the - * EFI_MEMORY_SP attribute. Define an e820 translation of this - * memory type for the purpose of reserving this range and - * marking it with the IORES_DESC_SOFT_RESERVED designation. - */ - SoftReserved = 0xefffffff, - /* - * Reserved RAM used by the kernel itself if - * CONFIG_INTEL_TXT=y is enabled, memory of this type - * will be included in the S3 integrity calculation - * and so should not include any memory that the BIOS - * might alter over the S3 transition: - */ - ReservedKern = 128, -} - -#[derive(Copy, Clone, Debug)] -#[repr(C, packed)] -pub(super) struct BootE820Entry { - pub(super) addr: u64, - pub(super) size: u64, - pub(super) typ: E820Type, -} - -const E820_MAX_ENTRIES_ZEROPAGE: usize = 128; - -#[derive(Copy, Clone, Debug)] -#[repr(C, packed)] -pub(super) struct EddDeviceParams { - // TODO: We currently have no plans to support the edd device, - // and we need unnamed fields (Rust RFC 2102) to implement this - // FFI neatly. So we put a dummy implementation here conforming - // to the BootParams struct ABI. - pub(super) _dummy: [u8; (0xeec - 0xd00) / 6 - 8], -} - -#[derive(Copy, Clone, Debug)] -#[repr(C, packed)] -pub(super) struct EddInfo { - pub(super) device: u8, - pub(super) version: u8, - pub(super) interface_support: u16, - pub(super) legacy_max_cylinder: u16, - pub(super) legacy_max_head: u8, - pub(super) legacy_sectors_per_track: u8, - pub(super) params: EddDeviceParams, -} - -const EDD_MBR_SIG_MAX: usize = 16; -const EDDMAXNR: usize = 6; - -/// Linux 32/64-bit Boot Protocol parameter struct. -/// -/// Originally defined in the linux source tree: -/// `linux/arch/x86/include/uapi/asm/bootparam.h` -#[derive(Copy, Clone, Debug)] -#[repr(C, packed)] -pub(super) struct BootParams { - pub(super) screen_info: ScreenInfo, /* 0x000 */ - pub(super) apm_bios_info: ApmBiosInfo, /* 0x040 */ - pub(super) _pad2: [u8; 4], /* 0x054 */ - pub(super) tboot_addr: u64, /* 0x058 */ - pub(super) ist_info: IstInfo, /* 0x060 */ - pub(super) acpi_rsdp_addr: u64, /* 0x070 */ - pub(super) _pad3: [u8; 8], /* 0x078 */ - pub(super) hd0_info: [u8; 16], /* obsolete! 0x080 */ - pub(super) hd1_info: [u8; 16], /* obsolete! 0x090 */ - pub(super) sys_desc_table: SysDescTable, /* obsolete! 0x0a0 */ - pub(super) olpc_ofw_header: OlpcOfwHeader, /* 0x0b0 */ - pub(super) ext_ramdisk_image: u32, /* 0x0c0 */ - pub(super) ext_ramdisk_size: u32, /* 0x0c4 */ - pub(super) ext_cmd_line_ptr: u32, /* 0x0c8 */ - pub(super) _pad4: [u8; 112], /* 0x0cc */ - pub(super) cc_blob_address: u32, /* 0x13c */ - pub(super) edid_info: EdidInfo, /* 0x140 */ - pub(super) efi_info: EfiInfo, /* 0x1c0 */ - pub(super) alt_mem_k: u32, /* 0x1e0 */ - pub(super) scratch: u32, /* Scratch field! 0x1e4 */ - pub(super) e820_entries: u8, /* 0x1e8 */ - pub(super) eddbuf_entries: u8, /* 0x1e9 */ - pub(super) edd_mbr_sig_buf_entries: u8, /* 0x1ea */ - pub(super) kbd_status: u8, /* 0x1eb */ - pub(super) secure_boot: u8, /* 0x1ec */ - pub(super) _pad5: [u8; 2], /* 0x1ed */ - /* - * The sentinel is set to a nonzero value (0xff) in header.S. - * - * A bootloader is supposed to only take setup_header and put - * it into a clean boot_params buffer. If it turns out that - * it is clumsy or too generous with the buffer, it most - * probably will pick up the sentinel variable too. The fact - * that this variable then is still 0xff will let kernel - * know that some variables in boot_params are invalid and - * kernel should zero out certain portions of boot_params. - */ - pub(super) sentinel: u8, /* 0x1ef */ - pub(super) _pad6: [u8; 1], /* 0x1f0 */ - pub(super) hdr: SetupHeader, /* setup header 0x1f1 */ - pub(super) _pad7: [u8; 0x290 - 0x1f1 - core::mem::size_of::()], - pub(super) edd_mbr_sig_buffer: [u32; EDD_MBR_SIG_MAX], /* 0x290 */ - pub(super) e820_table: [BootE820Entry; E820_MAX_ENTRIES_ZEROPAGE], /* 0x2d0 */ - pub(super) _pad8: [u8; 48], /* 0xcd0 */ - pub(super) eddbuf: [EddInfo; EDDMAXNR], /* 0xd00 */ - pub(super) _pad9: [u8; 276], /* 0xeec */ -} diff --git a/framework/aster-frame/src/arch/x86/boot/linux_boot/mod.rs b/framework/aster-frame/src/arch/x86/boot/linux_boot/mod.rs index 1967faa99..58d577790 100644 --- a/framework/aster-frame/src/arch/x86/boot/linux_boot/mod.rs +++ b/framework/aster-frame/src/arch/x86/boot/linux_boot/mod.rs @@ -1,8 +1,8 @@ //! The Linux 64-bit Boot Protocol supporting module. //! -mod boot_params; -use boot_params::E820Type; +extern crate linux_boot_params; +use linux_boot_params::{BootParams, E820Type, LINUX_BOOT_HEADER_MAGIC}; use crate::boot::{ kcmdline::KCmdlineArg, @@ -16,7 +16,7 @@ use core::ffi::CStr; use spin::Once; -static BOOT_PARAMS: Once = Once::new(); +static BOOT_PARAMS: Once = Once::new(); fn init_bootloader_name(bootloader_name: &'static Once) { let hdr = &BOOT_PARAMS.get().unwrap().hdr; @@ -140,9 +140,9 @@ fn init_memory_regions(memory_regions: &'static Once>) { /// The entry point of Rust code called by the Linux 64-bit boot compatible bootloader. #[no_mangle] -unsafe extern "sysv64" fn __linux64_boot(params_ptr: *const boot_params::BootParams) -> ! { +unsafe extern "sysv64" fn __linux64_boot(params_ptr: *const BootParams) -> ! { let params = *params_ptr; - assert_eq!({ params.hdr.header }, boot_params::LINUX_BOOT_HEADER_MAGIC); + assert_eq!({ params.hdr.header }, LINUX_BOOT_HEADER_MAGIC); BOOT_PARAMS.call_once(|| params); crate::boot::register_boot_init_callbacks( init_bootloader_name, diff --git a/framework/libs/boot-trojan/builder/src/lib.rs b/framework/libs/boot-trojan/builder/src/lib.rs index 31184258c..e6e530ecc 100644 --- a/framework/libs/boot-trojan/builder/src/lib.rs +++ b/framework/libs/boot-trojan/builder/src/lib.rs @@ -2,7 +2,6 @@ mod mapping; mod pe_header; use std::{ - ffi::OsStr, fs::File, io::{Read, Write}, path::{Path, PathBuf}, @@ -85,12 +84,15 @@ fn fill_legacy_header_fields( pub fn make_bzimage(path: &Path, kernel_path: &Path, trojan_src: &Path, trojan_out: &Path) { #[cfg(feature = "trojan64")] - let trojan = build_trojan_with_arch(trojan_src, trojan_out, "x86_64-unknown-none".as_ref()); + let trojan = build_trojan_with_arch(trojan_src, trojan_out, &TrojanBuildArch::X86_64); #[cfg(not(feature = "trojan64"))] let trojan = { - let arch = trojan_src.join("x86_64-i386_pm-none.json"); - build_trojan_with_arch(trojan_src, trojan_out, arch.as_os_str()) + let arch = trojan_src + .join("x86_64-i386_pm-none.json") + .canonicalize() + .unwrap(); + build_trojan_with_arch(trojan_src, trojan_out, &TrojanBuildArch::Other(arch)) }; let mut trojan_elf = Vec::new(); @@ -140,14 +142,32 @@ pub fn make_bzimage(path: &Path, kernel_path: &Path, trojan_src: &Path, trojan_o } } -fn build_trojan_with_arch(source_dir: &Path, out_dir: &Path, arch: &OsStr) -> PathBuf { +// 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 { + X86_64, + Other(PathBuf), +} + +fn build_trojan_with_arch(source_dir: &Path, out_dir: &Path, arch: &TrojanBuildArch) -> PathBuf { + if !out_dir.exists() { + std::fs::create_dir_all(&out_dir).unwrap(); + } + let out_dir = std::fs::canonicalize(out_dir).unwrap(); + let cargo = std::env::var("CARGO").unwrap(); let mut cmd = std::process::Command::new(cargo); + cmd.current_dir(source_dir); cmd.arg("build"); + // Relocations are fewer in release mode, saving header real-estate. + cmd.arg("--release"); cmd.arg("--package").arg("aster-boot-trojan"); - cmd.arg("--manifest-path") - .arg(source_dir.join("Cargo.toml").as_os_str()); - cmd.arg("--target").arg(arch); + cmd.arg("--target").arg(match arch { + TrojanBuildArch::X86_64 => "x86_64-unknown-none", + TrojanBuildArch::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 @@ -155,6 +175,7 @@ fn build_trojan_with_arch(source_dir: &Path, out_dir: &Path, arch: &OsStr) -> Pa cmd.arg("--target-dir").arg(out_dir.as_os_str()); cmd.env_remove("RUSTFLAGS"); cmd.env_remove("CARGO_ENCODED_RUSTFLAGS"); + let mut child = cmd.spawn().unwrap(); let status = child.wait().unwrap(); if !status.success() { @@ -164,12 +185,15 @@ fn build_trojan_with_arch(source_dir: &Path, out_dir: &Path, arch: &OsStr) -> Pa ); } - // If the arch is a builtin target rather than json, the path operation works as well. - let arch_name = Path::new(arch).file_stem().unwrap().to_str().unwrap(); + // Return the path to the trojan binary. + let arch_name = match arch { + TrojanBuildArch::X86_64 => "x86_64-unknown-none", + TrojanBuildArch::Other(path) => path.file_stem().unwrap().to_str().unwrap(), + }; let trojan_artifact = out_dir .join(arch_name) - .join("debug") + .join("release") .join("aster-boot-trojan"); trojan_artifact.to_owned() diff --git a/framework/libs/boot-trojan/builder/src/mapping.rs b/framework/libs/boot-trojan/builder/src/mapping.rs index b3044a673..471d886bb 100644 --- a/framework/libs/boot-trojan/builder/src/mapping.rs +++ b/framework/libs/boot-trojan/builder/src/mapping.rs @@ -1,7 +1,11 @@ //! 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}; +use std::{ + cmp::PartialOrd, + convert::From, + ops::{Add, 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. @@ -40,6 +44,16 @@ impl Sub for TrojanVA { } } +impl Add for TrojanVA { + type Output = Self; + + fn add(self, rhs: usize) -> Self::Output { + Self { + addr: self.addr + rhs, + } + } +} + impl From for TrojanFileOffset { fn from(offset: usize) -> Self { Self { offset } @@ -60,6 +74,16 @@ impl Sub for TrojanFileOffset { } } +impl Add for TrojanFileOffset { + type Output = Self; + + fn add(self, rhs: usize) -> Self::Output { + Self { + offset: self.offset + rhs, + } + } +} + impl From for TrojanFileOffset { fn from(va: TrojanVA) -> Self { Self { diff --git a/framework/libs/boot-trojan/builder/src/pe_header.rs b/framework/libs/boot-trojan/builder/src/pe_header.rs index 6bc640c80..62922341e 100644 --- a/framework/libs/boot-trojan/builder/src/pe_header.rs +++ b/framework/libs/boot-trojan/builder/src/pe_header.rs @@ -104,6 +104,42 @@ struct Pe32PlusOptHdr { data_dirs: u32, // number of data dir entries } +#[derive(Zeroable, Pod, Serialize, Clone, Copy)] +#[repr(C, packed)] +struct Pe32PlusOptDataDirEnt { + /// The RVA is the address of the table relative to the base address of the image when the table is loaded. + rva: u32, + size: u32, +} + +impl Pe32PlusOptDataDirEnt { + fn none() -> Self { + Self { rva: 0, size: 0 } + } +} + +/// The data directories in the PE32+ optional header. +/// +/// The `data_dirs` number field in the PE32+ optional header is just an illusion that you can choose to have a +/// subset of the data directories. The actual number of data directories is fixed to 16 and you can only ignore +/// data directories at the end of the list. We ignore data directories after the 8th as what Linux do. +#[derive(Zeroable, Pod, Serialize, Clone, Copy)] +#[repr(C, packed)] +struct Pe32PlusOptDataDirs { + export_table: Pe32PlusOptDataDirEnt, + import_table: Pe32PlusOptDataDirEnt, + resource_table: Pe32PlusOptDataDirEnt, + exception_table: Pe32PlusOptDataDirEnt, + certificate_table: Pe32PlusOptDataDirEnt, + base_relocation_table: Pe32PlusOptDataDirEnt, +} + +impl Pe32PlusOptDataDirs { + fn num_dirs() -> usize { + size_of::() / size_of::() + } +} + // The `flags` field choices in the PE section header. // Excluding the alignment flags, which is not bitflags. bitflags::bitflags! { @@ -163,51 +199,50 @@ struct PeSectionHdr { } struct TrojanSectionAddrInfo { - pub setup: Range, pub text: Range, + pub data: Range, + pub bss: Range, + /// All the readonly but loaded sections. + pub rodata: 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); + let mut data_start = None; + let mut data_end = None; + let mut bss_start = None; + let mut bss_end = None; + let mut rodata_start = None; + let mut rodata_end = None; + for program in elf.program_iter() { + if program.get_type().unwrap() == xmas_elf::program::Type::Load { + let offset = TrojanVA::from(program.virtual_addr() as usize); + let length = program.mem_size() as usize; + if program.flags().is_execute() { + text_start = Some(offset); + text_end = Some(offset + length); + } else if program.flags().is_write() { + data_start = Some(offset); + data_end = Some(offset + program.file_size() as usize); + bss_start = Some(offset + program.file_size() as usize); + bss_end = Some(offset + length); + } else if program.flags().is_read() { + rodata_start = Some(offset); + rodata_end = Some(offset + length); } } } - 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()), + data: TrojanVA::from(data_start.unwrap())..TrojanVA::from(data_end.unwrap()), + bss: TrojanVA::from(bss_start.unwrap())..TrojanVA::from(bss_end.unwrap()), + rodata: TrojanVA::from(rodata_start.unwrap())..TrojanVA::from(rodata_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 } @@ -215,6 +250,26 @@ impl TrojanSectionAddrInfo { fn text_file_size(&self) -> usize { TrojanFileOffset::from(self.text.end) - TrojanFileOffset::from(self.text.start) } + + fn data_virt_size(&self) -> usize { + self.data.end - self.data.start + } + + fn data_file_size(&self) -> usize { + TrojanFileOffset::from(self.data.end) - TrojanFileOffset::from(self.data.start) + } + + fn bss_virt_size(&self) -> usize { + self.bss.end - self.bss.start + } + + fn rodata_virt_size(&self) -> usize { + self.rodata.end - self.rodata.start + } + + fn rodata_file_size(&self) -> usize { + TrojanFileOffset::from(self.rodata.end) - TrojanFileOffset::from(self.rodata.start) + } } pub struct TrojanPeCoffHeaderBuf { @@ -226,11 +281,16 @@ pub(crate) fn make_pe_coff_header(setup_elf: &[u8], image_size: usize) -> Trojan let elf = xmas_elf::ElfFile::new(setup_elf).unwrap(); let mut bin = Vec::::new(); + // The EFI application loader requires a relocation section. + let relocs = vec![]; + // The place where we put the stub, must be after the legacy header and before 0x1000. + let reloc_offset = TrojanFileOffset::from(0x500); + // PE header - let pe_hdr = PeHdr { + let mut 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 + sections: 0, // this field will be modified later timestamp: 0, symbol_table: 0, symbols: 1, // I don't know why, Linux header.S says it's 1 @@ -271,33 +331,27 @@ pub(crate) fn make_pe_coff_header(setup_elf: &[u8], image_size: usize) -> Trojan heap_size_req: 0, heap_size: 0, loader_flags: 0, - data_dirs: 0, + data_dirs: Pe32PlusOptDataDirs::num_dirs() as u32, + }; + + let pe_opt_hdr_data_dirs = Pe32PlusOptDataDirs { + export_table: Pe32PlusOptDataDirEnt::none(), + import_table: Pe32PlusOptDataDirEnt::none(), + resource_table: Pe32PlusOptDataDirEnt::none(), + exception_table: Pe32PlusOptDataDirEnt::none(), + certificate_table: Pe32PlusOptDataDirEnt::none(), + base_relocation_table: Pe32PlusOptDataDirEnt { + rva: usize::from(reloc_offset) as u32, + size: relocs.len() as u32, + }, }; 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 { + let mut sec_hdrs = Vec::::new(); + // .reloc + sec_hdrs.push(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, @@ -312,8 +366,9 @@ pub(crate) fn make_pe_coff_header(setup_elf: &[u8], image_size: usize) -> Trojan | PeSectionHdrFlags::MEM_DISCARDABLE) .bits | PeSectionHdrFlagsAlign::_1Bytes as u32, - }; - let pe_text_hdr = PeSectionHdr { + }); + // .text + sec_hdrs.push(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, @@ -328,7 +383,55 @@ pub(crate) fn make_pe_coff_header(setup_elf: &[u8], image_size: usize) -> Trojan | PeSectionHdrFlags::MEM_EXECUTE) .bits | PeSectionHdrFlagsAlign::_16Bytes as u32, - }; + }); + // .data + sec_hdrs.push(PeSectionHdr { + name: [b'.', b'd', b'a', b't', b'a', 0, 0, 0], + virtual_size: addr_info.data_virt_size() as u32, + virtual_address: usize::from(addr_info.data.start) as u32, + raw_data_size: addr_info.data_file_size() as u32, + data_addr: usize::from(TrojanFileOffset::from(addr_info.data.start)) as u32, + relocs: 0, + line_numbers: 0, + num_relocs: 0, + num_lin_numbers: 0, + flags: (PeSectionHdrFlags::CNT_INITIALIZED_DATA + | PeSectionHdrFlags::MEM_READ + | PeSectionHdrFlags::MEM_WRITE) + .bits + | PeSectionHdrFlagsAlign::_16Bytes as u32, + }); + // .bss + sec_hdrs.push(PeSectionHdr { + name: [b'.', b'b', b's', b's', 0, 0, 0, 0], + virtual_size: addr_info.bss_virt_size() as u32, + virtual_address: usize::from(addr_info.bss.start) as u32, + raw_data_size: 0, + data_addr: 0, + relocs: 0, + line_numbers: 0, + num_relocs: 0, + num_lin_numbers: 0, + flags: (PeSectionHdrFlags::CNT_UNINITIALIZED_DATA + | PeSectionHdrFlags::MEM_READ + | PeSectionHdrFlags::MEM_WRITE) + .bits + | PeSectionHdrFlagsAlign::_16Bytes as u32, + }); + // .rodata + sec_hdrs.push(PeSectionHdr { + name: [b'.', b'r', b'o', b'd', b'a', b't', b'a', 0], + virtual_size: addr_info.rodata_virt_size() as u32, + virtual_address: usize::from(addr_info.rodata.start) as u32, + raw_data_size: addr_info.rodata_file_size() as u32, + data_addr: usize::from(TrojanFileOffset::from(addr_info.rodata.start)) as u32, + relocs: 0, + line_numbers: 0, + num_relocs: 0, + num_lin_numbers: 0, + flags: (PeSectionHdrFlags::CNT_INITIALIZED_DATA | PeSectionHdrFlags::MEM_READ).bits + | PeSectionHdrFlagsAlign::_16Bytes as u32, + }); // Write the MS-DOS header bin.extend_from_slice(&MZ_MAGIC.to_le_bytes()); @@ -338,13 +441,15 @@ pub(crate) fn make_pe_coff_header(setup_elf: &[u8], image_size: usize) -> Trojan bin.extend_from_slice(&(0x3cu32 + size_of::() as u32).to_le_bytes()); // Write the PE header + pe_hdr.sections = sec_hdrs.len() as u16; bin.extend_from_slice(bytemuck::bytes_of(&pe_hdr)); // Write the PE32+ optional header bin.extend_from_slice(bytemuck::bytes_of(&pe_opt_hdr)); + bin.extend_from_slice(bytemuck::bytes_of(&pe_opt_hdr_data_dirs)); // 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)); + for sec_hdr in sec_hdrs { + bin.extend_from_slice(bytemuck::bytes_of(&sec_hdr)); + } TrojanPeCoffHeaderBuf { header_at_zero: bin, diff --git a/framework/libs/boot-trojan/linux-boot-params/Cargo.toml b/framework/libs/boot-trojan/linux-boot-params/Cargo.toml new file mode 100644 index 000000000..6da15de39 --- /dev/null +++ b/framework/libs/boot-trojan/linux-boot-params/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "linux_boot_params" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/framework/libs/boot-trojan/linux-boot-params/src/lib.rs b/framework/libs/boot-trojan/linux-boot-params/src/lib.rs new file mode 100644 index 000000000..b7855402b --- /dev/null +++ b/framework/libs/boot-trojan/linux-boot-params/src/lib.rs @@ -0,0 +1,293 @@ +//! The Linux Boot Protocol boot_params definition. +//! +//! The bootloader will deliver the address of the `BootParams` struct +//! as the argument of the kernel entrypoint. So we must define a Linux +//! ABI compatible struct in Rust, despite that most of the fields are +//! currently not needed by Asterinas. +//! + +#![cfg_attr(not(test), no_std)] + +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct ScreenInfo { + pub orig_x: u8, /* 0x00 */ + pub orig_y: u8, /* 0x01 */ + pub ext_mem_k: u16, /* 0x02 */ + pub orig_video_page: u16, /* 0x04 */ + pub orig_video_mode: u8, /* 0x06 */ + pub orig_video_cols: u8, /* 0x07 */ + pub flags: u8, /* 0x08 */ + pub unused2: u8, /* 0x09 */ + pub orig_video_ega_bx: u16, /* 0x0a */ + pub unused3: u16, /* 0x0c */ + pub orig_video_lines: u8, /* 0x0e */ + pub orig_video_is_vga: u8, /* 0x0f */ + pub orig_video_points: u16, /* 0x10 */ + + /* VESA graphic mode -- linear frame buffer */ + pub lfb_width: u16, /* 0x12 */ + pub lfb_height: u16, /* 0x14 */ + pub lfb_depth: u16, /* 0x16 */ + pub lfb_base: u32, /* 0x18 */ + pub lfb_size: u32, /* 0x1c */ + pub cl_magic: u16, + pub cl_offset: u16, /* 0x20 */ + pub lfb_linelength: u16, /* 0x24 */ + pub red_size: u8, /* 0x26 */ + pub red_pos: u8, /* 0x27 */ + pub green_size: u8, /* 0x28 */ + pub green_pos: u8, /* 0x29 */ + pub blue_size: u8, /* 0x2a */ + pub blue_pos: u8, /* 0x2b */ + pub rsvd_size: u8, /* 0x2c */ + pub rsvd_pos: u8, /* 0x2d */ + pub vesapm_seg: u16, /* 0x2e */ + pub vesapm_off: u16, /* 0x30 */ + pub pages: u16, /* 0x32 */ + pub vesa_attributes: u16, /* 0x34 */ + pub capabilities: u32, /* 0x36 */ + pub ext_lfb_base: u32, /* 0x3a */ + pub _reserved: [u8; 2], /* 0x3e */ +} + +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct ApmBiosInfo { + pub version: u16, + pub cseg: u16, + pub offset: u32, + pub cseg_16: u16, + pub dseg: u16, + pub flags: u16, + pub cseg_len: u16, + pub cseg_16_len: u16, + pub dseg_len: u16, +} + +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct IstInfo { + pub signature: u32, + pub command: u32, + pub event: u32, + pub perf_level: u32, +} + +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct SysDescTable { + pub length: u16, + pub table: [u8; 14], +} + +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct OlpcOfwHeader { + pub ofw_magic: u32, /* OFW signature */ + pub ofw_version: u32, + pub cif_handler: u32, /* callback into OFW */ + pub irq_desc_table: u32, +} + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct EdidInfo { + pub dummy: [u8; 128], +} + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct EfiInfo { + pub efi_loader_signature: u32, + pub efi_systab: u32, + pub efi_memdesc_size: u32, + pub efi_memdesc_version: u32, + pub efi_memmap: u32, + pub efi_memmap_size: u32, + pub efi_systab_hi: u32, + pub efi_memmap_hi: u32, +} + +/// Magic stored in SetupHeader.header. +pub const LINUX_BOOT_HEADER_MAGIC: u32 = 0x53726448; + +/// Linux Boot Protocol Header. +/// +/// Originally defined in the linux source tree: +/// `linux/arch/x86/include/uapi/asm/bootparam.h` +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct SetupHeader { + pub setup_sects: u8, + pub root_flags: u16, + pub syssize: u32, + pub ram_size: u16, + pub vid_mode: u16, + pub root_dev: u16, + pub boot_flag: u16, + pub jump: u16, + pub header: u32, + pub version: u16, + pub realmode_swtch: u32, + pub start_sys_seg: u16, + pub kernel_version: u16, + pub type_of_loader: u8, + pub loadflags: u8, + pub setup_move_size: u16, + pub code32_start: u32, + pub ramdisk_image: u32, + pub ramdisk_size: u32, + pub bootsect_kludge: u32, + pub heap_end_ptr: u16, + pub ext_loader_ver: u8, + pub ext_loader_type: u8, + pub cmd_line_ptr: u32, + pub initrd_addr_max: u32, + pub kernel_alignment: u32, + pub relocatable_kernel: u8, + pub min_alignment: u8, + pub xloadflags: u16, + pub cmdline_size: u32, + pub hardware_subarch: u32, + pub hardware_subarch_data: u64, + pub payload_offset: u32, + pub payload_length: u32, + pub setup_data: u64, + pub pref_address: u64, + pub init_size: u32, + pub handover_offset: u32, + pub kernel_info_offset: u32, +} + +/// The E820 types known to the kernel. +/// +/// Originally defined in the linux source tree: +/// `linux/arch/x86/include/asm/e820/types.h` +#[derive(Copy, Clone, Debug)] +#[repr(u32)] +pub enum E820Type { + Ram = 1, + Reserved = 2, + Acpi = 3, + Nvs = 4, + Unusable = 5, + Pmem = 7, + /* + * This is a non-standardized way to represent ADR or + * NVDIMM regions that persist over a reboot. + * + * The kernel will ignore their special capabilities + * unless the CONFIG_X86_PMEM_LEGACY=y option is set. + * + * ( Note that older platforms also used 6 for the same + * type of memory, but newer versions switched to 12 as + * 6 was assigned differently. Some time they will learn... ) + */ + Pram = 12, + /* + * Special-purpose memory is indicated to the system via the + * EFI_MEMORY_SP attribute. Define an e820 translation of this + * memory type for the purpose of reserving this range and + * marking it with the IORES_DESC_SOFT_RESERVED designation. + */ + SoftReserved = 0xefffffff, + /* + * Reserved RAM used by the kernel itself if + * CONFIG_INTEL_TXT=y is enabled, memory of this type + * will be included in the S3 integrity calculation + * and so should not include any memory that the BIOS + * might alter over the S3 transition: + */ + ReservedKern = 128, +} + +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct BootE820Entry { + pub addr: u64, + pub size: u64, + pub typ: E820Type, +} + +const E820_MAX_ENTRIES_ZEROPAGE: usize = 128; + +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct EddDeviceParams { + // TODO: We currently have no plans to support the edd device, + // and we need unnamed fields (Rust RFC 2102) to implement this + // FFI neatly. So we put a dummy implementation here conforming + // to the BootParams struct ABI. + pub _dummy: [u8; (0xeec - 0xd00) / 6 - 8], +} + +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct EddInfo { + pub device: u8, + pub version: u8, + pub interface_support: u16, + pub legacy_max_cylinder: u16, + pub legacy_max_head: u8, + pub legacy_sectors_per_track: u8, + pub params: EddDeviceParams, +} + +const EDD_MBR_SIG_MAX: usize = 16; +const EDDMAXNR: usize = 6; + +/// Linux 32/64-bit Boot Protocol parameter struct. +/// +/// Originally defined in the linux source tree: +/// `linux/arch/x86/include/uapi/asm/bootparam.h` +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct BootParams { + pub screen_info: ScreenInfo, /* 0x000 */ + pub apm_bios_info: ApmBiosInfo, /* 0x040 */ + pub _pad2: [u8; 4], /* 0x054 */ + pub tboot_addr: u64, /* 0x058 */ + pub ist_info: IstInfo, /* 0x060 */ + pub acpi_rsdp_addr: u64, /* 0x070 */ + pub _pad3: [u8; 8], /* 0x078 */ + pub hd0_info: [u8; 16], /* obsolete! 0x080 */ + pub hd1_info: [u8; 16], /* obsolete! 0x090 */ + pub sys_desc_table: SysDescTable, /* obsolete! 0x0a0 */ + pub olpc_ofw_header: OlpcOfwHeader, /* 0x0b0 */ + pub ext_ramdisk_image: u32, /* 0x0c0 */ + pub ext_ramdisk_size: u32, /* 0x0c4 */ + pub ext_cmd_line_ptr: u32, /* 0x0c8 */ + pub _pad4: [u8; 112], /* 0x0cc */ + pub cc_blob_address: u32, /* 0x13c */ + pub edid_info: EdidInfo, /* 0x140 */ + pub efi_info: EfiInfo, /* 0x1c0 */ + pub alt_mem_k: u32, /* 0x1e0 */ + pub scratch: u32, /* Scratch field! 0x1e4 */ + pub e820_entries: u8, /* 0x1e8 */ + pub eddbuf_entries: u8, /* 0x1e9 */ + pub edd_mbr_sig_buf_entries: u8, /* 0x1ea */ + pub kbd_status: u8, /* 0x1eb */ + pub secure_boot: u8, /* 0x1ec */ + pub _pad5: [u8; 2], /* 0x1ed */ + /* + * The sentinel is set to a nonzero value (0xff) in header.S. + * + * A bootloader is supposed to only take setup_header and put + * it into a clean boot_params buffer. If it turns out that + * it is clumsy or too generous with the buffer, it most + * probably will pick up the sentinel variable too. The fact + * that this variable then is still 0xff will let kernel + * know that some variables in boot_params are invalid and + * kernel should zero out certain portions of boot_params. + */ + pub sentinel: u8, /* 0x1ef */ + pub _pad6: [u8; 1], /* 0x1f0 */ + pub hdr: SetupHeader, /* setup header 0x1f1 */ + pub _pad7: [u8; 0x290 - 0x1f1 - core::mem::size_of::()], + pub edd_mbr_sig_buffer: [u32; EDD_MBR_SIG_MAX], /* 0x290 */ + pub e820_table: [BootE820Entry; E820_MAX_ENTRIES_ZEROPAGE], /* 0x2d0 */ + pub _pad8: [u8; 48], /* 0xcd0 */ + pub eddbuf: [EddInfo; EDDMAXNR], /* 0xd00 */ + pub _pad9: [u8; 276], /* 0xeec */ +} diff --git a/framework/libs/boot-trojan/trojan/.cargo/config.toml b/framework/libs/boot-trojan/trojan/.cargo/config.toml new file mode 100644 index 000000000..c99a31b3e --- /dev/null +++ b/framework/libs/boot-trojan/trojan/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.x86_64-unknown-none] +rustflags = [ + "-Ccode-model=kernel", + "-Crelocation-model=pie", + "-Ctarget-feature=+crt-static", + "-Zplt=yes", + "-Zrelax-elf-relocations=yes", + "-Zrelro-level=full", +] diff --git a/framework/libs/boot-trojan/trojan/Cargo.toml b/framework/libs/boot-trojan/trojan/Cargo.toml index 40b7fdcea..943f73398 100644 --- a/framework/libs/boot-trojan/trojan/Cargo.toml +++ b/framework/libs/boot-trojan/trojan/Cargo.toml @@ -7,5 +7,11 @@ edition = "2021" [dependencies] cfg-if = "1.0.0" +linux_boot_params = { path = "../linux-boot-params" } uart_16550 = "0.3.0" -xmas-elf = "0.8.0" \ No newline at end of file +xmas-elf = "0.8.0" + +[target.x86_64-unknown-none.dependencies] +log = "0.4.20" +uefi = "0.26.0" +uefi-services = "0.23.0" diff --git a/framework/libs/boot-trojan/trojan/build.rs b/framework/libs/boot-trojan/trojan/build.rs index dd139ea17..26b8e7b2f 100644 --- a/framework/libs/boot-trojan/trojan/build.rs +++ b/framework/libs/boot-trojan/trojan/build.rs @@ -4,15 +4,12 @@ fn main() { let source_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); let target_arch = std::env::var("TARGET").unwrap(); let linker_script = if target_arch == "x86_64-unknown-none" { - source_dir.join("src/arch/x86_64.linker.ld") + source_dir.join("src/arch/x86_64/linker.ld") } else if target_arch == "x86_64-i386_pm-none" { - source_dir.join("src/arch/i386.linker.ld") + source_dir.join("src/arch/i386/linker.ld") } else { panic!("Unsupported target_arch: {}", target_arch); }; println!("cargo:rerun-if-changed={}", linker_script.display()); - println!( - "cargo:rustc-link-arg-bins=--script={}", - linker_script.display() - ); + println!("cargo:rustc-link-arg=-T{}", linker_script.display()); } diff --git a/framework/libs/boot-trojan/trojan/src/arch/header.S b/framework/libs/boot-trojan/trojan/src/arch/i386/header.S similarity index 94% rename from framework/libs/boot-trojan/trojan/src/arch/header.S rename to framework/libs/boot-trojan/trojan/src/arch/i386/header.S index 9f6a3209b..32a48f1ec 100644 --- a/framework/libs/boot-trojan/trojan/src/arch/header.S +++ b/framework/libs/boot-trojan/trojan/src/arch/i386/header.S @@ -42,7 +42,7 @@ initrd_addr_max: .long 0x7fffffff kernel_alignment: .long 0x1000000 relocatable_kernel: .byte 0 min_alignment: .byte 0x10 -xloadflags: .word 0b01111 # all handover protocols except kexec +xloadflags: .word 0 cmdline_size: .long 4096-1 hardware_subarch: .long 0 hardware_subarch_data: .quad 0 @@ -51,6 +51,6 @@ payload_length: .long 0xabababab # at 0x24c/4, to be filled by the build setup_data: .quad 0 pref_address: .quad CODE32_START - 0x200 * (SETUP_SECTS + 1); init_size: .long 0xabababab # at 0x260/4, to be filled by the builder -handover_offset: .long CODE32_START +handover_offset: .long 0 kernel_info_offset: .long 0 hdr_end: diff --git a/framework/libs/boot-trojan/trojan/src/arch/i386.linker.ld b/framework/libs/boot-trojan/trojan/src/arch/i386/linker.ld similarity index 100% rename from framework/libs/boot-trojan/trojan/src/arch/i386.linker.ld rename to framework/libs/boot-trojan/trojan/src/arch/i386/linker.ld diff --git a/framework/libs/boot-trojan/trojan/src/arch/i386.rs b/framework/libs/boot-trojan/trojan/src/arch/i386/mod.rs similarity index 62% rename from framework/libs/boot-trojan/trojan/src/arch/i386.rs rename to framework/libs/boot-trojan/trojan/src/arch/i386/mod.rs index c9b89edbf..e416ef5eb 100644 --- a/framework/libs/boot-trojan/trojan/src/arch/i386.rs +++ b/framework/libs/boot-trojan/trojan/src/arch/i386/mod.rs @@ -1,3 +1,5 @@ +use crate::println; + use core::arch::{asm, global_asm}; global_asm!(include_str!("header.S")); @@ -6,9 +8,11 @@ global_asm!(include_str!("setup.S")); #[no_mangle] extern "cdecl" fn _trojan_entry_32(boot_params_ptr: u32) -> ! { - crate::trojan_entry(boot_params_ptr); + crate::trojan_entry(0x100000, boot_params_ptr.try_into().unwrap()); } +pub const ASTER_ENTRY_POINT: u32 = 0x8001000; + pub 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); @@ -16,3 +20,9 @@ pub unsafe fn call_aster_entrypoint(entrypoint: u32, boot_params_ptr: u32) -> ! unreachable!(); } + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + println!("panic: {:?}", info); + loop {} +} diff --git a/framework/libs/boot-trojan/trojan/src/arch/setup.S b/framework/libs/boot-trojan/trojan/src/arch/i386/setup.S similarity index 100% rename from framework/libs/boot-trojan/trojan/src/arch/setup.S rename to framework/libs/boot-trojan/trojan/src/arch/i386/setup.S diff --git a/framework/libs/boot-trojan/trojan/src/arch/setup64.S b/framework/libs/boot-trojan/trojan/src/arch/setup64.S deleted file mode 100644 index a4a450f20..000000000 --- a/framework/libs/boot-trojan/trojan/src/arch/setup64.S +++ /dev/null @@ -1,16 +0,0 @@ -.section ".setup", "ax" -.code64 -.org 0x200 -// start_of_setup64 should be at start_of_setup32 + 0x200 -.global start_of_setup64 -start_of_setup64: - -.extern _trojan_entry_64 - lea rax, [rip + _trojan_entry_64] - push rsi - call rax - - // Unreachable here. -halt: - hlt - jmp halt diff --git a/framework/libs/boot-trojan/trojan/src/arch/x86_64/efi.rs b/framework/libs/boot-trojan/trojan/src/arch/x86_64/efi.rs new file mode 100644 index 000000000..a54f6a5f2 --- /dev/null +++ b/framework/libs/boot-trojan/trojan/src/arch/x86_64/efi.rs @@ -0,0 +1,66 @@ +use core::fmt::Write; +use uefi::{ + data_types::Handle, + proto::loaded_image::LoadedImage, + table::{Boot, SystemTable}, +}; + +use linux_boot_params::BootParams; + +#[no_mangle] +extern "sysv64" fn efi_stub_entry(handle: Handle, mut system_table: SystemTable) -> ! { + unsafe { + system_table.boot_services().set_image_handle(handle); + } + uefi_services::init(&mut system_table).unwrap(); + + // Suppress TODO warning. + #[allow(unreachable_code)] + efi_entry( + handle, + system_table, + todo!("Use EFI services to fill boot params"), + ); +} + +#[no_mangle] +extern "sysv64" fn efi_handover_entry( + handle: Handle, + mut system_table: SystemTable, + boot_params: *mut BootParams, +) -> ! { + unsafe { + system_table.boot_services().set_image_handle(handle); + } + uefi_services::init(&mut system_table).unwrap(); + + efi_entry(handle, system_table, boot_params) +} + +fn efi_entry( + handle: Handle, + mut system_table: SystemTable, + boot_params: *mut BootParams, +) -> ! { + system_table + .stdout() + .write_str("[EFI stub] Exiting EFI boot services.\n") + .unwrap(); + let memory_type = { + let boot_services = system_table.boot_services(); + let Ok(loaded_image) = boot_services.open_protocol_exclusive::(handle) else { + panic!("Failed to open LoadedImage protocol"); + }; + loaded_image.data_type().clone() + }; + let _ = system_table.exit_boot_services(memory_type); + + let loaded_base = { + extern "C" { + fn start_of_setup32(); + } + start_of_setup32 as usize + }; + + crate::trojan_entry(loaded_base, boot_params as usize); +} diff --git a/framework/libs/boot-trojan/trojan/src/arch/x86_64/header.S b/framework/libs/boot-trojan/trojan/src/arch/x86_64/header.S new file mode 100644 index 000000000..b458b5055 --- /dev/null +++ b/framework/libs/boot-trojan/trojan/src/arch/x86_64/header.S @@ -0,0 +1,59 @@ +// The compatibility file for the Linux x86 Boot Protocol. +// See https://www.kernel.org/doc/html/v5.6/x86/boot.html for +// more information on the Linux x86 Boot Protocol. + +// Some of the fields filled with a 0xab* values should be filled +// by the torjan builder. +// 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 +SETUP_SECTS_SIZE = 0x200 * (SETUP_SECTS + 1) +.code16 +.org 0x01f1 +hdr_start: +setup_sects: .byte SETUP_SECTS +root_flags: .word 1 +syssize: .long 0 +ram_size: .word 0 +vid_mode: .word 0xfffd +root_dev: .word 0 +boot_flag: .word 0xAA55 +jump: .byte 0xeb +jump_addr: .byte hdr_end-jump_addr +magic: .ascii "HdrS" + .word 0x020f +realmode_swtch: .word 0, 0 +start_sys_seg: .word 0 + .word 0 +type_of_loader: .byte 0 +loadflags: .byte (1 << 0) +setup_move_size: .word 0 +code32_start: .long CODE32_START +ramdisk_image: .long 0 +ramdisk_size: .long 0 +bootsect_kludge: .long 0 +heap_end_ptr: .word 65535 +ext_loader_ver: .byte 0 +ext_loader_type: .byte 0 +cmd_line_ptr: .long 0 +initrd_addr_max: .long 0x7fffffff +kernel_alignment: .long 0x1000000 +relocatable_kernel: .byte 0 +min_alignment: .byte 0x10 +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 builder +payload_length: .long 0xabababab # at 0x24c/4, to be filled by the builder +setup_data: .quad 0 +pref_address: .quad CODE32_START - SETUP_SECTS_SIZE +init_size: .long 0xabababab # at 0x260/4, to be filled by the builder +# The handover_offset should be efi_handover_setup_entry - CODE32_START - 0x200 +# But we use ABI workaround to avoid the relocation of efi_handover_setup_entry +handover_offset: .long 0x10 +kernel_info_offset: .long 0 +hdr_end: diff --git a/framework/libs/boot-trojan/trojan/src/arch/x86_64.linker.ld b/framework/libs/boot-trojan/trojan/src/arch/x86_64/linker.ld similarity index 74% rename from framework/libs/boot-trojan/trojan/src/arch/x86_64.linker.ld rename to framework/libs/boot-trojan/trojan/src/arch/x86_64/linker.ld index 5b9eebe23..b4a688cbd 100644 --- a/framework/libs/boot-trojan/trojan/src/arch/x86_64.linker.ld +++ b/framework/libs/boot-trojan/trojan/src/arch/x86_64/linker.ld @@ -1,4 +1,4 @@ -ENTRY(start_of_setup64) +ENTRY(efi_handover_setup_entry) OUTPUT_ARCH(i386:x86-64) OUTPUT_FORMAT(elf64-x86-64) @@ -17,7 +17,6 @@ SECTIONS .setup : { KEEP(*(.setup)) } .text : { *(.text .text.*) } - .rodata : { *(.rodata .rodata.*) } .data : { *(.data .data.*) } .bss : { @@ -26,6 +25,15 @@ SECTIONS PROVIDE(__bss_end = .); } + .got.plt : { + *(.got.plt .got.plt.*) + } + .dynamic : { + *(.dynamic .dynamic.*) + } + + .rodata : { *(.rodata .rodata.*) } + .eh_frame : { *(.eh_frame .eh_frame.*) } @@ -36,4 +44,10 @@ SECTIONS .rela.dyn : { *(.rela.dyn .rela.dyn.*) } + + .rela.plt : { + *(.rela.plt .rela.plt.*) + } + + .comment : { *(.comment) } } diff --git a/framework/libs/boot-trojan/trojan/src/arch/x86_64.rs b/framework/libs/boot-trojan/trojan/src/arch/x86_64/mod.rs similarity index 65% rename from framework/libs/boot-trojan/trojan/src/arch/x86_64.rs rename to framework/libs/boot-trojan/trojan/src/arch/x86_64/mod.rs index 06c82accd..48590d0e5 100644 --- a/framework/libs/boot-trojan/trojan/src/arch/x86_64.rs +++ b/framework/libs/boot-trojan/trojan/src/arch/x86_64/mod.rs @@ -1,13 +1,12 @@ +mod efi; + use core::arch::{asm, global_asm}; global_asm!(include_str!("header.S")); -global_asm!(include_str!("setup64.S")); +global_asm!(include_str!("setup.S")); -#[no_mangle] -extern "cdecl" fn _trojan_entry_64(boot_params_ptr: u64) -> ! { - crate::trojan_entry(boot_params_ptr as u32); -} +pub const ASTER_ENTRY_POINT: u32 = 0x8001200; pub unsafe fn call_aster_entrypoint(entrypoint: u64, boot_params_ptr: u64) -> ! { asm!("mov rsi, {}", in(reg) boot_params_ptr as u64); diff --git a/framework/libs/boot-trojan/trojan/src/arch/x86_64/setup.S b/framework/libs/boot-trojan/trojan/src/arch/x86_64/setup.S new file mode 100644 index 000000000..7a0f18fd5 --- /dev/null +++ b/framework/libs/boot-trojan/trojan/src/arch/x86_64/setup.S @@ -0,0 +1,41 @@ +.section ".setup", "ax" +.code64 +// start_of_setup32 should be loaded at CODE32_START, which is our base. +.global start_of_setup32 +start_of_setup32: + +// `efi_handover_setup_entry64` should be at efi_handover_setup_entry32 + 0x200, but +// we could provide the 32 bit dummy entry point as the 64 bit entry point - 0x200 +// since we do not provide 32-bit entry point in the x86_64 specific implementation. +.org 0x210 +.global efi_handover_setup_entry +efi_handover_setup_entry: + // The 3 parameters of is stored in rdi, rsi and rdx (sysv64). + // Do not use them. + + // Setup the stack. + lea rsp, [rip + setup_stack_top] + lea rax, [rip + halt] + push rax # the return address + mov rbp, rsp + add rbp, -4 + push rbp + mov rbp, rsp + +.extern efi_handover_entry + lea rax, [rip + efi_handover_entry] + call rax + + // Unreachable here. +halt: + hlt + jmp halt + +// A small stack for the setup code. +.section .data +.align 0x1000 / 8 +.global setup_stack +setup_stack: + .skip 0x1000 +.global setup_stack_top +setup_stack_top: \ No newline at end of file diff --git a/framework/libs/boot-trojan/trojan/src/boot_params.rs b/framework/libs/boot-trojan/trojan/src/boot_params.rs deleted file mode 100644 index a250c53c3..000000000 --- a/framework/libs/boot-trojan/trojan/src/boot_params.rs +++ /dev/null @@ -1,12 +0,0 @@ -const FIELD_PAYLOAD_OFFSET: u32 = 0x248; -const FIELD_PAYLOAD_LENGTH: u32 = 0x24c; - -/// Safty: user must ensure that the boot_params_ptr is valid -pub unsafe fn get_payload_offset(boot_params_ptr: u32) -> u32 { - *((boot_params_ptr + FIELD_PAYLOAD_OFFSET) as *const u32) -} - -/// Safty: user must ensure that the boot_params_ptr is valid -pub unsafe fn get_payload_length(boot_params_ptr: u32) -> u32 { - *((boot_params_ptr + FIELD_PAYLOAD_LENGTH) as *const u32) -} diff --git a/framework/libs/boot-trojan/trojan/src/console.rs b/framework/libs/boot-trojan/trojan/src/console.rs index 60dc7043e..d71cf6b85 100644 --- a/framework/libs/boot-trojan/trojan/src/console.rs +++ b/framework/libs/boot-trojan/trojan/src/console.rs @@ -1,4 +1,4 @@ -use core::fmt::{self, Write}; +use core::fmt::Write; use uart_16550::SerialPort; @@ -17,37 +17,45 @@ pub unsafe fn init() { impl Stdout { /// safety: this function must only be called once - pub unsafe fn init() -> Self { + unsafe fn init() -> Self { let mut serial_port = unsafe { SerialPort::new(0x3F8) }; serial_port.init(); Self { serial_port } } } -impl Write for Stdout { - fn write_str(&mut self, s: &str) -> fmt::Result { +impl Stdout { + fn write_str(&mut self, s: &str) { self.serial_port.write_str(s).unwrap(); - Ok(()) + } + + fn write_char(&mut self, c: char) { + self.serial_port.send(c as u8); } } -pub fn print(args: fmt::Arguments) { - // safety: init() must be called before print() and there is no race condition - unsafe { - STDOUT.write_fmt(args).unwrap(); +/// Safety: init() must be called before print() and there should be no race condition +pub unsafe fn print(s: &str) { + STDOUT.write_str(s); +} + +/// Safety: init() must be called before print_char() and there should be no race condition +pub unsafe fn print_char(c: char) { + STDOUT.write_char(c); +} + +// Safety: init() must be called before print_hex() and there should be no race condition +pub unsafe fn print_hex(n: usize) { + print("0x"); + let mut n = n; + for _ in 0..16 { + let digit = (n & 0xf) as u8; + n >>= 4; + let c = if digit < 10 { + (b'0' + digit) as char + } else { + (b'a' + digit - 10) as char + }; + print_char(c); } } - -#[macro_export] -macro_rules! print { - ($fmt: literal $(, $($arg: tt)+)?) => { - $crate::console::print(format_args!($fmt $(, $($arg)+)?)) - } -} - -#[macro_export] -macro_rules! println { - ($fmt: literal $(, $($arg: tt)+)?) => { - $crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?)) - } -} diff --git a/framework/libs/boot-trojan/trojan/src/loader.rs b/framework/libs/boot-trojan/trojan/src/loader.rs index 2f515c202..0972595fc 100644 --- a/framework/libs/boot-trojan/trojan/src/loader.rs +++ b/framework/libs/boot-trojan/trojan/src/loader.rs @@ -24,6 +24,6 @@ pub fn load_elf(file: &[u8]) -> u32 { } } - // Return the Linux 32-bit Boot Protocol entry point defined by Asterinas. - 0x8001000 + // Return the Linux Boot Protocol entry point defined by Asterinas. + crate::arch::ASTER_ENTRY_POINT } diff --git a/framework/libs/boot-trojan/trojan/src/main.rs b/framework/libs/boot-trojan/trojan/src/main.rs index 931ecd0d8..d8c6b3ce8 100644 --- a/framework/libs/boot-trojan/trojan/src/main.rs +++ b/framework/libs/boot-trojan/trojan/src/main.rs @@ -1,32 +1,48 @@ #![no_std] #![no_main] +use linux_boot_params::BootParams; + mod arch; -mod boot_params; mod console; mod loader; -fn trojan_entry(boot_params_ptr: u32) -> ! { +use console::{print, print_hex}; + +/// The entrypoint of the trojan. The architecture-specific entrypoint will call this function. +/// +/// The loaded address of the CODE32_START should be passed in as `loaded_base`, since the trojan +/// may be loaded at any address, and offsets in the header are not position-independent. +fn trojan_entry(loaded_base: usize, boot_params_ptr: usize) -> ! { // Safety: this init function is only called once. unsafe { console::init() }; - println!("[setup] boot_params_ptr: {:#x}", boot_params_ptr); + unsafe { + print("[setup] bzImage loaded at "); + print_hex(loaded_base); + print("\n"); + } - let payload_offset = unsafe { boot_params::get_payload_offset(boot_params_ptr) }; - let payload_length = unsafe { boot_params::get_payload_length(boot_params_ptr) }; + // Safety: the boot_params_ptr is a valid pointer to be borrowed. + let boot_params = unsafe { &*(boot_params_ptr as *const BootParams) }; + let hdr = &boot_params.hdr; + let payload_offset = loaded_base + hdr.payload_offset as usize; + let payload_length = hdr.payload_length as usize; let payload = unsafe { core::slice::from_raw_parts_mut(payload_offset as *mut u8, payload_length as usize) }; - println!("[setup] loading ELF payload..."); + unsafe { + print("[setup] loading ELF payload at "); + print_hex(payload_offset); + print("...\n"); + } let entrypoint = loader::load_elf(payload); - println!("[setup] entrypoint: {:#x}", entrypoint); + unsafe { + print("[setup] jumping to payload entrypoint at "); + print_hex(entrypoint as usize); + print("...\n"); + } // Safety: the entrypoint and the ptr is valid. - unsafe { arch::call_aster_entrypoint(entrypoint.into(), boot_params_ptr.into()) }; -} - -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { - println!("panic: {:?}", info); - loop {} + unsafe { arch::call_aster_entrypoint(entrypoint.into(), boot_params_ptr.try_into().unwrap()) }; } diff --git a/runner/grub/grub.cfg.template b/runner/grub/grub.cfg.template index c7db16aba..a17634136 100644 --- a/runner/grub/grub.cfg.template +++ b/runner/grub/grub.cfg.template @@ -2,7 +2,7 @@ # AUTOMATICALLY GENERATED FILE, DO NOT EDIT IF YOU KNOW WHAT YOU ARE DOING -# set debug=linux,efi +# set debug=linux,efi,linuxefi set timeout_style=#GRUB_TIMEOUT_STYLE# set timeout=#GRUB_TIMEOUT# diff --git a/runner/src/gdb.rs b/runner/src/gdb.rs index f5a13b69e..5b9e75edd 100644 --- a/runner/src/gdb.rs +++ b/runner/src/gdb.rs @@ -10,7 +10,7 @@ use std::{fs::OpenOptions, io::Write, path::PathBuf, process::Command}; /// 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 `0x0007E685000`). Then use the following GDB command to load symbols: +/// (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.