From 25af0ac87c67c3c2ea5c8e4646a0d71a9b9f65a3 Mon Sep 17 00:00:00 2001 From: smallkirby Date: Sun, 25 Aug 2024 22:52:20 +0900 Subject: [PATCH] successful vmlaunch with nop-only guest Successful VMLAUNCH. The guest loops NOP and never returns. Simple VM-exit handler is also implemented. Signed-off-by: smallkirby --- .gitignore | 2 + ymir/arch/x86/asm.zig | 133 +++++++-- ymir/arch/x86/gdt.zig | 8 +- ymir/arch/x86/vmx.zig | 235 ++++++++++++++-- ymir/arch/x86/vmx/error.zig | 184 ++++++++++++- ymir/arch/x86/vmx/regs.zig | 23 +- ymir/arch/x86/vmx/vmcs.zig | 519 ++++++++++++++++++++---------------- ymir/main.zig | 3 +- 8 files changed, 815 insertions(+), 292 deletions(-) diff --git a/.gitignore b/.gitignore index 8ddf5eb..13a2adc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /.zig-cache /zig-out /docs + +.gdb_history diff --git a/ymir/arch/x86/asm.zig b/ymir/arch/x86/asm.zig index 75b2f5d..4d3c56a 100644 --- a/ymir/arch/x86/asm.zig +++ b/ymir/arch/x86/asm.zig @@ -120,12 +120,10 @@ pub inline fn loadCr3(cr3: u64) void { } pub inline fn readCr3() u64 { - var cr3: u64 = undefined; - asm volatile ( + return asm volatile ( \\mov %%cr3, %[cr3] - : [cr3] "=r" (cr3), + : [cr3] "=r" (-> u64), ); - return cr3; } pub inline fn loadCr4(cr4: Cr4) void { @@ -146,13 +144,11 @@ pub inline fn readCr4() Cr4 { } pub inline fn readEflags() FlagsRegister { - var eflags: u64 = undefined; - asm volatile ( + return @bitCast(asm volatile ( \\pushfq \\pop %[eflags] - : [eflags] "=r" (eflags), - ); - return @bitCast(eflags); + : [eflags] "=r" (-> u64), + )); } pub inline fn writeEflags(eflags: FlagsRegister) void { @@ -164,6 +160,89 @@ pub inline fn writeEflags(eflags: FlagsRegister) void { ); } +const Segment = enum { + cs, + ss, + ds, + es, + fs, + gs, + tr, + ldtr, +}; + +pub inline fn readSegSelector(segment: Segment) u16 { + return switch (segment) { + .cs => asm volatile ("mov %%cs, %[ret]" + : [ret] "=r" (-> u16), + ), + .ss => asm volatile ("mov %%ss, %[ret]" + : [ret] "=r" (-> u16), + ), + .ds => asm volatile ("mov %%ds, %[ret]" + : [ret] "=r" (-> u16), + ), + .es => asm volatile ("mov %%es, %[ret]" + : [ret] "=r" (-> u16), + ), + .fs => asm volatile ("mov %%fs, %[ret]" + : [ret] "=r" (-> u16), + ), + .gs => asm volatile ("mov %%gs, %[ret]" + : [ret] "=r" (-> u16), + ), + .tr => asm volatile ("str %[ret]" + : [ret] "=r" (-> u16), + ), + .ldtr => asm volatile ("sldt %[ret]" + : [ret] "=r" (-> u16), + ), + }; +} + +pub inline fn readSegLimit(selector: u32) u32 { + return asm volatile ( + \\lsl %[selector], %[ret] + : [ret] "=r" (-> u32), + : [selector] "r" (selector), + ); +} + +const SgdtRet = packed struct { + limit: u16, + base: u64, +}; + +pub inline fn sgdt() SgdtRet { + var gdtr: SgdtRet = undefined; + asm volatile ( + \\sgdt %[ret] + : [ret] "=m" (gdtr), + ); + return gdtr; +} + +const SidtRet = packed struct { + limit: u16, + base: u64, +}; + +pub inline fn sidt() SidtRet { + var idtr: SidtRet = undefined; + asm volatile ( + \\sidt %[ret] + : [ret] "=m" (idtr), + ); + return idtr; +} + +inline fn readDr7() u64 { + return asm volatile ( + \\mov %%dr7, %[dr7] + : [dr7] "=r" (-> u64), + ); +} + const CpuidRegisters = struct { eax: u32, ebx: u32, @@ -358,6 +437,7 @@ pub fn relax() void { asm volatile ("rep; nop"); } +/// MSR addresses. pub const Msr = enum(u32) { /// IA32_FEATURE_CONTROL MSR. feature_control = 0x003A, @@ -367,6 +447,8 @@ pub const Msr = enum(u32) { sysenter_esp = 0x175, /// IA32_SYSENTER_EIP MSR. SDM Vol.3A Table 2-2. sysenter_eip = 0x176, + /// IA32_PAT MSR. + pat = 0x277, /// IA32_DEBUGCTL MSR. SDM Vol.4 Table 2-2. debugctl = 0x01D9, /// IA32_VMX_BASIC MSR. @@ -379,6 +461,8 @@ pub const Msr = enum(u32) { vmx_exit_ctls = 0x0483, /// IA32_VMX_ENTRY_CTLS MSR. vmx_entry_ctls = 0x0484, + /// IA32_VMX_MISC MSR. + vmx_misc = 0x0485, /// IA32_VMX_CR0_FIXED0 MSR. vmx_cr0_fixed0 = 0x0486, /// IA32_VMX_CR0_FIXED1 MSR. @@ -397,6 +481,13 @@ pub const Msr = enum(u32) { vmx_true_exit_ctls = 0x048F, /// IA32_VMX_TRUE_ENTRY_CTLS MSR. vmx_true_entry_ctls = 0x0490, + + /// IA32_FS_BASE MSR. + fs_base = 0xC0000100, + /// IA32_GS_BASE MSR. + gs_base = 0xC0000101, + /// IA32_EFER MSR. + efer = 0xC0000080, }; /// IA32_FEATURE_CONTROL MSR. @@ -428,6 +519,7 @@ pub const MsrFeatureControl = packed struct(u64) { _reserved4: u43, }; +/// IA32_VMX_BASIC MSR. pub const MsrVmxBasic = packed struct(u64) { /// VMCS revision identifier. vmcs_revision_id: u31, @@ -443,19 +535,20 @@ pub const MsrVmxBasic = packed struct(u64) { _reserved2: u8, }; +/// EFLAGS register. pub const FlagsRegister = packed struct(u64) { /// Carry flag. cf: bool, - /// Reserved. - _reserved1: u1, + /// Reserved. Must be 1. + _reserved1: u1 = 1, /// Parity flag. pf: bool, - /// Reserved. - _reserved2: u1, + /// Reserved. Must be 0. + _reserved2: u1 = 0, /// Auxiliary carry flag. af: bool, - /// Reserved. - _reserved3: u1, + /// Reserved. Must be 0. + _reserved3: u1 = 0, /// Zero flag. zf: bool, /// Sign flag. @@ -472,8 +565,8 @@ pub const FlagsRegister = packed struct(u64) { iopl: u2, /// Nested task flag. nt: bool, - /// Reserved. - md: bool, + /// Reserved. Must be 0. + md: u1 = 0, /// Resume flag. rf: bool, /// Virtual 8086 mode flag. @@ -492,8 +585,8 @@ pub const FlagsRegister = packed struct(u64) { aes: bool, /// Alternate instruction set enabled. ai: bool, - /// Reserved. - _reserved: u32, + /// Reserved. Must be 0. + _reserved: u32 = 0, }; /// CR0 register. @@ -531,7 +624,7 @@ pub const Cr0 = packed struct(u64) { }; /// CR2 register. It contains VA of the last page fault. -pub const Cr2 = u64; +pub const Cr2 = ymir.mem.Virt; /// CR4 register. pub const Cr4 = packed struct(u64) { diff --git a/ymir/arch/x86/gdt.zig b/ymir/arch/x86/gdt.zig index 37df721..c00b7b3 100644 --- a/ymir/arch/x86/gdt.zig +++ b/ymir/arch/x86/gdt.zig @@ -102,7 +102,7 @@ pub const SegmentDescriptor = packed struct(u64) { /// Segment present. present: bool = true, limit_high: u4, - /// Available for use by system software. Not used by Zakuro-OS. + /// Available for use by system software. avl: u1 = 0, /// 64-bit code segment. /// If set to true, the code segment contains native 64-bit code. @@ -147,19 +147,19 @@ pub const SegmentDescriptor = packed struct(u64) { } }; -const DescriptorType = enum(u1) { +pub const DescriptorType = enum(u1) { /// System Descriptor. System = 0, /// Application Descriptor. CodeOrData = 1, }; -const Granularity = enum(u1) { +pub const Granularity = enum(u1) { Byte = 0, KByte = 1, }; -const SegmentType = enum(u4) { +pub const SegmentType = enum(u4) { // R: Read-Only // W: Write // A: Accessed diff --git a/ymir/arch/x86/vmx.zig b/ymir/arch/x86/vmx.zig index 5b82ebe..12fc2b7 100644 --- a/ymir/arch/x86/vmx.zig +++ b/ymir/arch/x86/vmx.zig @@ -10,9 +10,15 @@ const am = @import("asm.zig"); const vmcs = @import("vmx/vmcs.zig"); const regs = @import("vmx/regs.zig"); -pub const VmxError = @import("vmx/error.zig").VmxError; -pub const VmxInstructionError = @import("vmx/error.zig").VmxInstructionError; +const vmwrite = vmcs.vmwrite; +const vmread = vmcs.vmread; +const vmx_error = @import("vmx/error.zig"); +pub const VmxError = vmx_error.VmxError; +pub const ExitInformation = vmx_error.ExitInformation; +pub const InstructionError = vmx_error.InstructionError; + +/// Read RFLAGS and checks if a VMX instruction has failed. pub fn vmxtry(rflags: u64) VmxError!void { const flags: am.FlagsRegister = @bitCast(rflags); return if (flags.cf) VmxError.FailureInvalidVmcsPointer else if (flags.zf) VmxError.FailureStatusAvailable; @@ -80,6 +86,8 @@ pub fn vmxon(page_allocator: Allocator) VmxError!void { /// Exit VMX operation. pub const vmxoff = am.vmxoff; +/// Clear and reset VMCS. +/// After this operation, the VMCS becomes active and current logical processor. fn resetVmcs(vmcs_region: *VmcsRegion) VmxError!void { // The VMCS becomes inactive and flushed to memory. try am.vmclear(mem.virt2phys(@intFromPtr(vmcs_region))); @@ -103,8 +111,16 @@ pub fn setupVmcs(page_allocator: Allocator) VmxError!void { try setupExitCtrls(); try setupEntryCtrls(); try setupHostState(); + try setupGuestState(); +} - // TODO +/// TODO: temporary +export fn guestDebugHandler() callconv(.Naked) noreturn { + while (true) + asm volatile ( + \\cli + \\nop + ); } /// Set up VM-Execution control fields. @@ -120,14 +136,12 @@ fn setupExecCtrls() VmxError!void { ).load(); // Primary Processor-based VM-Execution control. - const ppb_exec_ctrl = try vmcs.exec_control.PrimaryProcessorBasedExecutionControl.get(); + var ppb_exec_ctrl = try vmcs.exec_control.PrimaryProcessorBasedExecutionControl.get(); + ppb_exec_ctrl.hlt = true; // exit on halt try adjustRegMandatoryBits( ppb_exec_ctrl, if (basic_msr.true_control) am.readMsr(.vmx_true_procbased_ctls) else am.readMsr(.vmx_procbased_ctls), ).load(); - - // CR-3 target. - try vmcs.vmwrite(vmcs.control_cr3_target_count, 0); } /// Set up VM-Exit control fields. @@ -150,7 +164,10 @@ fn setupEntryCtrls() VmxError!void { const basic_msr = am.readMsrVmxBasic(); // VM-Entry control. - const entry_ctrl = try vmcs.entry_control.EntryControls.get(); + var entry_ctrl = try vmcs.entry_control.EntryControls.get(); + entry_ctrl.ia32e_mode_guest = true; + entry_ctrl.load_ia32_efer = true; + entry_ctrl.load_ia32_pat = true; try adjustRegMandatoryBits( entry_ctrl, if (basic_msr.true_control) am.readMsr(.vmx_true_entry_ctls) else am.readMsr(.vmx_entry_ctls), @@ -161,30 +178,187 @@ fn setupEntryCtrls() VmxError!void { /// cf. SDM Vol.3C 27.2.2. fn setupHostState() VmxError!void { // Control registers. - var crs = try regs.ControlRegisters.get(.host); - crs.cr0 = @bitCast(am.readCr0()); - crs.cr3 = @bitCast(am.readCr3()); - crs.cr4 = @bitCast(am.readCr4()); - try crs.load(.host); + try vmwrite(vmcs.Host.cr0, am.readCr0()); + try vmwrite(vmcs.Host.cr3, am.readCr3()); + try vmwrite(vmcs.Host.cr4, am.readCr4()); // General registers. - try vmcs.vmwrite(vmcs.host_rip, @intFromPtr(&vmexitHandler)); + try vmwrite(vmcs.Host.rip, &vmexitBootstrapHandler); + try vmwrite(vmcs.Host.rsp, @intFromPtr(&debug_temp_stack) + debug_temp_stack_size); + + // Segment registers. + try vmwrite(vmcs.Host.cs_sel, am.readSegSelector(.cs)); + try vmwrite(vmcs.Host.ss_sel, am.readSegSelector(.ss)); + try vmwrite(vmcs.Host.ds_sel, am.readSegSelector(.ds)); + try vmwrite(vmcs.Host.es_sel, am.readSegSelector(.es)); + try vmwrite(vmcs.Host.fs_sel, am.readSegSelector(.fs)); + try vmwrite(vmcs.Host.gs_sel, am.readSegSelector(.gs)); + try vmwrite(vmcs.Host.tr_sel, 1 << 3); // TODO: Host TR selector is now 0. We have to set it up first. + try vmwrite(vmcs.Host.gs_base, am.readMsr(.gs_base)); + try vmwrite(vmcs.Host.fs_base, am.readMsr(.fs_base)); + try vmwrite(vmcs.Host.tr_base, 0); // TODO + try vmwrite(vmcs.Host.gdtr_base, am.sgdt().base); + try vmwrite(vmcs.Host.idtr_base, am.sidt().base); + + // MSR. + try vmwrite(vmcs.Host.efer, am.readMsr(.efer)); +} + +fn setupGuestState() VmxError!void { + const entry_ctrl = try vmcs.entry_control.EntryControls.get(); + + // Control registers. + const cr0 = am.readCr0(); + const cr3 = am.readCr3(); + const cr4 = am.readCr4(); + try vmwrite(vmcs.Guest.cr0, cr0); + try vmwrite(vmcs.Guest.cr3, cr3); + try vmwrite(vmcs.Guest.cr4, cr4); // Segment registers. - try vmcs.vmwrite(vmcs.host_cs_selector, 1 << 3); // TODO - try vmcs.vmwrite(vmcs.host_ss_selector, 1 << 3); // TODO - try vmcs.vmwrite(vmcs.host_ds_selector, 1 << 3); // TODO - try vmcs.vmwrite(vmcs.host_es_selector, 1 << 3); // TODO - try vmcs.vmwrite(vmcs.host_fs_selector, 1 << 3); // TODO - try vmcs.vmwrite(vmcs.host_gs_selector, 1 << 3); // TODO - try vmcs.vmwrite(vmcs.host_tr_selector, 1 << 3); // TODO + { + try vmwrite(vmcs.Guest.cs_base, 0); + try vmwrite(vmcs.Guest.ss_base, 0); + try vmwrite(vmcs.Guest.ds_base, 0); + try vmwrite(vmcs.Guest.es_base, 0); + try vmwrite(vmcs.Guest.fs_base, 0); + try vmwrite(vmcs.Guest.gs_base, 0); + try vmwrite(vmcs.Guest.tr_base, 0); + try vmwrite(vmcs.Guest.gdtr_base, am.sgdt().base); + try vmwrite(vmcs.Guest.idtr_base, am.sidt().base); + try vmwrite(vmcs.Guest.ldtr_base, 0); + + try vmwrite(vmcs.Guest.cs_limit, @as(u64, std.math.maxInt(u32))); + try vmwrite(vmcs.Guest.ss_limit, @as(u64, std.math.maxInt(u32))); + try vmwrite(vmcs.Guest.ds_limit, @as(u64, std.math.maxInt(u32))); + try vmwrite(vmcs.Guest.es_limit, @as(u64, std.math.maxInt(u32))); + try vmwrite(vmcs.Guest.fs_limit, @as(u64, std.math.maxInt(u32))); + try vmwrite(vmcs.Guest.gs_limit, @as(u64, std.math.maxInt(u32))); + try vmwrite(vmcs.Guest.tr_limit, @as(u64, std.math.maxInt(u16))); // TODO + try vmwrite(vmcs.Guest.ldtr_limit, @as(u64, std.math.maxInt(u16))); // TODO + try vmwrite(vmcs.Guest.idtr_limit, am.sidt().limit); + try vmwrite(vmcs.Guest.gdtr_limit, am.sgdt().limit); + + const cs_right = vmcs.SegmentRights{ + .type = .CodeERA, + .s = .CodeOrData, + .dpl = 0, + .g = .KByte, + .long = true, + .db = 0, + }; + const ds_right = vmcs.SegmentRights{ + .type = .DataRWA, + .s = .CodeOrData, + .dpl = 0, + .g = .KByte, + .long = false, + .db = 1, + }; + const tr_right = vmcs.SegmentRights{ + .type = .CodeERA, + .s = .System, + .dpl = 0, + .g = .Byte, + .long = false, + .db = 0, + }; + const ldtr_right = vmcs.SegmentRights{ + .type = .DataRW, // XXX: DataRWE? + .s = .System, + .dpl = 0, + .g = .Byte, + .long = false, + .db = 0, + }; + try vmwrite(vmcs.Guest.cs_rights, cs_right); + try vmwrite(vmcs.Guest.ss_rights, ds_right); + try vmwrite(vmcs.Guest.ds_rights, ds_right); + try vmwrite(vmcs.Guest.es_rights, ds_right); + try vmwrite(vmcs.Guest.fs_rights, ds_right); + try vmwrite(vmcs.Guest.gs_rights, ds_right); + try vmwrite(vmcs.Guest.tr_rights, tr_right); + try vmwrite(vmcs.Guest.ldtr_rights, ldtr_right); + + try vmwrite(vmcs.Guest.cs_sel, am.readSegSelector(.cs)); + try vmwrite(vmcs.Guest.ss_sel, am.readSegSelector(.ss)); + try vmwrite(vmcs.Guest.ds_sel, am.readSegSelector(.ds)); + try vmwrite(vmcs.Guest.es_sel, am.readSegSelector(.es)); + try vmwrite(vmcs.Guest.fs_sel, am.readSegSelector(.fs)); + try vmwrite(vmcs.Guest.gs_sel, am.readSegSelector(.gs)); + try vmwrite(vmcs.Guest.tr_sel, am.readSegSelector(.tr)); + try vmwrite(vmcs.Guest.ldtr_sel, am.readSegSelector(.ldtr)); + + try vmwrite(vmcs.Guest.fs_base, am.readMsr(.fs_base)); + try vmwrite(vmcs.Guest.gs_base, am.readMsr(.gs_base)); + } + + // General registers. + try vmwrite(vmcs.Guest.rip, @intFromPtr(&guestDebugHandler)); + try vmwrite(vmcs.Guest.rsp, 0xDEAD0000); // TODO + try vmwrite(vmcs.Guest.rflags, am.readEflags()); + + // MSR + try vmwrite(vmcs.Guest.sysenter_cs, am.readMsr(.sysenter_cs)); + try vmwrite(vmcs.Guest.sysenter_esp, am.readMsr(.sysenter_esp)); + try vmwrite(vmcs.Guest.sysenter_eip, am.readMsr(.sysenter_eip)); + const efer = am.readMsr(.efer); + try vmwrite(vmcs.Guest.efer, efer); + const pat = am.readMsr(.pat); + try vmwrite(vmcs.Guest.pat, pat); + + // Other crucial fields. + try vmwrite(vmcs.Guest.vmcs_link_pointer, std.math.maxInt(u64)); + + // Guest register state partial checks. + if (cr0.pg and !cr0.pe) @panic("CR0: PE must be set when PG is set"); + if (cr4.cet and !cr0.wp) @panic("CR0: WP must be set when CR4.CET is set"); + if (entry_ctrl.ia32e_mode_guest and !(cr0.pg and cr4.pae)) @panic("CR0: PG and CR4.PAE must be set when IA-32e mode is enabled"); + if (!entry_ctrl.ia32e_mode_guest and cr4.pcide) @panic("CR4: PCIDE must be unset when IA-32e mode is disabled"); + if (cr3 >> 46 != 0) @panic("CR3: Reserved bits must be zero"); + if (entry_ctrl.load_debug_controls) @panic("Unsupported guest state: load debug controls."); + if (entry_ctrl.load_cet_state) @panic("Unsupported guest state: load CET state."); + if (entry_ctrl.load_perf_global_ctrl) @panic("Unsupported guest state: load perf global ctrl."); + if (entry_ctrl.load_ia32_efer) { + const lma = (efer >> 10) & 1 != 0; + const lme = (efer >> 8) & 1 != 0; + if (lma != entry_ctrl.ia32e_mode_guest) @panic("EFER and IA-32e mode mismatch"); + if (cr0.pg and (lma != lme)) @panic("EFER.LME must be identical to EFER.LMA when CR0.PG is set"); + } + + const rflags: am.FlagsRegister = @bitCast(try vmcs.vmread(vmcs.Guest.rflags)); + if (rflags._reserved != 0 or rflags._reserved2 != 0 or rflags._reserved3 != 0 or rflags._reserved4 != 0) @panic("RFLAGS: Reserved bits must be zero"); + if (rflags._reserved1 != 1) @panic("RFLAGS: Reserved bit 1 must be one"); + if (entry_ctrl.ia32e_mode_guest and rflags.vm) @panic("RFLAGS: VM must be clear when IA-32e mode is enabled"); + const intr_info = try vmread(vmcs.Ctrl.vmentry_interrupt_information_field); + if (((intr_info >> 31) & 1 != 0) and !rflags.ief) @panic("RFLAGS: IF must be set when valid bit in VM-entry interruption-information field is set."); + + // Guest non-register state partial checks. + const activity_state = try vmcs.vmread(vmcs.Guest.activity_state); + if (activity_state != 0) @panic("Unsupported activity state."); + const intr_state = try vmcs.vmread(vmcs.Guest.interrupt_status); + if ((intr_state >> 5) != 0) @panic("Unsupported interruptability state."); } +/// TODO: Size of temporary stack for VM-exit handler. +const debug_temp_stack_size: usize = 4096; +/// TODO: Temporary stack for VM-exit handler. +var debug_temp_stack: [debug_temp_stack_size + 0x30]u8 align(0x10) = [_]u8{0} ** (debug_temp_stack_size + 0x30); + /// TODO: temporary -fn vmexitHandler() callconv(.Naked) noreturn { - while (true) { +fn vmexitBootstrapHandler() callconv(.Naked) noreturn { + asm volatile ( + \\call vmexitHandler + ); +} + +export fn vmexitHandler() noreturn { + log.debug("[VMEXIT handler]", .{}); + const reason = getExitReason() catch unreachable; + log.debug(" VMEXIT reason: {?}", .{reason}); + + while (true) am.hlt(); - } } fn adjustRegMandatoryBits(control: anytype, mask: u64) @TypeOf(control) { @@ -197,12 +371,19 @@ fn adjustRegMandatoryBits(control: anytype, mask: u64) @TypeOf(control) { // TODO: template pub fn launch() VmxError!void { // Launch VM. + // TODO: save current register state. try am.vmlaunch(); + // TODO: restore register state. +} + +/// Get a instruction error number from VMCS. +pub fn getInstError() VmxError!InstructionError { + return @enumFromInt(@as(u32, @truncate(try vmread(vmcs.Ro.vminstruction_error)))); } -/// Read error reason from the current logical processor's VMCS. -pub fn getErrorReason() VmxError!VmxInstructionError { - return @enumFromInt(try vmcs.vmread(vmcs.ro_vminstruction_error)); +/// Get a VM-exit reason from VMCS. +pub fn getExitReason() VmxError!ExitInformation { + return @bitCast(@as(u32, @truncate(try vmread(vmcs.Ro.vmexit_reason)))); } fn debugPrintVmxonValidity() void { diff --git a/ymir/arch/x86/vmx/error.zig b/ymir/arch/x86/vmx/error.zig index dbe0e2a..c0f92f0 100644 --- a/ymir/arch/x86/vmx/error.zig +++ b/ymir/arch/x86/vmx/error.zig @@ -1,6 +1,27 @@ -/// VM Instruction Errors. +/// Provides a basic information about VM exit. +/// cf. SDM Vol 3C 25.9.1. +pub const ExitInformation = packed struct(u32) { + /// Basic exit reason. + basic_reason: ExitReason, + /// Always 0. + _zero: u1 = 0, + /// Undefined. + _reserved1: u10 = 0, + _one: u1 = 1, + /// Pending MTF VM exit. + pending_mtf: u1 = 0, + /// VM exit from VMX root operation. + exit_vmxroot: bool, + /// Undefined. + _reserved2: u1 = 0, + /// If true, VM-entry failure. If false, true VM exit. + entry_failure: bool, +}; + +/// Reason of failures of VMX instructions. +/// This is not updated on VM-exit. /// cf. SDM Vol.3C 31.4. -pub const VmxInstructionError = enum(u8) { +pub const InstructionError = enum(u32) { error_not_available = 0, vmcall_in_vmxroot = 1, vmclear_invalid_phys = 2, @@ -29,6 +50,165 @@ pub const VmxInstructionError = enum(u8) { invalid_invept = 28, }; +/// Reason of every VM-exit and certain VM-entry failures. +/// cf. SDM Vol.3C Appendix C. +pub const ExitReason = enum(u16) { + /// Exception or NMI. + /// 1. Guest caused an exception of which the bit in the exception bitmap is set. + /// 2. NMI was delivered to the logical processor. + exception_nmi = 0, + /// An external interrupt arrived. + extintr = 1, + /// Triple fault occurred. + triple_fault = 2, + /// INIT signal arrived. + init = 3, + /// Start-up IPI arrived. + sipi = 4, + /// I/O system-management interrupt. + io_intr = 5, + /// SMI arrived and caused an SMM VM exit. + other_smi = 6, + /// Interrupt window. + intr_window = 7, + /// NMI window. + nmi_window = 8, + /// Guest attempted a task switch. + task_switch = 9, + /// Guest attempted to execute CPUID. + cpuid = 10, + /// Guest attempted to execute GETSEC. + getsec = 11, + /// Guest attempted to execute HLT. + hlt = 12, + /// Guest attempted to execute INVD. + invd = 13, + /// Guest attempted to execute INVLPG. + invlpg = 14, + /// Guest attempted to execute RDPMC. + rdpmc = 15, + /// Guest attempted to execute RDTSC. + rdtsc = 16, + /// Guest attempted to execute RSM in SMM. + rsm = 17, + /// Guest attempted to execute VMCALL. + vmcall = 18, + /// Guest attempted to execute VMCLEAR. + vmclear = 19, + /// Guest attempted to execute VMLAUNCH. + vmlaunch = 20, + /// Guest attempted to execute VMPTRLD. + vmptrld = 21, + /// Guest attempted to execute VMPTRST. + vmptrst = 22, + /// Guest attempted to execute VMREAD. + vmread = 23, + /// Guest attempted to execute VMRESUME. + vmresume = 24, + /// Guest attempted to execute VMWRITE. + vmwrite = 25, + /// Guest attempted to execute VMXOFF. + vmxoff = 26, + /// Guest attempted to execute VMXON. + vmxon = 27, + /// Control-register access. + cr = 28, + /// Debug-register access. + dr = 29, + /// I/O instruction. + io = 30, + /// Guest attempted to execute RDMSR. + rdmsr = 31, + /// Guest attempted to execute WRMSR. + wrmsr = 32, + /// VM-entry failure due to invalid guest state. + entry_fail_guest = 33, + /// VM-entry failure due to MSR loading. + entry_fail_msr = 34, + + /// Guest attempted to execute MWAIT. + mwait = 36, + /// Monitor trap flag. + monitor_trap = 37, + + /// Guest attempted to execute MONITOR. + monitor = 39, + /// Guest attempted to execute PAUSE. + pause = 40, + /// VM-entry failure due to machine-check event. + entry_fail_mce = 41, + + /// TPR below threshold. + tpr_threshold = 43, + /// Guest attempted to access memory at a physical address on the API-access page. + apic = 44, + /// EOI virtualization was performed for a virtual interrupt whose vector indexed a bit set in the EOI-exit bitmap. + veoi = 45, + /// Access to GDTR or IDTR. + gdtr_idtr = 46, + /// Access to LDTR or TR. + ldtr_tr = 47, + /// EPT violation. + ept = 48, + /// EPT misconfiguration. + ept_misconfig = 49, + /// Guest attempted to execute INVEPT. + invept = 50, + /// Guest attempted to execute RDTSCP. + rdtscp = 51, + /// Preemption timer counted down to zero. + preemption_timer = 52, + /// Guest attempted to execute INVVPID. + invvpid = 53, + /// Guest attempted to execute WBINVD or WBNOINVD. + wbinvd_wbnoinvd = 54, + /// Guest attempted to execute XSETBV. + xsetbv = 55, + /// Guest completed a write to the virtual-APIC page that must be virtualized. + apic_write = 56, + /// Guest attempted to execute RDRAND. + rdrand = 57, + /// Guest attempted to execute INVPCID. + invpcid = 58, + /// Guest invoked a VM function with the VMFUNC. + vmfunc = 59, + /// Guest attempted to execute ENCLS. + encls = 60, + /// Guest attempted to execute RDSEED. + rdseed = 61, + /// Processor attempted to create a page-modification log entry but the PML index exceeded range 0-511. + page_log_full = 62, + /// Guest attempted to execute XSAVES. + xsaves = 63, + /// Guest attempted to execute XRSTORS. + xrstors = 64, + /// Guest attempted to execute PCONFIG. + pconfig = 65, + /// SPP-related event. + spp = 66, + /// Guest attempted to execute UMWAIT. + umwait = 67, + /// Guest attempted to execute TPAUSE. + tpause = 68, + /// Guest attempted to execute LOADIWKEY. + loadiwkey = 69, + /// Guest attempted to execute ENCLV. + enclv = 70, + + /// ENQCMD PASID translation failure. + enqcmd_pasid_fail = 72, + /// ENQCMDS PASID translation failure. + enqcmds_pasid_fail = 73, + /// Bus lock. + bus_lock = 74, + /// Certain operations prevented the processor from reaching an instruction boundary within timeout. + timeout = 75, + /// Guest attempted to execute SEAMCALL. + seamcall = 76, + /// Guest attempted to execute SEAMOP. + tdcall = 77, +}; + pub const VmxError = error{ /// VMCS pointer is invalid. No status available. FailureInvalidVmcsPointer, diff --git a/ymir/arch/x86/vmx/regs.zig b/ymir/arch/x86/vmx/regs.zig index 0a93822..7bc924f 100644 --- a/ymir/arch/x86/vmx/regs.zig +++ b/ymir/arch/x86/vmx/regs.zig @@ -3,11 +3,6 @@ const vmx = @import("../vmx.zig"); const vmcs = @import("vmcs.zig"); const VmxError = vmx.VmxError; -const FieldDirection = enum { - host, - guest, -}; - /// x64 guest registers. pub const Registers = struct { rax: u64, @@ -41,22 +36,22 @@ pub const SegRegisters = struct { ldt: SegRegister, }; -pub const ControlRegisters = struct { +pub const HostCregs = struct { cr0: u64, cr3: u64, cr4: u64, - pub fn load(self: ControlRegisters, dir: FieldDirection) VmxError!void { - try vmcs.vmwrite(if (dir == .guest) vmcs.guest_cr0 else vmcs.host_cr0, self.cr0); - try vmcs.vmwrite(if (dir == .guest) vmcs.guest_cr3 else vmcs.host_cr3, self.cr3); - try vmcs.vmwrite(if (dir == .guest) vmcs.guest_cr4 else vmcs.host_cr4, self.cr4); + pub fn load(self: HostCregs) VmxError!void { + try vmcs.vmwrite(vmcs.Host.cr0, self.cr0); + try vmcs.vmwrite(vmcs.Host.cr3, self.cr3); + try vmcs.vmwrite(vmcs.Host.cr4, self.cr4); } - pub fn get(dir: FieldDirection) VmxError!ControlRegisters { + pub fn get() VmxError!HostCregs { return .{ - .cr0 = try vmcs.vmread(if (dir == .guest) vmcs.guest_cr0 else vmcs.host_cr0), - .cr3 = try vmcs.vmread(if (dir == .guest) vmcs.guest_cr3 else vmcs.host_cr3), - .cr4 = try vmcs.vmread(if (dir == .guest) vmcs.guest_cr4 else vmcs.host_cr4), + .cr0 = try vmcs.vmread(vmcs.Host.cr0), + .cr3 = try vmcs.vmread(vmcs.Host.cr3), + .cr4 = try vmcs.vmread(vmcs.Host.cr4), }; } }; diff --git a/ymir/arch/x86/vmx/vmcs.zig b/ymir/arch/x86/vmx/vmcs.zig index 7b89877..704ccae 100644 --- a/ymir/arch/x86/vmx/vmcs.zig +++ b/ymir/arch/x86/vmx/vmcs.zig @@ -6,8 +6,12 @@ const vmcs = @import("vmcs.zig"); const VmxError = vmx.VmxError; const err = vmx.vmxtry; -pub fn vmread(field: ComponentEncoding) VmxError!u64 { - const field_u64: u32 = @bitCast(field); +/// VMREAD. +/// `field` is a encoded VMCS field. +/// If the operation succeeds, the value of the field is returned. +/// Regardless of the field length, this function returns a 64-bit value. +/// If the operation fails, an error is returned. +pub fn vmread(field: anytype) VmxError!u64 { var rflags: u64 = undefined; const ret = asm volatile ( \\vmread %[field], %[ret] @@ -15,227 +19,261 @@ pub fn vmread(field: ComponentEncoding) VmxError!u64 { \\popq %[rflags] : [ret] "={rax}" (-> u64), [rflags] "=r" (rflags), - : [field] "r" (@as(u64, @intCast(field_u64))), + : [field] "r" (@as(u64, @intFromEnum(field))), ); try err(rflags); return ret; } -pub fn vmwrite(field: ComponentEncoding, value: u64) VmxError!void { - const field_u64: u32 = @bitCast(field); - var rflags: u64 = undefined; - asm volatile ( +/// VMWRITE. +/// `field` is a encoded VMCS field. +/// `value` is a value to write to the field. +/// `value` can be either of the following types: +/// - integer (including comptime value) +/// - pointer +/// - structure (up to 8 bytes) +/// `value` is automatically casted to an appropriate-width integer. +/// If the operation fails, an error is returned. +pub fn vmwrite(field: anytype, value: anytype) VmxError!void { + const value_int = switch (@typeInfo(@TypeOf(value))) { + .Int, .ComptimeInt => @as(u64, value), + .Struct => switch (@sizeOf(@TypeOf(value))) { + 1 => @as(u8, @bitCast(value)), + 2 => @as(u16, @bitCast(value)), + 4 => @as(u32, @bitCast(value)), + 8 => @as(u64, @bitCast(value)), + else => @compileError("Unsupported structure size for vmwrite"), + }, + .Pointer => @as(u64, @intFromPtr(value)), + else => @compileError("Unsupported type for vmwrite"), + }; + + const rflags = asm volatile ( \\vmwrite %[value], %[field] - : [rflags] "=r" (rflags), - : [field] "r" (@as(u64, @intCast(field_u64))), - [value] "r" (value), + \\pushf + \\popq %[rflags] + : [rflags] "=r" (-> u64), + : [field] "r" (@as(u64, @intFromEnum(field))), + [value] "r" (@as(u64, value_int)), ); try err(rflags); } -// === Guest State Area. cf. SDM Vol.3C 25.4, Appendix B. ================ -// Natural-width fields. -pub const guest_cr0 = encode(.guest_state, 0, .full, .natural); -pub const guest_cr3 = encode(.guest_state, 1, .full, .natural); -pub const guest_cr4 = encode(.guest_state, 2, .full, .natural); -pub const guest_es_base = encode(.guest_state, 3, .full, .natural); -pub const guest_cs_base = encode(.guest_state, 4, .full, .natural); -pub const guest_ss_base = encode(.guest_state, 5, .full, .natural); -pub const guest_ds_base = encode(.guest_state, 6, .full, .natural); -pub const guest_fs_base = encode(.guest_state, 7, .full, .natural); -pub const guest_gs_base = encode(.guest_state, 8, .full, .natural); -pub const guest_ldtr_base = encode(.guest_state, 9, .full, .natural); -pub const guest_tr_base = encode(.guest_state, 10, .full, .natural); -pub const guest_gdtr_base = encode(.guest_state, 11, .full, .natural); -pub const guest_idtr_base = encode(.guest_state, 12, .full, .natural); -pub const guest_dr7 = encode(.guest_state, 13, .full, .natural); -pub const guest_rsp = encode(.guest_state, 14, .full, .natural); -pub const guest_rip = encode(.guest_state, 15, .full, .natural); -pub const guest_rflags = encode(.guest_state, 16, .full, .natural); -pub const guest_pending_debug_exceptions = encode(.guest_state, 17, .full, .natural); -pub const guest_sysenter_esp = encode(.guest_state, 18, .full, .natural); -pub const guest_sysenter_eip = encode(.guest_state, 19, .full, .natural); -pub const guest_s_cet = encode(.guest_state, 20, .full, .natural); -pub const guest_ssp = encode(.guest_state, 21, .full, .natural); -pub const guest_interrupt_ssp_table_addr = encode(.guest_state, 22, .full, .natural); -// 16-bit fields. -pub const guest_es_selector = encode(.guest_state, 0, .full, .word); -pub const guest_cs_selector = encode(.guest_state, 1, .full, .word); -pub const guest_ss_selector = encode(.guest_state, 2, .full, .word); -pub const guest_ds_selector = encode(.guest_state, 3, .full, .word); -pub const guest_fs_selector = encode(.guest_state, 4, .full, .word); -pub const guest_gs_selector = encode(.guest_state, 5, .full, .word); -pub const guest_ldtr_selector = encode(.guest_state, 6, .full, .word); -pub const guest_tr_selector = encode(.guest_state, 7, .full, .word); -pub const guest_interrupt_status = encode(.guest_state, 8, .full, .word); -pub const guest_pml_index = encode(.guest_state, 9, .full, .word); -pub const guest_uinv = encode(.guest_state, 10, .full, .word); -// 32-bit fields. -pub const guest_es_limit = encode(.guest_state, 0, .full, .dword); -pub const guest_cs_limit = encode(.guest_state, 1, .full, .dword); -pub const guest_ss_limit = encode(.guest_state, 2, .full, .dword); -pub const guest_ds_limit = encode(.guest_state, 3, .full, .dword); -pub const guest_fs_limit = encode(.guest_state, 4, .full, .dword); -pub const guest_gs_limit = encode(.guest_state, 5, .full, .dword); -pub const guest_ldtr_limit = encode(.guest_state, 6, .full, .dword); -pub const guest_tr_limit = encode(.guest_state, 7, .full, .dword); -pub const guest_gdtr_limit = encode(.guest_state, 8, .full, .dword); -pub const guest_idtr_limit = encode(.guest_state, 9, .full, .dword); -pub const guest_es_access_rights = encode(.guest_state, 10, .full, .dword); -pub const guest_cs_access_rights = encode(.guest_state, 11, .full, .dword); -pub const guest_ss_access_rights = encode(.guest_state, 12, .full, .dword); -pub const guest_ds_access_rights = encode(.guest_state, 13, .full, .dword); -pub const guest_fs_access_rights = encode(.guest_state, 14, .full, .dword); -pub const guest_gs_access_rights = encode(.guest_state, 15, .full, .dword); -pub const guest_ldtr_access_rights = encode(.guest_state, 16, .full, .dword); -pub const guest_tr_access_rights = encode(.guest_state, 17, .full, .dword); -pub const guest_interruptibility_state = encode(.guest_state, 18, .full, .dword); -pub const guest_activity_state = encode(.guest_state, 19, .full, .dword); -pub const guest_smbase = encode(.guest_state, 20, .full, .dword); -pub const guest_sysenter_cs = encode(.guest_state, 21, .full, .dword); -pub const guest_vmx_preemption_timer_value = encode(.guest_state, 22, .full, .dword); -// 64-bit fields. -pub const guest_vmcs_link_pointer = encode(.guest_state, 0, .full, .qword); -pub const guest_debugctl = encode(.guest_state, 1, .full, .qword); -pub const guest_pat = encode(.guest_state, 2, .full, .qword); -pub const guest_efer = encode(.guest_state, 3, .full, .qword); -pub const guest_perf_global_ctrl = encode(.guest_state, 4, .full, .qword); -pub const guest_pdpte0 = encode(.guest_state, 5, .full, .qword); -pub const guest_pdpte1 = encode(.guest_state, 6, .full, .qword); -pub const guest_pdpte2 = encode(.guest_state, 7, .full, .qword); -pub const guest_pdpte3 = encode(.guest_state, 8, .full, .qword); -pub const guest_bndcfgs = encode(.guest_state, 9, .full, .qword); -pub const guest_rtit_ctl = encode(.guest_state, 10, .full, .qword); -pub const guest_lbr_ctl = encode(.guest_state, 11, .full, .qword); -pub const guest_pkrs = encode(.guest_state, 12, .full, .qword); - -// === Host State Area. cf. SDM Vol.3C 25.4, Appendix B. ================ -// Natural-width fields. -pub const host_cr0 = encode(.host_state, 0, .full, .natural); -pub const host_cr3 = encode(.host_state, 1, .full, .natural); -pub const host_cr4 = encode(.host_state, 2, .full, .natural); -pub const host_fs_base = encode(.host_state, 3, .full, .natural); -pub const host_gs_base = encode(.host_state, 4, .full, .natural); -pub const host_tr_base = encode(.host_state, 5, .full, .natural); -pub const host_gdtr_base = encode(.host_state, 6, .full, .natural); -pub const host_idtr_base = encode(.host_state, 7, .full, .natural); -pub const host_sysenter_esp = encode(.host_state, 8, .full, .natural); -pub const host_sysenter_eip = encode(.host_state, 9, .full, .natural); -pub const host_rsp = encode(.host_state, 10, .full, .natural); -pub const host_rip = encode(.host_state, 11, .full, .natural); -pub const host_s_cet = encode(.host_state, 12, .full, .natural); -pub const host_ssp = encode(.host_state, 13, .full, .natural); -pub const host_interrupt_ssp_table_addr = encode(.host_state, 14, .full, .natural); -// 16-bit fields. -pub const host_es_selector = encode(.host_state, 0, .full, .word); -pub const host_cs_selector = encode(.host_state, 1, .full, .word); -pub const host_ss_selector = encode(.host_state, 2, .full, .word); -pub const host_ds_selector = encode(.host_state, 3, .full, .word); -pub const host_fs_selector = encode(.host_state, 4, .full, .word); -pub const host_gs_selector = encode(.host_state, 5, .full, .word); -pub const host_tr_selector = encode(.host_state, 6, .full, .word); -// 32-bit fields. -pub const host_sysenter_cs = encode(.host_state, 0, .full, .dword); -// 64-bit fields. -pub const host_pat = encode(.host_state, 0, .full, .qword); -pub const host_efer = encode(.host_state, 1, .full, .qword); -pub const host_perf_global_ctrl = encode(.host_state, 2, .full, .qword); -pub const host_pkrs = encode(.host_state, 3, .full, .qword); - -// === Control cf. SDM Vol.3C 25.4, Appendix B. ================ -// Natural-width fields. -pub const control_cr0_mask = encode(.control, 0, .full, .natural); -pub const control_cr4_mask = encode(.control, 1, .full, .natural); -pub const control_cr0_read_shadow = encode(.control, 2, .full, .natural); -pub const control_cr4_read_shadow = encode(.control, 3, .full, .natural); -pub const control_cr3_target_value0 = encode(.control, 4, .full, .natural); -pub const control_cr3_target_value1 = encode(.control, 5, .full, .natural); -pub const control_cr3_target_value2 = encode(.control, 6, .full, .natural); -pub const control_cr3_target_value3 = encode(.control, 7, .full, .natural); -// 16-bit fields. -pub const control_vpid = encode(.control, 0, .full, .word); -pub const control_posted_interrupt_notification_vector = encode(.control, 1, .full, .word); -pub const control_eptp_index = encode(.control, 2, .full, .word); -pub const control_hlat_prefix_size = encode(.control, 3, .full, .word); -pub const control_pid_pointer_index = encode(.control, 4, .full, .word); -// 32-bit fields. -pub const control_pinbased_vmexec_controls = encode(.control, 0, .full, .dword); -pub const control_procbased_vmexec_controls = encode(.control, 1, .full, .dword); -pub const control_exception_bitmap = encode(.control, 2, .full, .dword); -pub const control_pagefault_error_code_mask = encode(.control, 3, .full, .dword); -pub const control_pagefault_error_code_match = encode(.control, 4, .full, .dword); -pub const control_cr3_target_count = encode(.control, 5, .full, .dword); -pub const control_primary_vmexit_controls = encode(.control, 6, .full, .dword); -pub const control_vmexit_msr_store_count = encode(.control, 7, .full, .dword); -pub const control_vmexit_msr_load_count = encode(.control, 8, .full, .dword); -pub const control_vmentry_controls = encode(.control, 9, .full, .dword); -pub const control_vmentry_msr_load_count = encode(.control, 10, .full, .dword); -pub const control_vmentry_interrupt_information_field = encode(.control, 11, .full, .dword); -pub const control_vmentry_exception_error_code = encode(.control, 12, .full, .dword); -pub const control_vmentry_instruction_length = encode(.control, 13, .full, .dword); -pub const control_tpr_threshold = encode(.control, 14, .full, .dword); -pub const control_secondary_procbased_vmexec_controls = encode(.control, 15, .full, .dword); -pub const control_ple_gap = encode(.control, 16, .full, .dword); -pub const control_ple_window = encode(.control, 17, .full, .dword); -pub const control_instruction_timeouts = encode(.control, 18, .full, .dword); -// 64-bit fields. -pub const control_io_bitmap_a = encode(.control, 0, .full, .qword); -pub const control_io_bitmap_b = encode(.control, 1, .full, .qword); -pub const control_msr_bitmap = encode(.control, 2, .full, .qword); -pub const control_vmexit_msr_store_address = encode(.control, 3, .full, .qword); -pub const control_vmexit_msr_load_address = encode(.control, 4, .full, .qword); -pub const control_vmentry_msr_load_address = encode(.control, 5, .full, .qword); -pub const control_executive_vmcs_pointer = encode(.control, 6, .full, .qword); -pub const control_pml_address = encode(.control, 7, .full, .qword); -pub const control_tsc_offset = encode(.control, 8, .full, .qword); -pub const control_virtual_apic_address = encode(.control, 9, .full, .qword); -pub const control_apic_access_address = encode(.control, 10, .full, .qword); -pub const control_posted_interrupt_descriptor_address = encode(.control, 11, .full, .qword); -pub const control_vm_function_controls = encode(.control, 12, .full, .qword); -pub const control_eptp = encode(.control, 13, .full, .qword); -pub const control_eoi_exit_bitmap0 = encode(.control, 14, .full, .qword); -pub const control_eoi_exit_bitmap1 = encode(.control, 15, .full, .qword); -pub const control_eoi_exit_bitmap2 = encode(.control, 16, .full, .qword); -pub const control_eoi_exit_bitmap3 = encode(.control, 17, .full, .qword); -pub const control_eptp_list_address = encode(.control, 18, .full, .qword); -pub const control_vmread_bitmap = encode(.control, 19, .full, .qword); -pub const control_vmwrite_bitmap = encode(.control, 20, .full, .qword); -pub const control_vexception_information_address = encode(.control, 21, .full, .qword); -pub const control_xss_exiting_bitmap = encode(.control, 22, .full, .qword); -pub const control_encls_exiting_bitmap = encode(.control, 23, .full, .qword); -pub const control_sub_page_permission_table_pointer = encode(.control, 24, .full, .qword); -pub const control_tsc_multiplier = encode(.control, 25, .full, .qword); -pub const control_tertiary_processor_based_vmexec_controls = encode(.control, 26, .full, .qword); -pub const enclv_exiting_bitmap = encode(.control, 27, .full, .qword); -pub const control_low_pasid_directory = encode(.control, 28, .full, .qword); -pub const control_high_pasid_directory = encode(.control, 29, .full, .qword); -pub const control_shared_eptp = encode(.control, 30, .full, .qword); -pub const control_pconfig_exiting_bitmap = encode(.control, 31, .full, .qword); -pub const control_hlatp = encode(.control, 32, .full, .qword); -pub const control_pid_pointer_table = encode(.control, 33, .full, .qword); -pub const control_secondary_vmexit_controls = encode(.control, 34, .full, .qword); -pub const control_spec_ctrl_mask = encode(.control, 37, .full, .qword); -pub const control_spec_ctrl_shadow = encode(.control, 38, .full, .qword); - -// === Read-Only cf. SDM Vol.3C 25.4, Appendix B. ================ -// Natural-width fields. -pub const ro_exit_qual = encode(.vmexit, 0, .full, .natural); -pub const ro_io_rcx = encode(.vmexit, 1, .full, .natural); -pub const ro_io_rsi = encode(.vmexit, 2, .full, .natural); -pub const ro_io_rdi = encode(.vmexit, 3, .full, .natural); -pub const ro_io_rip = encode(.vmexit, 4, .full, .natural); -pub const ro_guest_linear_address = encode(.vmexit, 5, .full, .natural); -// 32-bit fields. -pub const ro_vminstruction_error = encode(.vmexit, 0, .full, .dword); -pub const ro_vmexit_reason = encode(.vmexit, 1, .full, .dword); -pub const ro_vmexit_interruption_information = encode(.vmexit, 2, .full, .dword); -pub const ro_vmexit_interruption_error_code = encode(.vmexit, 3, .full, .dword); -pub const ro_idt_vectoring_information = encode(.vmexit, 4, .full, .dword); -pub const ro_idt_vectoring_error_code = encode(.vmexit, 5, .full, .dword); -pub const ro_vmexit_instruction_length = encode(.vmexit, 6, .full, .dword); -pub const ro_vmexit_instruction_information = encode(.vmexit, 7, .full, .dword); -// 64-bit fields. -pub const ro_guest_physical_address = encode(.vmexit, 0, .full, .qword); +/// Guest state area encodings. +/// cf. SDM Vol.3C 25.4, Appendix B. +pub const Guest = enum(u32) { + // Natural-width fields. + cr0 = encode(.guest_state, 0, .full, .natural), + cr3 = encode(.guest_state, 1, .full, .natural), + cr4 = encode(.guest_state, 2, .full, .natural), + es_base = encode(.guest_state, 3, .full, .natural), + cs_base = encode(.guest_state, 4, .full, .natural), + ss_base = encode(.guest_state, 5, .full, .natural), + ds_base = encode(.guest_state, 6, .full, .natural), + fs_base = encode(.guest_state, 7, .full, .natural), + gs_base = encode(.guest_state, 8, .full, .natural), + ldtr_base = encode(.guest_state, 9, .full, .natural), + tr_base = encode(.guest_state, 10, .full, .natural), + gdtr_base = encode(.guest_state, 11, .full, .natural), + idtr_base = encode(.guest_state, 12, .full, .natural), + dr7 = encode(.guest_state, 13, .full, .natural), + rsp = encode(.guest_state, 14, .full, .natural), + rip = encode(.guest_state, 15, .full, .natural), + rflags = encode(.guest_state, 16, .full, .natural), + pending_debug_exceptions = encode(.guest_state, 17, .full, .natural), + sysenter_esp = encode(.guest_state, 18, .full, .natural), + sysenter_eip = encode(.guest_state, 19, .full, .natural), + s_cet = encode(.guest_state, 20, .full, .natural), + ssp = encode(.guest_state, 21, .full, .natural), + interrupt_ssp_table_addr = encode(.guest_state, 22, .full, .natural), + // 16-bit fields. + es_sel = encode(.guest_state, 0, .full, .word), + cs_sel = encode(.guest_state, 1, .full, .word), + ss_sel = encode(.guest_state, 2, .full, .word), + ds_sel = encode(.guest_state, 3, .full, .word), + fs_sel = encode(.guest_state, 4, .full, .word), + gs_sel = encode(.guest_state, 5, .full, .word), + ldtr_sel = encode(.guest_state, 6, .full, .word), + tr_sel = encode(.guest_state, 7, .full, .word), + interrupt_status = encode(.guest_state, 8, .full, .word), + pml_index = encode(.guest_state, 9, .full, .word), + uinv = encode(.guest_state, 10, .full, .word), + // 32-bit fields. + es_limit = encode(.guest_state, 0, .full, .dword), + cs_limit = encode(.guest_state, 1, .full, .dword), + ss_limit = encode(.guest_state, 2, .full, .dword), + ds_limit = encode(.guest_state, 3, .full, .dword), + fs_limit = encode(.guest_state, 4, .full, .dword), + gs_limit = encode(.guest_state, 5, .full, .dword), + ldtr_limit = encode(.guest_state, 6, .full, .dword), + tr_limit = encode(.guest_state, 7, .full, .dword), + gdtr_limit = encode(.guest_state, 8, .full, .dword), + idtr_limit = encode(.guest_state, 9, .full, .dword), + es_rights = encode(.guest_state, 10, .full, .dword), + cs_rights = encode(.guest_state, 11, .full, .dword), + ss_rights = encode(.guest_state, 12, .full, .dword), + ds_rights = encode(.guest_state, 13, .full, .dword), + fs_rights = encode(.guest_state, 14, .full, .dword), + gs_rights = encode(.guest_state, 15, .full, .dword), + ldtr_rights = encode(.guest_state, 16, .full, .dword), + tr_rights = encode(.guest_state, 17, .full, .dword), + interruptibility_state = encode(.guest_state, 18, .full, .dword), + activity_state = encode(.guest_state, 19, .full, .dword), + smbase = encode(.guest_state, 20, .full, .dword), + sysenter_cs = encode(.guest_state, 21, .full, .dword), + preemp_timer = encode(.guest_state, 22, .full, .dword), + // 64-bit fields. + vmcs_link_pointer = encode(.guest_state, 0, .full, .qword), + dbgctl = encode(.guest_state, 1, .full, .qword), + pat = encode(.guest_state, 2, .full, .qword), + efer = encode(.guest_state, 3, .full, .qword), + perf_global_ctrl = encode(.guest_state, 4, .full, .qword), + pdpte0 = encode(.guest_state, 5, .full, .qword), + pdpte1 = encode(.guest_state, 6, .full, .qword), + pdpte2 = encode(.guest_state, 7, .full, .qword), + pdpte3 = encode(.guest_state, 8, .full, .qword), + bndcfgs = encode(.guest_state, 9, .full, .qword), + rtit_ctl = encode(.guest_state, 10, .full, .qword), + lbr_ctl = encode(.guest_state, 11, .full, .qword), + pkrs = encode(.guest_state, 12, .full, .qword), +}; + +/// Host state area encodings. +/// cf. SDM Vol.3C 25.4, Appendix B. +pub const Host = enum(u32) { + // Natural-width fields. + cr0 = encode(.host_state, 0, .full, .natural), + cr3 = encode(.host_state, 1, .full, .natural), + cr4 = encode(.host_state, 2, .full, .natural), + fs_base = encode(.host_state, 3, .full, .natural), + gs_base = encode(.host_state, 4, .full, .natural), + tr_base = encode(.host_state, 5, .full, .natural), + gdtr_base = encode(.host_state, 6, .full, .natural), + idtr_base = encode(.host_state, 7, .full, .natural), + sysenter_esp = encode(.host_state, 8, .full, .natural), + sysenter_eip = encode(.host_state, 9, .full, .natural), + rsp = encode(.host_state, 10, .full, .natural), + rip = encode(.host_state, 11, .full, .natural), + s_cet = encode(.host_state, 12, .full, .natural), + ssp = encode(.host_state, 13, .full, .natural), + interrupt_ssp_table_addr = encode(.host_state, 14, .full, .natural), + // 16-bit fields. + es_sel = encode(.host_state, 0, .full, .word), + cs_sel = encode(.host_state, 1, .full, .word), + ss_sel = encode(.host_state, 2, .full, .word), + ds_sel = encode(.host_state, 3, .full, .word), + fs_sel = encode(.host_state, 4, .full, .word), + gs_sel = encode(.host_state, 5, .full, .word), + tr_sel = encode(.host_state, 6, .full, .word), + // 32-bit fields. + sysenter_cs = encode(.host_state, 0, .full, .dword), + // 64-bit fields. + pat = encode(.host_state, 0, .full, .qword), + efer = encode(.host_state, 1, .full, .qword), + perf_global_ctrl = encode(.host_state, 2, .full, .qword), + pkrs = encode(.host_state, 3, .full, .qword), +}; + +/// Control area encodings. +/// cf. SDM Vol.3C 25.4, Appendix B. +pub const Ctrl = enum(u32) { + // Natural-width fields. + cr0_mask = encode(.control, 0, .full, .natural), + cr4_mask = encode(.control, 1, .full, .natural), + cr0_read_shadow = encode(.control, 2, .full, .natural), + cr4_read_shadow = encode(.control, 3, .full, .natural), + cr3_target0 = encode(.control, 4, .full, .natural), + cr3_target1 = encode(.control, 5, .full, .natural), + cr3_target2 = encode(.control, 6, .full, .natural), + cr3_target3 = encode(.control, 7, .full, .natural), + // 16-bit fields. + vpid = encode(.control, 0, .full, .word), + posted_interrupt_notification_vector = encode(.control, 1, .full, .word), + eptp_index = encode(.control, 2, .full, .word), + hlat_prefix_size = encode(.control, 3, .full, .word), + pid_pointer_index = encode(.control, 4, .full, .word), + // 32-bit fields. + pinbased_vmexec_controls = encode(.control, 0, .full, .dword), + procbased_vmexec_controls = encode(.control, 1, .full, .dword), + exception_bitmap = encode(.control, 2, .full, .dword), + pagefault_error_code_mask = encode(.control, 3, .full, .dword), + pagefault_error_code_match = encode(.control, 4, .full, .dword), + cr3_target_count = encode(.control, 5, .full, .dword), + primary_vmexit_controls = encode(.control, 6, .full, .dword), + vmexit_msr_store_count = encode(.control, 7, .full, .dword), + vmexit_msr_load_count = encode(.control, 8, .full, .dword), + vmentry_controls = encode(.control, 9, .full, .dword), + vmentry_msr_load_count = encode(.control, 10, .full, .dword), + vmentry_interrupt_information_field = encode(.control, 11, .full, .dword), + vmentry_exception_error_code = encode(.control, 12, .full, .dword), + vmentry_instruction_length = encode(.control, 13, .full, .dword), + tpr_threshold = encode(.control, 14, .full, .dword), + secondary_procbased_vmexec_controls = encode(.control, 15, .full, .dword), + ple_gap = encode(.control, 16, .full, .dword), + ple_window = encode(.control, 17, .full, .dword), + instruction_timeouts = encode(.control, 18, .full, .dword), + // 64-bit fields. + io_bitmap_a = encode(.control, 0, .full, .qword), + io_bitmap_b = encode(.control, 1, .full, .qword), + msr_bitmap = encode(.control, 2, .full, .qword), + vmexit_msr_store_address = encode(.control, 3, .full, .qword), + vmexit_msr_load_address = encode(.control, 4, .full, .qword), + vmentry_msr_load_address = encode(.control, 5, .full, .qword), + executive_vmcs_pointer = encode(.control, 6, .full, .qword), + pml_address = encode(.control, 7, .full, .qword), + tsc_offset = encode(.control, 8, .full, .qword), + virtual_apic_address = encode(.control, 9, .full, .qword), + apic_access_address = encode(.control, 10, .full, .qword), + posted_interrupt_descriptor_address = encode(.control, 11, .full, .qword), + vm_function_controls = encode(.control, 12, .full, .qword), + eptp = encode(.control, 13, .full, .qword), + eoi_exit_bitmap0 = encode(.control, 14, .full, .qword), + eoi_exit_bitmap1 = encode(.control, 15, .full, .qword), + eoi_exit_bitmap2 = encode(.control, 16, .full, .qword), + eoi_exit_bitmap3 = encode(.control, 17, .full, .qword), + eptp_list_address = encode(.control, 18, .full, .qword), + vmread_bitmap = encode(.control, 19, .full, .qword), + vmwrite_bitmap = encode(.control, 20, .full, .qword), + vexception_information_address = encode(.control, 21, .full, .qword), + xss_exiting_bitmap = encode(.control, 22, .full, .qword), + encls_exiting_bitmap = encode(.control, 23, .full, .qword), + sub_page_permission_table_pointer = encode(.control, 24, .full, .qword), + tsc_multiplier = encode(.control, 25, .full, .qword), + tertiary_processor_based_vmexec_controls = encode(.control, 26, .full, .qword), + enclv_exiting_bitmap = encode(.control, 27, .full, .qword), + low_pasid_directory = encode(.control, 28, .full, .qword), + high_pasid_directory = encode(.control, 29, .full, .qword), + shared_eptp = encode(.control, 30, .full, .qword), + pconfig_exiting_bitmap = encode(.control, 31, .full, .qword), + hlatp = encode(.control, 32, .full, .qword), + pid_pointer_table = encode(.control, 33, .full, .qword), + secondary_vmexit_controls = encode(.control, 34, .full, .qword), + spec_ctrl_mask = encode(.control, 37, .full, .qword), + spec_ctrl_shadow = encode(.control, 38, .full, .qword), +}; + +/// Read-only area encodings. +/// cf. SDM Vol.3C 25.4, Appendix B. +pub const Ro = enum(u32) { + // Natural-width fields. + exit_qual = encode(.vmexit, 0, .full, .natural), + io_rcx = encode(.vmexit, 1, .full, .natural), + io_rsi = encode(.vmexit, 2, .full, .natural), + io_rdi = encode(.vmexit, 3, .full, .natural), + io_rip = encode(.vmexit, 4, .full, .natural), + guest_linear_address = encode(.vmexit, 5, .full, .natural), + // 32-bit fields. + vminstruction_error = encode(.vmexit, 0, .full, .dword), + vmexit_reason = encode(.vmexit, 1, .full, .dword), + vmexit_interruption_information = encode(.vmexit, 2, .full, .dword), + vmexit_interruption_error_code = encode(.vmexit, 3, .full, .dword), + idt_vectoring_information = encode(.vmexit, 4, .full, .dword), + idt_vectoring_error_code = encode(.vmexit, 5, .full, .dword), + vmexit_instruction_length = encode(.vmexit, 6, .full, .dword), + vmexit_instruction_information = encode(.vmexit, 7, .full, .dword), + // 64-bit fields. + guest_physical_address = encode(.vmexit, 0, .full, .qword), +}; /// Access type for VMCS fields. /// cf. SDM Vol.3C 25.11.2 @@ -281,13 +319,18 @@ const ComponentEncoding = packed struct(u32) { _reserved2: u17 = 0, }; -fn encode(field_type: FieldType, index: u9, access_type: AccessType, width: Width) ComponentEncoding { - return ComponentEncoding{ +fn encode( + comptime field_type: FieldType, + comptime index: u9, + comptime access_type: AccessType, + comptime width: Width, +) u32 { + return @bitCast(ComponentEncoding{ .access_type = access_type, .index = index, .field_type = field_type, .width = width, - }; + }); } /// VM-Execution Control Fields. @@ -327,11 +370,11 @@ pub const exec_control = struct { pub fn load(self: PinBasedExecutionControl) VmxError!void { const val: u32 = @bitCast(self); - try vmcs.vmwrite(vmcs.control_pinbased_vmexec_controls, val); + try vmcs.vmwrite(vmcs.Ctrl.pinbased_vmexec_controls, val); } pub fn get() VmxError!PinBasedExecutionControl { - const val: u32 = @truncate(try vmcs.vmread(vmcs.control_pinbased_vmexec_controls)); + const val: u32 = @truncate(try vmcs.vmread(vmcs.Ctrl.pinbased_vmexec_controls)); return @bitCast(val); } }; @@ -408,11 +451,11 @@ pub const exec_control = struct { pub fn load(self: PrimaryProcessorBasedExecutionControl) VmxError!void { const val: u32 = @bitCast(self); - try vmcs.vmwrite(vmcs.control_procbased_vmexec_controls, val); + try vmcs.vmwrite(vmcs.Ctrl.procbased_vmexec_controls, val); } pub fn get() VmxError!PrimaryProcessorBasedExecutionControl { - const val: u32 = @truncate(try vmcs.vmread(vmcs.control_procbased_vmexec_controls)); + const val: u32 = @truncate(try vmcs.vmread(vmcs.Ctrl.procbased_vmexec_controls)); return @bitCast(val); } }; @@ -586,11 +629,11 @@ pub const exit_control = struct { pub fn load(self: PrimaryExitControls) VmxError!void { const val: u32 = @bitCast(self); - try vmcs.vmwrite(vmcs.control_primary_vmexit_controls, val); + try vmcs.vmwrite(vmcs.Ctrl.primary_vmexit_controls, val); } pub fn get() VmxError!PrimaryExitControls { - const val: u32 = @truncate(try vmcs.vmread(vmcs.control_primary_vmexit_controls)); + const val: u32 = @truncate(try vmcs.vmread(vmcs.Ctrl.primary_vmexit_controls)); return @bitCast(val); } }; @@ -661,16 +704,44 @@ pub const entry_control = struct { pub fn load(self: EntryControls) VmxError!void { const val: u32 = @bitCast(self); - try vmcs.vmwrite(vmcs.control_vmentry_controls, val); + try vmcs.vmwrite(vmcs.Ctrl.vmentry_controls, val); } pub fn get() VmxError!EntryControls { - const val: u32 = @truncate(try vmcs.vmread(vmcs.control_vmentry_controls)); + const val: u32 = @truncate(try vmcs.vmread(vmcs.Ctrl.vmentry_controls)); return @bitCast(val); } }; }; +/// Segment rights that can be set in guest-state area. +pub const SegmentRights = packed struct(u32) { + const gdt = @import("../gdt.zig"); + + /// Segment type. + type: gdt.SegmentType, + /// Descriptor type. + s: gdt.DescriptorType, + /// DPL. + dpl: u2, + /// Present. + p: bool = true, + /// Reserved. + _reserved1: u4 = 0, + /// AVL. + avl: bool = false, + /// Long mode. + long: bool = false, + /// D/B + db: u1, + /// Granularity. + g: gdt.Granularity, + /// Unusable. + unusable: bool = false, + /// Reserved. + _reserved2: u15 = 0, +}; + test { std.testing.refAllDeclsRecursive(@This()); } diff --git a/ymir/main.zig b/ymir/main.zig index 5c3966a..6d36f34 100644 --- a/ymir/main.zig +++ b/ymir/main.zig @@ -131,9 +131,10 @@ fn kernelMain(boot_info: surtr.BootInfo) !void { try arch.vmx.setupVmcs(ymir.mem.page_allocator); // Launch + log.info("Entering VMX non-root operation...", .{}); arch.vmx.launch() catch |err| switch (err) { error.FailureStatusAvailable => { - log.err("VMLAUNCH failed: error={?}", .{try arch.vmx.getErrorReason()}); + log.err("VMLAUNCH failed: error={?}", .{try arch.vmx.getInstError()}); return err; }, else => return err,