Skip to content

Commit 2b783dd

Browse files
committed
amd64: add exception stack at top of scratch memory
A separate exception stack is needed in preparation for moving management of the main guest stack into the guest. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
1 parent 9095115 commit 2b783dd

4 files changed

Lines changed: 127 additions & 5 deletions

File tree

src/hyperlight_guest/src/arch/amd64/prim_alloc.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
18+
1719
// There are no notable architecture-specific safety considerations
1820
// here, and the general conditions are documented in the
1921
// architecture-independent re-export in prim_alloc.rs
@@ -29,8 +31,18 @@ pub unsafe fn alloc_phys_pages(n: u64) -> u64 {
2931
x = inout(reg) x
3032
);
3133
}
32-
if x.checked_add(nbytes).is_none() {
33-
panic!("Out of physical memory!")
34+
// Set aside two pages at the top of the scratch region for the
35+
// exception stack, shared state, etc
36+
let max_avail = hyperlight_common::layout::MAX_GPA - hyperlight_common::vmem::PAGE_SIZE * 2;
37+
if x.checked_add(nbytes)
38+
.is_none_or(|xx| xx >= max_avail as u64)
39+
{
40+
unsafe {
41+
crate::exit::abort_with_code_and_message(
42+
&[ErrorCode::MallocFailed as u8],
43+
c"Out of physical memory".as_ptr(),
44+
)
45+
}
3446
}
3547
x
3648
}

src/hyperlight_guest_bin/src/arch/amd64/init.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,42 @@ use core::arch::asm;
1818
use core::mem;
1919

2020
use super::exception::entry::init_idt;
21-
use super::machine::{GDT, GdtEntry, GdtPointer, ProcCtrl};
21+
use super::machine::{GDT, GdtEntry, GdtPointer, ProcCtrl, TSS};
2222

23+
/// See AMD64 Architecture Programmer's Manual, Volume 2: System Programming
24+
/// Section 4: Segmented Virtual Memory
25+
/// §4.6: Descriptor Tables
26+
/// for the functions of the GDT.
27+
///
28+
/// Hyperlight's GDT consists of:
29+
/// - A null first entry, which is architecturally required
30+
/// - A single code segment descriptor, used for all code accesses
31+
/// - A single data segment descriptor, used for all data accesses
32+
/// - A TSS System descriptor that outlines the location of the TSS
33+
/// (see [`init_tss`], below)
2334
#[repr(C)]
2435
struct HyperlightGDT {
2536
null: GdtEntry,
2637
kernel_code: GdtEntry,
2738
kernel_data: GdtEntry,
39+
tss: [GdtEntry; 2],
2840
}
2941
const _: () = assert!(mem::size_of::<HyperlightGDT>() == mem::size_of::<GDT>());
3042
const _: () = assert!(mem::offset_of!(HyperlightGDT, null) == 0x00);
3143
const _: () = assert!(mem::offset_of!(HyperlightGDT, kernel_code) == 0x08);
3244
const _: () = assert!(mem::offset_of!(HyperlightGDT, kernel_data) == 0x10);
45+
const _: () = assert!(mem::offset_of!(HyperlightGDT, tss) == 0x18);
3346

3447
unsafe fn init_gdt(pc: *mut ProcCtrl) {
3548
unsafe {
3649
let gdt_ptr = &raw mut (*pc).gdt as *mut HyperlightGDT;
3750
(&raw mut (*gdt_ptr).null).write_volatile(GdtEntry::new(0, 0, 0, 0));
3851
(&raw mut (*gdt_ptr).kernel_code).write_volatile(GdtEntry::new(0, 0, 0x9A, 0xA));
3952
(&raw mut (*gdt_ptr).kernel_data).write_volatile(GdtEntry::new(0, 0, 0x92, 0xC));
53+
(&raw mut (*gdt_ptr).tss).write_volatile(GdtEntry::tss(
54+
&raw mut (*pc).tss as u64,
55+
mem::size_of::<TSS>() as u32,
56+
));
4057
let gdtr = GdtPointer {
4158
limit: (core::mem::size_of::<[GdtEntry; 5]>() - 1) as u16,
4259
base: gdt_ptr as u64,
@@ -62,13 +79,39 @@ unsafe fn init_gdt(pc: *mut ProcCtrl) {
6279
}
6380
}
6481

82+
/// Hyperlight's TSS contains only a single IST entry, which is used
83+
/// to set up the stack switch to the exception stack whenever we take
84+
/// an exception (including page faults, which are important, since
85+
/// the fault might be due to needing to grow the stack!)
86+
///
87+
/// This function sets up the TSS and then points the processor at the
88+
/// system segment descriptor, initialized in [`init_gdt`] above,
89+
/// which describes the location of the TSS.
90+
unsafe fn init_tss(pc: *mut ProcCtrl) {
91+
unsafe {
92+
let tss_ptr = &raw mut (*pc).tss;
93+
// copy byte by byte to avoid alignment issues
94+
let ist1_ptr = &raw mut (*tss_ptr).ist1 as *mut [u8; 8];
95+
let exn_stack = hyperlight_common::layout::MAX_GVA as u64
96+
- hyperlight_common::layout::SCRATCH_TOP_EXN_STACK_OFFSET
97+
+ 1;
98+
ist1_ptr.write_volatile(exn_stack.to_ne_bytes());
99+
asm!(
100+
"ltr ax",
101+
in("ax") core::mem::offset_of!(HyperlightGDT, tss),
102+
options(nostack, preserves_flags)
103+
);
104+
}
105+
}
106+
65107
/// Machine-specific initialisation; calls [`crate::generic_init`]
66108
/// once stack, CoW, etc have been set up.
67109
#[unsafe(no_mangle)]
68110
pub extern "C" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_level: u64) {
69111
unsafe {
70112
let pc = ProcCtrl::init();
71113
init_gdt(pc);
114+
init_tss(pc);
72115
init_idt(pc);
73116
call_generic_init(peb_address, seed, ops, max_log_level);
74117
}

src/hyperlight_guest_bin/src/arch/amd64/machine.rs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,40 @@ impl GdtEntry {
5555
access,
5656
}
5757
}
58+
59+
/// Create a new entry that describes the Task State Segment
60+
/// (TSS).
61+
///
62+
/// The segment descriptor for the TSS needs to be wider than
63+
/// other segments, because its base address is actually used &
64+
/// must therefore be able to encode an entire 64-bit VA. Because
65+
/// of this, it uses two adjacent descriptor entries.
66+
///
67+
/// See AMD64 Architecture Programmer's Manual, Volume 2: System Programming
68+
/// Section 4: Segmented Virtual Memory
69+
/// §4.8: Long-Mod Segment Descriptors
70+
/// §4.8.3: System Descriptors
71+
/// for details of the layout
72+
pub const fn tss(base: u64, limit: u32) -> [Self; 2] {
73+
[
74+
Self {
75+
limit_low: (limit & 0xffff) as u16,
76+
base_low: (base & 0xffff) as u16,
77+
base_middle: ((base >> 16) & 0xff) as u8,
78+
access: 0x89,
79+
flags_limit: ((limit >> 16) & 0x0f) as u8,
80+
base_high: ((base >> 24) & 0xff) as u8,
81+
},
82+
Self {
83+
limit_low: ((base >> 32) & 0xffff) as u16,
84+
base_low: ((base >> 48) & 0xffff) as u16,
85+
base_middle: 0,
86+
access: 0,
87+
flags_limit: 0,
88+
base_high: 0,
89+
},
90+
]
91+
}
5892
}
5993

6094
/// GDTR (GDT pointer)
@@ -68,6 +102,32 @@ pub(super) struct GdtPointer {
68102
pub(super) base: u64,
69103
}
70104

105+
/// Task State Segment
106+
///
107+
/// See AMD64 Architecture Programmer's Manual, Volume 2: System Programming
108+
/// Section 12: Task Management
109+
/// §12.2: Task-Management Resources
110+
/// §12.2.5: 64-bit Task State Segment
111+
#[allow(clippy::upper_case_acronyms)]
112+
#[repr(C, packed)]
113+
pub(super) struct TSS {
114+
_rsvd0: [u8; 4],
115+
_rsp0: u64,
116+
_rsp1: u64,
117+
_rsp2: u64,
118+
_rsvd1: [u8; 8],
119+
pub(super) ist1: u64,
120+
_ist2: u64,
121+
_ist3: u64,
122+
_ist4: u64,
123+
_ist5: u64,
124+
_ist6: u64,
125+
_ist7: u64,
126+
_rsvd2: [u8; 8],
127+
}
128+
const _: () = assert!(mem::size_of::<TSS>() == 0x64);
129+
const _: () = assert!(mem::offset_of!(TSS, ist1) == 0x24);
130+
71131
/// An entry in the Interrupt Descriptor Table (IDT)
72132
/// For reference, see page 7-20 Vol. 3A of Intel 64 and IA-32
73133
/// Architectures Software Developer's Manual, figure 7-8
@@ -97,7 +157,7 @@ impl IdtEntry {
97157
Self {
98158
offset_low: (handler & 0xFFFF) as u16,
99159
selector: 0x08, // Kernel Code Segment
100-
interrupt_stack_table_offset: 0,
160+
interrupt_stack_table_offset: 1,
101161
type_attr: 0x8E,
102162
// 0x8E = 10001110b
103163
// 1 00 0 1101
@@ -121,14 +181,15 @@ pub(super) struct IdtPointer {
121181
const _: () = assert!(mem::size_of::<IdtPointer>() == 10);
122182

123183
#[allow(clippy::upper_case_acronyms)]
124-
pub(super) type GDT = [GdtEntry; 3];
184+
pub(super) type GDT = [GdtEntry; 5];
125185
#[allow(clippy::upper_case_acronyms)]
126186
#[repr(align(0x1000))]
127187
pub(super) struct IDT {
128188
pub(super) entries: [IdtEntry; 256],
129189
}
130190
const _: () = assert!(mem::size_of::<IDT>() == 0x1000);
131191

192+
const PADDING_BEFORE_TSS: usize = 64 - mem::size_of::<GDT>();
132193
/// A single structure containing all of the processor control
133194
/// structures that we use during early initialization, making it easy
134195
/// to keep them in an early-allocated physical page. Field alignment
@@ -138,11 +199,14 @@ const _: () = assert!(mem::size_of::<IDT>() == 0x1000);
138199
#[repr(C, align(0x1000))]
139200
pub(super) struct ProcCtrl {
140201
pub(super) gdt: GDT,
202+
_pad: mem::MaybeUninit<[u8; PADDING_BEFORE_TSS]>,
203+
pub(super) tss: TSS,
141204
pub(super) idt: IDT,
142205
}
143206
const _: () = assert!(mem::size_of::<ProcCtrl>() == 0x2000);
144207
const _: () = assert!(mem::size_of::<ProcCtrl>() <= PAGE_SIZE * 2);
145208
const _: () = assert!(mem::offset_of!(ProcCtrl, gdt) == 0);
209+
const _: () = assert!(mem::offset_of!(ProcCtrl, tss) == 64);
146210
const _: () = assert!(mem::offset_of!(ProcCtrl, idt) == 0x1000);
147211

148212
impl ProcCtrl {
@@ -162,6 +226,7 @@ impl ProcCtrl {
162226
);
163227
let ptr = ptr as *mut Self;
164228
(&raw mut (*ptr).gdt).write_bytes(0u8, 1);
229+
(&raw mut (*ptr).tss).write_bytes(0u8, 1);
165230
(&raw mut (*ptr).idt).write_bytes(0u8, 1);
166231
ptr
167232
}

typos.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ mmaped="mmapped"
1111
fpr="fpr"
1212
# consts is a module name in stdlib
1313
consts="consts"
14+
# ist is an acronym for Interrupt Stack Table, not a missspelling of its
15+
ist="ist"

0 commit comments

Comments
 (0)