Skip to main content

nub_host_common/
layout.rs

1/*
2Copyright 2025  The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15 */
16
17#[path = "arch/amd64/layout.rs"]
18mod arch;
19
20pub use arch::{MAX_GPA, MAX_GVA};
21
22/// Base VA at which the guest's entire memory range is mapped.
23/// Both the host (via mmap of snapshot/scratch regions) and the
24/// guest (via its page table) use this as the anchor. Configurable
25/// via JAR_GUEST_VA_BASE env var (hex string, with or without 0x
26/// prefix); default chosen to sit in the practically-never-touched
27/// mid-range band of x86_64 user VA space.
28pub const GUEST_VA_BASE_DEFAULT: u64 = 0x5000_0000_0000;
29/// Total VA range reserved for the guest. Layout inside:
30/// [0, 4 GiB) javm program; [4, 5 GiB) JIT scratch;
31/// [5 GiB, 7 GiB) kernel (KERNEL_OFFSET); [7 GiB, end) scratch.
32pub const GUEST_VA_SIZE: u64 = 0x4_4000_0000;
33/// Offset within the reservation where the kernel binary loads.
34pub const KERNEL_OFFSET: u64 = 0x1_4000_0000; // 5 GiB
35
36/// The guest VA base: the `JAR_GUEST_VA_BASE` env override (hex) if set,
37/// otherwise [`GUEST_VA_BASE_DEFAULT`].
38#[cfg(feature = "std")]
39pub fn guest_va_base() -> u64 {
40    if let Ok(s) = std::env::var("JAR_GUEST_VA_BASE") {
41        let s = s.trim().trim_start_matches("0x");
42        u64::from_str_radix(s, 16).expect("JAR_GUEST_VA_BASE must be hex")
43    } else {
44        GUEST_VA_BASE_DEFAULT
45    }
46}
47
48/// One-time process-wide reservation of the [`guest_va_base()`,
49/// `guest_va_base() + GUEST_VA_SIZE`) range. Done on host startup so
50/// later mmaps of guest-visible regions (snapshot, scratch, kernel
51/// shadow) can land at known fixed VAs via `MAP_FIXED` inside this
52/// reservation.
53///
54/// We use `MAP_FIXED_NOREPLACE` to claim the configured base
55/// atomically; failure means something is squatting on the range,
56/// which is almost certainly a misconfiguration — error loudly.
57#[cfg(feature = "std")]
58pub fn reserve_guest_va_range() -> Result<(), std::io::Error> {
59    use std::sync::OnceLock;
60    static RESERVED: OnceLock<Result<(), String>> = OnceLock::new();
61    let res = RESERVED.get_or_init(reserve_guest_va_range_inner);
62    res.clone().map_err(std::io::Error::other)
63}
64
65#[cfg(all(feature = "std", target_os = "linux"))]
66fn reserve_guest_va_range_inner() -> Result<(), String> {
67    let base = guest_va_base();
68    let size = GUEST_VA_SIZE as usize;
69    // SAFETY: mmap is a kernel call; we check the result before use.
70    let ptr = unsafe {
71        libc::mmap(
72            base as *mut libc::c_void,
73            size,
74            libc::PROT_NONE,
75            libc::MAP_PRIVATE
76                | libc::MAP_ANONYMOUS
77                | libc::MAP_FIXED_NOREPLACE
78                | libc::MAP_NORESERVE,
79            -1,
80            0,
81        )
82    };
83    if ptr == libc::MAP_FAILED {
84        return Err(format!(
85            "JAR guest VA reservation failed: mmap({:#x}, {} bytes, MAP_FIXED_NOREPLACE): {}",
86            base,
87            size,
88            std::io::Error::last_os_error()
89        ));
90    }
91    if ptr as u64 != base {
92        // Older glibc fallback path: NOREPLACE was ignored and the
93        // kernel placed the mapping elsewhere. Unmap and bail —
94        // something is squatting on our VA range.
95        // SAFETY: ptr came from a successful mmap.
96        unsafe {
97            libc::munmap(ptr, size);
98        }
99        return Err(format!(
100            "JAR guest VA reservation: requested {:#x}, kernel returned {:#x} — \
101             something is squatting on our range",
102            base, ptr as u64
103        ));
104    }
105    Ok(())
106}
107
108#[cfg(all(feature = "std", not(target_os = "linux")))]
109fn reserve_guest_va_range_inner() -> Result<(), String> {
110    Err("JAR guest VA reservation: unsupported host OS (only linux is supported)".into())
111}
112
113// offsets down from the top of scratch memory for various things
114pub const SCRATCH_TOP_SIZE_OFFSET: u64 = 0x08;
115pub const SCRATCH_TOP_ALLOCATOR_OFFSET: u64 = 0x10;
116pub const SCRATCH_TOP_SNAPSHOT_PT_GPA_BASE_OFFSET: u64 = 0x18;
117pub const SCRATCH_TOP_SNAPSHOT_GENERATION_OFFSET: u64 = 0x20;
118pub const SCRATCH_TOP_EXN_STACK_OFFSET: u64 = 0x30;
119
120/// Bytes reserved for each vCPU's ring-0 exception/interrupt stack. The stack
121/// top for lane `n` is below the legacy Hyperlight exception-stack top by
122/// `n * VCPU_EXCEPTION_STACK_STRIDE`.
123pub const VCPU_EXCEPTION_STACK_STRIDE: u64 = 64 * 1024;
124
125/// Bytes reserved for each vCPU's host-dispatch stack. These stacks are used
126/// while executing guest function dispatchers and long-lived invoke workers,
127/// not for ring-3 PVM execution.
128pub const VCPU_DISPATCH_STACK_STRIDE: u64 = 64 * 1024;
129
130/// Fixed number of dispatch-stack lanes mapped by the guest at boot. The
131/// public Nub default caps at 8 vCPUs, but this leaves headroom for explicit
132/// test/bench overrides without needing a guest ABI field during phase 1.
133pub const VCPU_DISPATCH_STACK_LANES: u64 = 64;
134
135pub fn scratch_base_gpa(size: usize) -> u64 {
136    (MAX_GPA - size + 1) as u64
137}
138pub fn scratch_base_gva(size: usize) -> u64 {
139    (MAX_GVA - size + 1) as u64
140}
141
142/// Compute the minimum scratch region size needed for a sandbox.
143pub use arch::min_scratch_size;