diff --git a/Cargo.lock b/Cargo.lock index c02c0022..59533910 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,7 @@ name = "hypervisor-fw" version = "0.1.0" dependencies = [ "atomic_refcell 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "r-efi 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "x86_64 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index 8150a06c..c0b1ac4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ log-serial = [] log-panic = ["log-serial"] [dependencies] +bitflags = "1.2" x86_64 = "0.9" atomic_refcell = "0.1" r-efi = "2.1.0" diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..05103706 --- /dev/null +++ b/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rerun-if-changed=target.json"); + println!("cargo:rerun-if-changed=layout.ld"); +} diff --git a/layout.ld b/layout.ld index 5aff83e1..2cd98d62 100644 --- a/layout.ld +++ b/layout.ld @@ -8,9 +8,6 @@ PHDRS /* Loaders like to put stuff in low memory (< 1M), so we don't use it. */ ram_min = 1M; -ram_max = 2M; -/* Our stack grows down from ram_max. TODO: Add a guard for stack overflows. */ -stack_size = 64K; SECTIONS { @@ -28,16 +25,18 @@ SECTIONS .data : { *(.data .data.*) } data_size = . - data_start; - /* The BSS section isn't mapped from any file data. It is simply zeroed - in RAM. So our file size should be computed from here. */ - file_size = . - ram_min; + /* The BSS section isn't mapped from file data. It is just zeroed in RAM. */ .bss : { bss_start = .; *(.bss .bss.*) bss_size = . - bss_start; } - ASSERT((. <= ram_max - stack_size), "firmware size too big for RAM region") + /* Our stack grows down and is page-aligned. TODO: Add stack guard pages. */ + .stack (NOLOAD) : ALIGN(4K) { . += 64K; } + stack_start = .; + /* ram32.s only maps the first 2 MiB, and that must include the stack. */ + ASSERT((. <= 2M), "Stack overflows initial identity-mapped memory region") /* Match edk2's GccBase.lds DISCARD section */ /DISCARD/ : { @@ -51,4 +50,9 @@ SECTIONS *(.comment) *(COMMON) } + /* Strip symbols from the output binary (comment out to get symbols) */ + /DISCARD/ : { + *(.symtab) + *(.strtab) + } } diff --git a/src/asm/gdt64.s b/src/asm/gdt64.s deleted file mode 100644 index 9737c43f..00000000 --- a/src/asm/gdt64.s +++ /dev/null @@ -1,32 +0,0 @@ -.section .rodata, "a" - -gdt64_ptr: - .short gdt64_end - gdt64_start - 1 # GDT length is actually (length - 1) - .quad gdt64_start - -gdt64_start: # First descriptor is always null - .quad 0 -code64_desc: # 64-bit Code-Segments always have: Base = 0, Limit = 4G - # CS.Limit[15:00] = 0 - Ignored - .short 0x0000 - # CS.Base[15:00] = 0 - Ignored - .short 0x0000 - # CS.Base[23:16] = 0 (bits 0-7) - Ignored - .byte 0x00 - # CS.Accessed = 1 (bit 8) - Don't write to segment on first use - # CS.ReadEnable = 1 (bit 9) - Read/Execute Code-Segment - # CS.Conforming = 0 (bit 10) - Nonconforming, no lower-priv access - # CS.Executable = 1 (bit 11) - Code-Segement - # CS.S = 1 (bit 12) - Not a System-Segement - # CS.DPL = 0 (bits 13-14) - We only use this segment in Ring 0 - # CS.P = 1 (bit 15) - Segment is present - .byte 0b10011011 - # CS.Limit[19:16] = 0 (bits 16-19) - Ignored - # CS.AVL = 0 (bit 20) - Our software doesn't use this bit - # CS.L = 1 (bit 21) - This isn't a 64-bit segment - # CS.D = 0 (bit 22) - This is a 32-bit segment - # CS.G = 0 (bit 23) - Ignored - .byte 0b00100000 - # CS.Base[31:24] = 0 (bits 24-31) - Ignored - .byte 0x00 -gdt64_end: diff --git a/src/asm/mod.rs b/src/asm/mod.rs index 95dd9360..ab08edf0 100644 --- a/src/asm/mod.rs +++ b/src/asm/mod.rs @@ -1,4 +1,2 @@ -global_asm!(include_str!("note.s")); global_asm!(include_str!("ram32.s")); global_asm!(include_str!("ram64.s")); -global_asm!(include_str!("gdt64.s")); diff --git a/src/asm/note.s b/src/asm/note.s deleted file mode 100644 index 674cf70e..00000000 --- a/src/asm/note.s +++ /dev/null @@ -1,20 +0,0 @@ -.section .note, "a" - -# From xen/include/public/elfnote.h, "Physical entry point into the kernel." -XEN_ELFNOTE_PHYS32_ENTRY = 18 - -# We don't bother defining an ELFNOTE macro, as we only have one note. -# This is equialent to the kernel's: -# ELFNOTE(Xen, XEN_ELFNOTE_PHYS32_ENTRY, .long pvh_start) -.align 4 - .long name_end - name_start # namesz - .long desc_end - desc_start # descsz - .long XEN_ELFNOTE_PHYS32_ENTRY # type -name_start: - .asciz "Xen" -name_end: -.align 4 -desc_start: - .long ram32_start -desc_end: -.align 4 diff --git a/src/asm/ram32.s b/src/asm/ram32.s index e989fd7e..3e165a53 100644 --- a/src/asm/ram32.s +++ b/src/asm/ram32.s @@ -41,6 +41,6 @@ enable_paging: jump_to_64bit: # We are now in 32-bit compatibility mode. To enter 64-bit mode, we need to # load a 64-bit code segment into our GDT. - lgdtl gdt64_ptr + lgdtl GDT64_PTR # Set CS to a 64-bit segment and jump to 64-bit code. - ljmpl $(code64_desc - gdt64_start), $ram64_start + ljmpl $0x08, $ram64_start diff --git a/src/asm/ram64.s b/src/asm/ram64.s index 6999c048..7f6ae7a9 100644 --- a/src/asm/ram64.s +++ b/src/asm/ram64.s @@ -7,8 +7,8 @@ linux64_start: xorq %rdi, %rdi ram64_start: - # Setup the stack (at the end of our RAM region) - movq $ram_max, %rsp + # Initialize the stack pointer (Rust code always uses the stack) + movq $stack_start, %rsp # PVH start_info is in %rdi, the first paramter of the System V ABI. # BootParams are in %rsi, the second paramter of the System V ABI. diff --git a/src/gdt.rs b/src/gdt.rs new file mode 100644 index 00000000..5b64713d --- /dev/null +++ b/src/gdt.rs @@ -0,0 +1,53 @@ +use core::mem::size_of; + +bitflags::bitflags! { + // An extension of x86_64::structures::gdt::DescriptorFlags + struct Descriptor: u64 { + const LIMIT_0_15 = 0xFFFF; + const BASE_0_23 = 0xFF_FFFF << 16; + const ACCESSED = 1 << 40; + const WRITABLE = 1 << 41; // Only for Data-Segments + const READABLE = 1 << 41; // Only for Code-Segments + const EXPANSION = 1 << 42; // Only for Data-Segments + const CONFORMING = 1 << 42; // Only for Code-Segments + const EXECUTABLE = 1 << 43; + const USER_SEGMENT = 1 << 44; + const DPL_RING_3 = 3 << 45; + const PRESENT = 1 << 47; + const LIMIT_16_19 = 0xF << 48; + const SOFTWARE = 1 << 52; + const BIT64 = 1 << 53; + const BIT32 = 1 << 54; + const GRANULARITY = 1 << 55; + const BASE_24_31 = 0xFF << 56; + + // All segments are nonconforming, non-system, ring-0 only, and present. + // We set ACCESSED in advance to avoid writing to the descriptor. + const COMMON = Self::ACCESSED.bits | Self::USER_SEGMENT.bits | Self::PRESENT.bits; + // BIT32 must be 0, all other bits (not yet mentioned) are ignored. + const CODE64 = Self::COMMON.bits | Self::EXECUTABLE.bits | Self::BIT64.bits; + } +} + +// An alternative to x86_64::structures::DescriptorTablePointer that avoids +// "pointer-to-integer cast" (which rust does not support in statics). +#[repr(C, packed)] +struct Pointer { + limit: u16, + base: &'static Descriptor, +} + +impl Pointer { + const fn new(gdt: &'static [Descriptor]) -> Self { + let size = gdt.len() * size_of::(); + Self { + limit: size as u16 - 1, + base: &gdt[0], + } + } +} + +// Our 64-bit GDT lives in RAM, so it can be accessed like any other global. +#[no_mangle] +static GDT64_PTR: Pointer = Pointer::new(&GDT64); +static GDT64: [Descriptor; 2] = [Descriptor::empty(), Descriptor::CODE64]; diff --git a/src/main.rs b/src/main.rs index 5b36e0a1..d3af9a47 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,7 @@ mod boot; mod bzimage; mod efi; mod fat; +mod gdt; mod loader; mod mem; mod paging; diff --git a/src/pvh.rs b/src/pvh.rs index c5a49f4f..22a661be 100644 --- a/src/pvh.rs +++ b/src/pvh.rs @@ -1,3 +1,5 @@ +use core::mem::size_of; + use crate::{ boot::{E820Entry, Info}, common, @@ -53,3 +55,37 @@ impl Info for StartInfo { } } } + +// The PVH Boot Protocol starts at the 32-bit entrypoint to our firmware. +extern "C" { + fn ram32_start(); +} + +// The kind/name/desc of the PHV ELF Note are from xen/include/public/elfnote.h. +// This is the "Physical entry point into the kernel". +const XEN_ELFNOTE_PHYS32_ENTRY: u32 = 18; +type Name = [u8; 4]; +type Desc = unsafe extern "C" fn(); + +// We make sure our ELF Note has an alignment of 4 for maximum compatibility. +// Some software (QEMU) calculates padding incorectly if alignment != 4. +#[repr(C, packed(4))] +struct Note { + name_size: u32, + desc_size: u32, + kind: u32, + name: Name, + desc: Desc, +} + +// This is: ELFNOTE(Xen, XEN_ELFNOTE_PHYS32_ENTRY, .quad ram32_start) +#[cfg(not(test))] +#[link_section = ".note"] +#[used] +static PVH_NOTE: Note = Note { + name_size: size_of::() as u32, + desc_size: size_of::() as u32, + kind: XEN_ELFNOTE_PHYS32_ENTRY, + name: *b"Xen\0", + desc: ram32_start, +}; diff --git a/target.json b/target.json index 4814517d..6c208b55 100644 --- a/target.json +++ b/target.json @@ -16,9 +16,6 @@ "code-model": "small", "relocation-model": "static", "pre-link-args": { - "ld.lld": [ - "-s", - "--script=layout.ld" - ] + "ld.lld": ["--script=layout.ld"] } }