From 1c472471254e4ce7d458e145f9345efb4f4abb8a Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Sun, 12 Apr 2020 14:09:14 -0700 Subject: [PATCH 1/5] layout: Improve development workflow Right now, if layout.ld or target.json is changed, the firmware will not automatically relink when running "cargo xbuild". This can make working on layout.ld quite tedious, as you have to manually change the Rust code to get the firmware to rebuild. We solve this by adding a tiny build script which simply rebuilds the program if either layout.ld or target.json changes. We also stop using "-s" (aka "--strip-all") in the target options. Instead, we add a second /DISCARD/ section in the linker script. This puts all of our logic about sections in a single place. It also makes it easier for a developer to get the symbols (if they want them). Signed-off-by: Joe Richey --- build.rs | 4 ++++ layout.ld | 5 +++++ target.json | 5 +---- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 build.rs 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..2652887c 100644 --- a/layout.ld +++ b/layout.ld @@ -51,4 +51,9 @@ SECTIONS *(.comment) *(COMMON) } + /* Strip symbols from the output binary (comment out to get symbols) */ + /DISCARD/ : { + *(.symtab) + *(.strtab) + } } 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"] } } From 0f813a5648e291dfbb98fbaae99d574c0d85620a Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 6 Apr 2020 03:04:14 -0700 Subject: [PATCH 2/5] layout: Add explict .stack section This ensures that the stack region is mapped by the ELF loader and doesn't conflict with any hypervisor memory regions. To do this without increasing binary size, we place the stack right after the .bss section. Note that we still need an assert to make sure that our minimal page table setup in ram32.s covers our program + stack. Also, being able to see the stack as a section makes debugging easier. Signed-off-by: Joe Richey --- layout.ld | 13 ++++++------- src/asm/ram64.s | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/layout.ld b/layout.ld index 2652887c..582826da 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-algined. 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/ : { 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. From 70ee5b7a67084845f2e4e4d35dbe215bd6a22a8d Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Thu, 9 Apr 2020 01:11:28 -0700 Subject: [PATCH 3/5] pvh: Move note definition/declaration to Rust The structure of the ELF note can be done with pure Rust code. We can definie the Name and Desc types and use a static struct to hold the note. Due to Rust's limitations on "pointer-to-integer cast", we have to have Desc have a function pointer type, which means that field is now 8 bytes long instead of 4. However, this doesn't seem to be an issue. The binary still works w/ PVH Boot on QEMU and CH. Signed-off-by: Joe Richey --- src/asm/mod.rs | 1 - src/asm/note.s | 20 -------------------- src/pvh.rs | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 21 deletions(-) delete mode 100644 src/asm/note.s diff --git a/src/asm/mod.rs b/src/asm/mod.rs index 95dd9360..8183e317 100644 --- a/src/asm/mod.rs +++ b/src/asm/mod.rs @@ -1,4 +1,3 @@ -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/pvh.rs b/src/pvh.rs index c5a49f4f..cad11ef6 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,35 @@ 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(); + +#[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, +}; From f7f5c6d9e77fd8a6b2f838d0e61fc2e2e979a0fb Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Thu, 9 Apr 2020 01:23:50 -0700 Subject: [PATCH 4/5] gdt: Move GDT and GDT Definitions to Rust The GDT is just some data in static memory, so there's not a good reason to have this code in assembly. Ideally, we would use types from the x86_64 crate for this. However, - We can't use x86_64::structures::gdt::DescriptorFlags for the contents of the descriptors, as that definition is missing flags. - We can't use x86_64::structures::DescriptorTablePointer for the GDT pointers because its base is a u64, and Rust doesn't allow "pointer-to-integer cast" in statics. So we have to roll our own. The definitions aren't that bad. The only downside is that we have to add the bitflags dependency, but it was already a dependancy of x86_64, so that's also not bad. Signed-off-by: Joe Richey --- Cargo.lock | 1 + Cargo.toml | 1 + src/asm/gdt64.s | 32 ----------------------------- src/asm/mod.rs | 1 - src/asm/ram32.s | 4 ++-- src/gdt.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + 7 files changed, 58 insertions(+), 35 deletions(-) delete mode 100644 src/asm/gdt64.s create mode 100644 src/gdt.rs 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/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 8183e317..ab08edf0 100644 --- a/src/asm/mod.rs +++ b/src/asm/mod.rs @@ -1,3 +1,2 @@ global_asm!(include_str!("ram32.s")); global_asm!(include_str!("ram64.s")); -global_asm!(include_str!("gdt64.s")); 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/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; From ae04b876d80645d44bd3903dcae2f1145a543e8f Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Wed, 15 Apr 2020 04:35:46 -0700 Subject: [PATCH 5/5] Fix typos and add comments Signed-off-by: Joe Richey --- layout.ld | 2 +- src/pvh.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/layout.ld b/layout.ld index 582826da..2cd98d62 100644 --- a/layout.ld +++ b/layout.ld @@ -32,7 +32,7 @@ SECTIONS bss_size = . - bss_start; } - /* Our stack grows down and is page-algined. TODO: Add stack guard pages. */ + /* 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. */ diff --git a/src/pvh.rs b/src/pvh.rs index cad11ef6..22a661be 100644 --- a/src/pvh.rs +++ b/src/pvh.rs @@ -67,6 +67,8 @@ 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,