Skip to content

Move Note and GDT definitions into Rust #37

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main() {
println!("cargo:rerun-if-changed=target.json");
println!("cargo:rerun-if-changed=layout.ld");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

18 changes: 11 additions & 7 deletions layout.ld
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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/ : {
Expand All @@ -51,4 +50,9 @@ SECTIONS
*(.comment)
*(COMMON)
}
/* Strip symbols from the output binary (comment out to get symbols) */
/DISCARD/ : {
*(.symtab)
*(.strtab)
}
}
32 changes: 0 additions & 32 deletions src/asm/gdt64.s

This file was deleted.

2 changes: 0 additions & 2 deletions src/asm/mod.rs
Original file line number Diff line number Diff line change
@@ -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"));
20 changes: 0 additions & 20 deletions src/asm/note.s

This file was deleted.

4 changes: 2 additions & 2 deletions src/asm/ram32.s
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions src/asm/ram64.s
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
53 changes: 53 additions & 0 deletions src/gdt.rs
Original file line number Diff line number Diff line change
@@ -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::<Descriptor>();
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];
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ mod boot;
mod bzimage;
mod efi;
mod fat;
mod gdt;
mod loader;
mod mem;
mod paging;
Expand Down
36 changes: 36 additions & 0 deletions src/pvh.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use core::mem::size_of;

use crate::{
boot::{E820Entry, Info},
common,
Expand Down Expand Up @@ -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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly when it was 4 bytes that forced a change in the Cloud Hypervisor PVH code to support it. It's ambiguous to me what it should be. But if it works... :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The size of Desc seems to depend on how the code in question is compiled. Linux sets a field of type _ASM_PTR, which is 8 bytes (at least on my kernel). The PVH Specification seems to indicate the type should be .long aka 4 bytes. I think it's fine for hypervisors to just support any size <= 8 for this field. QEMU just does a raw read of a size_t without checking descsz.

There type of namesz, descsz, and kind seems to usually be u32 (even for 64-bit ELF). That's what Linux does, NetBSD does, and what Oracle says to do. However, the gABI says they should be u64s but that seems to get ignored. See this mailing list discussion for more info.

The final complexity is alignment of the structure (and hence the containing phdr's alignment). Some loaders expect this to always be 4, others allow for other alignments but compute the structure padding incorrectly:

Using an alignment of 4 prevents any issues (as there will just not be any padding). This is what Linux does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment about alignment to pvh.rs.

}

// 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::<Name>() as u32,
desc_size: size_of::<Desc>() as u32,
kind: XEN_ELFNOTE_PHYS32_ENTRY,
name: *b"Xen\0",
desc: ram32_start,
};
5 changes: 1 addition & 4 deletions target.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@
"code-model": "small",
"relocation-model": "static",
"pre-link-args": {
"ld.lld": [
"-s",
"--script=layout.ld"
]
"ld.lld": ["--script=layout.ld"]
}
}