Skip to content

Change base address from 0x200_000 to 0x0 #450

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 3 commits into from
May 1, 2025
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -468,3 +468,6 @@ hyperlight_guest.h
.mono

!.gitkeep

# gdb
.gdbinit
16 changes: 16 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ clean-rust:

# Note: most testing recipes take an optional "features" comma separated list argument. If provided, these will be passed to cargo as **THE ONLY FEATURES**, i.e. default features will be disabled.

# convenience recipe to run all tests with the given target and features (similar to CI)
test-like-ci config=default-target hypervisor="kvm":
@# with default features
just test {{config}} {{ if hypervisor == "mshv3" {"mshv3"} else {""} }}

@# with only one driver enabled + seccomp + inprocess
just test {{config}} inprocess,seccomp,{{ if hypervisor == "mshv" {"mshv2"} else if hypervisor == "mshv3" {"mshv3"} else {"kvm"} }}

@# make sure certain cargo features compile
cargo check -p hyperlight-host --features crashdump
cargo check -p hyperlight-host --features print_debug
cargo check -p hyperlight-host --features gdb

@# without any driver (should fail to compile)
just test-compilation-fail {{config}}

# runs all tests
test target=default-target features="": (test-unit target features) (test-isolated target features) (test-integration "rust" target features) (test-integration "c" target features) (test-seccomp target features)

Expand Down
16 changes: 8 additions & 8 deletions docs/paging-development-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,35 @@ Hyperlight uses paging, which means the all addresses inside a Hyperlight VM are

## Host-to-Guest memory mapping

Into each Hyperlight VM, memory from the host is mapped into the VM as physical memory. The physical memory inside the VM starts at address `0x200_000` and extends linearly to however much memory was mapped into the VM (depends on various parameters).
Into each Hyperlight VM, memory from the host is mapped into the VM as physical memory. The physical memory inside the VM starts at address `0x0` and extends linearly to however much memory was mapped into the VM (depends on various parameters).

## Page table setup

The following page table structs are set up in memory before running a Hyperlight VM (See [Access Flags](#access-flags) for details on access flags that are also set on each entry)

### PML4 (Page Map Level 4) Table

The PML4 table is located at physical address specified in CR3. In Hyperlight we set `CR3=0x200_000`, which means the PML4 table is located at physical address `0x200_000`. The PML4 table comprises 512 64-bit entries.
The PML4 table is located at physical address specified in CR3. In Hyperlight we set `CR3=0x0`, which means the PML4 table is located at physical address `0x0`. The PML4 table comprises 512 64-bit entries.

In Hyperlight, we only initialize the first entry (at address `0x200_000`), with value `0x201_000`, implying that we only have a single PDPT.
In Hyperlight, we only initialize the first entry (at address `0x0`), with value `0x1_000`, implying that we only have a single PDPT.

### PDPT (Page-directory-pointer Table)

The first and only PDPT is located at physical address `0x201_000`. The PDPT comprises 512 64-bit entries. In Hyperlight, we only initialize the first entry of the PDPT (at address `0x201_000`), with the value `0x202_000`, implying that we only have a single PD.
The first and only PDPT is located at physical address `0x1_000`. The PDPT comprises 512 64-bit entries. In Hyperlight, we only initialize the first entry of the PDPT (at address `0x1_000`), with the value `0x2_000`, implying that we only have a single PD.

### PD (Page Directory)

The first and only PD is located at physical address `0x202_000`. The PD comprises 512 64-bit entries, each entry `i` is set to the value `(i * 0x1000) + 0x203_000`. Thus, the first entry is `0x203_000`, the second entry is `0x204_000` and so on.
The first and only PD is located at physical address `0x2_000`. The PD comprises 512 64-bit entries, each entry `i` is set to the value `(i * 0x1000) + 0x3_000`. Thus, the first entry is `0x3_000`, the second entry is `0x4_000` and so on.

### PT (Page Table)

The page tables start at physical address `0x203_000`. Each page table has 512 64-bit entries. Each entry is set to the value `p << 21|i << 12` where `p` is the page table number and `i` is the index of the entry in the page table. Thus, the first entry of the first page table is `0x000_000`, the second entry is `0x000_000 + 0x1000`, and so on. The first entry of the second page table is `0x200_000 + 0x1000`, the second entry is `0x200_000 + 0x2000`, and so on. Enough page tables are created to cover the size of memory mapped into the VM.
The page tables start at physical address `0x3_000`. Each page table has 512 64-bit entries. Each entry is set to the value `p << 21|i << 12` where `p` is the page table number and `i` is the index of the entry in the page table. Thus, the first entry of the first page table is `0x000_000`, the second entry is `0x000_000 + 0x1000`, and so on. The first entry of the second page table is `0x200_000 + 0x1000`, the second entry is `0x200_000 + 0x2000`, and so on. Enough page tables are created to cover the size of memory mapped into the VM.

## Address Translation

Given a 64-bit virtual address X, the corresponding physical address is obtained as follows:

1. PML4 table's physical address is located using CR3 (CR3 is `0x200_000`).
1. PML4 table's physical address is located using CR3 (CR3 is `0x0`).
2. Bits 47:39 of X are used to index into PML4, giving us the address of the PDPT.
3. Bits 38:30 of X are used to index into PDPT, giving us the address of the PD.
4. Bits 29:21 of X are used to index into PD, giving us the address of the PT.
Expand All @@ -63,7 +63,7 @@ In addition to providing addresses, page table entries also contain access flags

PML4E, PDPTE, and PD Entries have the present flag set to 1, and the rest of the flags are not set.

PTE Entries all have the present flag set to 1, apart from those for the address range `0x000_000` to `0x1FF_000` which have the present flag set to 0 as we do not map memory below physical address `0x200_000`.
PTE Entries all have the present flag set to 1.

In addition, the following flags are set according to the type of memory being mapped:

Expand Down
10 changes: 5 additions & 5 deletions src/hyperlight_host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ pub mod hypervisor;
///
/// Virtual Address
///
/// 0x200000 PML4
/// 0x201000 PDPT
/// 0x202000 PD
/// 0x203000 The guest PE code (When the code has been loaded using LoadLibrary to debug the guest this will not be
/// 0x0000 PML4
/// 0x1000 PDPT
/// 0x2000 PD
/// 0x3000 The guest PE code (When the code has been loaded using LoadLibrary to debug the guest this will not be
/// present and code length will be zero;
///
/// The pointer passed to the Entrypoint in the Guest application is the 0x200000 + size of page table + size of code,
/// The pointer passed to the Entrypoint in the Guest application is the ize of page table + size of code,
/// at this address structs below are laid out in this order
pub mod mem;
/// Metric definitions and helpers
Expand Down
19 changes: 8 additions & 11 deletions src/hyperlight_host/src/mem/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ use crate::{log_then_return, new_error, Result};
// | Guest Code |
// +-------------------------------------------+
// | PT |
// +-------------------------------------------+ 0x203_000
// +-------------------------------------------+ 0x3_000
// | PD |
// +-------------------------------------------+ 0x202_000
// +-------------------------------------------+ 0x2_000
// | PDPT |
// +-------------------------------------------+ 0x201_000
// +-------------------------------------------+ 0x1_000
// | PML4 |
// +-------------------------------------------+ 0x200_000
// +-------------------------------------------+ 0x0_000
// | ⋮ |
// | Unmapped |
// | ⋮ |
Expand Down Expand Up @@ -308,7 +308,7 @@ impl SandboxMemoryLayout {
const MAX_MEMORY_SIZE: usize = 0x40000000 - Self::BASE_ADDRESS;

/// The base address of the sandbox's memory.
pub(crate) const BASE_ADDRESS: usize = 0x0200000;
pub(crate) const BASE_ADDRESS: usize = 0x0;

// the offset into a sandbox's input/output buffer where the stack starts
const STACK_POINTER_SIZE_BYTES: u64 = 8;
Expand Down Expand Up @@ -699,12 +699,10 @@ impl SandboxMemoryLayout {
// The size of a single table is 4K, we can map up to 1GB total memory which requires 1 PML4, 1 PDPT, 1 PD and 512 PTs
// but we only need enough PTs to map the memory we are using. (In other words we only need 512 PTs to map the memory if the memory size is 1GB)
//
// Because we always start the physical address space at 0x200_000
// we can calculate the amount of memory needed for the PTs by calculating how much memory is needed for the sandbox configuration in total,
// then add 0x200_000 to that (as we start at 0x200_000),
// We can calculate the amount of memory needed for the PTs by calculating how much memory is needed for the sandbox configuration in total,
// and then add 3 * 4K (for the PML4, PDPT and PD) to that,
// then add 2MB to that (the maximum size of memory required for the PTs themselves is 2MB when we map 1GB of memory in 4K pages),
// then divide that by 0x200_000 (as we can map 2MB in each PT) and then round the result up by 1 .
// then divide that by 0x200_000 (as we can map 2MB in each PT).
// This will give us the total size of the PTs required for the sandbox to which we can add the size of the PML4, PDPT and PD.
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
fn get_total_page_table_size(
Expand Down Expand Up @@ -741,7 +739,6 @@ impl SandboxMemoryLayout {

let num_pages: usize = ((total_mapped_memory_size + AMOUNT_OF_MEMORY_PER_PT - 1)
/ AMOUNT_OF_MEMORY_PER_PT)
+ 1 // Round up
+ 3; // PML4, PDPT, PD

num_pages * PAGE_SIZE_USIZE
Expand Down Expand Up @@ -1077,7 +1074,7 @@ impl SandboxMemoryLayout {
macro_rules! get_address {
($something:ident) => {
paste! {
if guest_offset == 0 {
if run_inprocess {
let offset = self.[<$something _offset>];
let calculated_addr = shared_mem.calculate_address(offset)?;
u64::try_from(calculated_addr)?
Expand Down
77 changes: 33 additions & 44 deletions src/hyperlight_host/src/mem/mgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ where
mem_size: u64,
regions: &mut [MemoryRegion],
) -> Result<u64> {
// Add 0x200000 because that's the start of mapped memory
// For MSVC, move rsp down by 0x28. This gives the called 'main'
// function the appearance that rsp was 16 byte aligned before
// the 'call' that calls main (note we don't really have a return value
Expand Down Expand Up @@ -177,60 +176,50 @@ where
// We only need to create enough PTEs to map the amount of memory we have
// We need one PT for every 2MB of memory that is mapped
// We can use the memory size to calculate the number of PTs we need
// We round up mem_size/2MB and then we need to add 1 as we start our memory mapping at 0x200000
// We round up mem_size/2MB

let mem_size = usize::try_from(mem_size)?;

let num_pages: usize =
((mem_size + AMOUNT_OF_MEMORY_PER_PT - 1) / AMOUNT_OF_MEMORY_PER_PT) + 1;
(mem_size + AMOUNT_OF_MEMORY_PER_PT - 1) / AMOUNT_OF_MEMORY_PER_PT;

// Create num_pages PT with 512 PTEs
for p in 0..num_pages {
for i in 0..512 {
let offset = SandboxMemoryLayout::PT_OFFSET + (p * 4096) + (i * 8);
// Each PTE maps a 4KB page
let val_to_write = if p == 0 {
(p << 21) as u64 | (i << 12) as u64
} else {
let flags = match Self::get_page_flags(p, i, regions) {
Ok(region_type) => match region_type {
// TODO: We parse and load the exe according to its sections and then
// have the correct flags set rather than just marking the entire binary as executable
MemoryRegionType::Code => PAGE_PRESENT | PAGE_RW | PAGE_USER,
MemoryRegionType::Stack => {
PAGE_PRESENT | PAGE_RW | PAGE_USER | PAGE_NX
}
#[cfg(feature = "executable_heap")]
MemoryRegionType::Heap => PAGE_PRESENT | PAGE_RW | PAGE_USER,
#[cfg(not(feature = "executable_heap"))]
MemoryRegionType::Heap => {
PAGE_PRESENT | PAGE_RW | PAGE_USER | PAGE_NX
}
// The guard page is marked RW and User so that if it gets written to we can detect it in the host
// If/When we implement an interrupt handler for page faults in the guest then we can remove this access and handle things properly there
MemoryRegionType::GuardPage => {
PAGE_PRESENT | PAGE_RW | PAGE_USER | PAGE_NX
}
MemoryRegionType::InputData => PAGE_PRESENT | PAGE_RW | PAGE_NX,
MemoryRegionType::OutputData => PAGE_PRESENT | PAGE_RW | PAGE_NX,
MemoryRegionType::Peb => PAGE_PRESENT | PAGE_RW | PAGE_NX,
// Host Function Definitions are readonly in the guest
MemoryRegionType::HostFunctionDefinitions => PAGE_PRESENT | PAGE_NX,
MemoryRegionType::PanicContext => PAGE_PRESENT | PAGE_RW | PAGE_NX,
MemoryRegionType::GuestErrorData => {
PAGE_PRESENT | PAGE_RW | PAGE_NX
}
// Host Exception Data are readonly in the guest
MemoryRegionType::HostExceptionData => PAGE_PRESENT | PAGE_NX,
MemoryRegionType::PageTables => PAGE_PRESENT | PAGE_RW | PAGE_NX,
MemoryRegionType::KernelStack => PAGE_PRESENT | PAGE_RW | PAGE_NX,
MemoryRegionType::BootStack => PAGE_PRESENT | PAGE_RW | PAGE_NX,
},
// If there is an error then the address isn't mapped so mark it as not present
Err(_) => 0,
};
((p << 21) as u64 | (i << 12) as u64) | flags
let flags = match Self::get_page_flags(p, i, regions) {
Ok(region_type) => match region_type {
// TODO: We parse and load the exe according to its sections and then
// have the correct flags set rather than just marking the entire binary as executable
MemoryRegionType::Code => PAGE_PRESENT | PAGE_RW | PAGE_USER,
MemoryRegionType::Stack => PAGE_PRESENT | PAGE_RW | PAGE_USER | PAGE_NX,
#[cfg(feature = "executable_heap")]
MemoryRegionType::Heap => PAGE_PRESENT | PAGE_RW | PAGE_USER,
#[cfg(not(feature = "executable_heap"))]
MemoryRegionType::Heap => PAGE_PRESENT | PAGE_RW | PAGE_USER | PAGE_NX,
// The guard page is marked RW and User so that if it gets written to we can detect it in the host
// If/When we implement an interrupt handler for page faults in the guest then we can remove this access and handle things properly there
MemoryRegionType::GuardPage => {
PAGE_PRESENT | PAGE_RW | PAGE_USER | PAGE_NX
}
MemoryRegionType::InputData => PAGE_PRESENT | PAGE_RW | PAGE_NX,
MemoryRegionType::OutputData => PAGE_PRESENT | PAGE_RW | PAGE_NX,
MemoryRegionType::Peb => PAGE_PRESENT | PAGE_RW | PAGE_NX,
// Host Function Definitions are readonly in the guest
MemoryRegionType::HostFunctionDefinitions => PAGE_PRESENT | PAGE_NX,
MemoryRegionType::PanicContext => PAGE_PRESENT | PAGE_RW | PAGE_NX,
MemoryRegionType::GuestErrorData => PAGE_PRESENT | PAGE_RW | PAGE_NX,
// Host Exception Data are readonly in the guest
MemoryRegionType::HostExceptionData => PAGE_PRESENT | PAGE_NX,
MemoryRegionType::PageTables => PAGE_PRESENT | PAGE_RW | PAGE_NX,
MemoryRegionType::KernelStack => PAGE_PRESENT | PAGE_RW | PAGE_NX,
MemoryRegionType::BootStack => PAGE_PRESENT | PAGE_RW | PAGE_NX,
},
// If there is an error then the address isn't mapped so mark it as not present
Err(_) => 0,
};
let val_to_write = ((p << 21) as u64 | (i << 12) as u64) | flags;
shared_mem.write_u64(offset, val_to_write)?;
}
}
Expand Down
9 changes: 0 additions & 9 deletions src/hyperlight_host/src/mem/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,4 @@ mod tests {
);
}
}

#[test]
fn ptr_fail() {
{
let raw_guest_ptr = RawPtr(SandboxMemoryLayout::BASE_ADDRESS as u64 - 1);
let guest_ptr = GuestPtr::try_from(raw_guest_ptr);
assert!(guest_ptr.is_err());
}
}
}