fix: let nested-KVM vmlaunch succeed with Virtual NMIs

Self-test and Linux guest were failing VM-instruction error 7. Share a KVM-style control adjust, request PIN_VIRTUAL_NMIS, and stop intercepting guest #PF. Land isolated XNU/Python/KDE scaffolding off the boot path.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Linehu
2026-09-06 10:48:07 +08:00
co-authored by Cursor
parent cc0fcf26e3
commit 737a2cb0db
17 changed files with 1615 additions and 163 deletions
+135
View File
@@ -0,0 +1,135 @@
#ifndef UTSM_IPC_NATIVE_H
#define UTSM_IPC_NATIVE_H
#include <utsm/types.h>
/*
* Experimental native Deshab capability IPC ABI.
*
* This is intentionally distinct from CODE/utsm-ipc/ipc_proto.h, which is a
* UTSM <-> Linux guest transport. No native queue implementation or syscall
* binding exists yet. All ABI fields use bounded fixed-width Deshab types;
* pointers and implementation-owned kernel addresses never appear on wire.
*/
#define UTSM_IPC_NATIVE_MAGIC 0x44534950u /* "DSIP" */
#define UTSM_IPC_NATIVE_ABI_MAJOR 1u
#define UTSM_IPC_NATIVE_ABI_MINOR 0u
#define UTSM_IPC_NATIVE_MESSAGE_BYTES 256u
#define UTSM_IPC_NATIVE_PAYLOAD_BYTES 144u
#define UTSM_IPC_NATIVE_MAX_CAPS 4u
#define UTSM_IPC_NATIVE_MIN_DEPTH 2u
#define UTSM_IPC_NATIVE_MAX_DEPTH 4096u
#define UTSM_IPC_NATIVE_DEADLINE_NONE (~0ULL)
/*
* The rights field requests or describes attenuation. Authority comes from
* the calling task's kernel capability table; copying or editing this value
* must never create authority.
*/
typedef struct utsm_ipc_native_cap {
u64 object_id;
u32 generation;
u32 rights;
} utsm_ipc_native_cap;
typedef enum utsm_ipc_native_right {
UTSM_IPC_RIGHT_SEND = (1u << 0),
UTSM_IPC_RIGHT_RECEIVE = (1u << 1),
UTSM_IPC_RIGHT_CALL = (1u << 2),
UTSM_IPC_RIGHT_REPLY = (1u << 3),
UTSM_IPC_RIGHT_TRANSFER = (1u << 4),
UTSM_IPC_RIGHT_DUPLICATE = (1u << 5),
UTSM_IPC_RIGHT_MANAGE = (1u << 6),
UTSM_IPC_RIGHT_WAIT = (1u << 7),
UTSM_IPC_RIGHT_SIGNAL = (1u << 8)
} utsm_ipc_native_right;
#define UTSM_IPC_RIGHT_ALL ((u32)((1u << 9) - 1u))
/*
* Queue operations are carried in a u16 ABI field. CALL creates a transaction
* ID; REPLY must name that transaction. WAIT/SIGNAL are queue-state events,
* not substitutes for SEND/RECEIVE.
*/
typedef enum utsm_ipc_native_queue_op {
UTSM_IPC_QUEUE_CREATE = 1,
UTSM_IPC_QUEUE_DESTROY = 2,
UTSM_IPC_QUEUE_SEND = 3,
UTSM_IPC_QUEUE_RECEIVE = 4,
UTSM_IPC_QUEUE_CALL = 5,
UTSM_IPC_QUEUE_REPLY = 6,
UTSM_IPC_QUEUE_WAIT = 7,
UTSM_IPC_QUEUE_SIGNAL = 8,
UTSM_IPC_QUEUE_QUERY = 9
} utsm_ipc_native_queue_op;
#define UTSM_IPC_MSG_F_NONBLOCK (1u << 0)
#define UTSM_IPC_MSG_F_MOVE_CAPS (1u << 1)
#define UTSM_IPC_MSG_F_EXPECT_REPLY (1u << 2)
#define UTSM_IPC_MSG_F_CANCEL (1u << 3)
typedef enum utsm_ipc_native_result {
UTSM_IPC_NATIVE_OK = 0,
UTSM_IPC_NATIVE_ERR_INVALID = -1,
UTSM_IPC_NATIVE_ERR_ACCESS = -2,
UTSM_IPC_NATIVE_ERR_STALE_CAP = -3,
UTSM_IPC_NATIVE_ERR_FULL = -4,
UTSM_IPC_NATIVE_ERR_EMPTY = -5,
UTSM_IPC_NATIVE_ERR_CLOSED = -6,
UTSM_IPC_NATIVE_ERR_TIMEOUT = -7,
UTSM_IPC_NATIVE_ERR_CANCELLED = -8,
UTSM_IPC_NATIVE_ERR_UNSUPPORTED = -38
} utsm_ipc_native_result;
/*
* Fixed 256-byte message. message_size must equal sizeof(this structure),
* payload_length is bounded by PAYLOAD_BYTES, and capability_count is bounded
* by MAX_CAPS. Receivers must reject nonzero reserved fields.
*/
typedef struct utsm_ipc_native_message {
u32 magic;
u16 abi_major;
u16 abi_minor;
u16 operation; /* utsm_ipc_native_queue_op */
u16 flags;
u32 message_size;
u64 transaction_id;
utsm_ipc_native_cap endpoint;
u32 payload_length;
u16 capability_count;
u16 reserved0;
utsm_ipc_native_cap capabilities[UTSM_IPC_NATIVE_MAX_CAPS];
u8 payload[UTSM_IPC_NATIVE_PAYLOAD_BYTES];
} utsm_ipc_native_message;
/*
* Fixed queue-control request for a future syscall dispatcher. depth is valid
* only for CREATE and must stay within MIN_DEPTH..MAX_DEPTH. deadline_ns is
* an absolute monotonic deadline or UTSM_IPC_NATIVE_DEADLINE_NONE.
*/
typedef struct utsm_ipc_native_queue_request {
u16 abi_major;
u16 abi_minor;
u16 operation; /* utsm_ipc_native_queue_op */
u16 flags;
u32 depth;
u32 message_size;
utsm_ipc_native_cap queue;
u64 deadline_ns;
} utsm_ipc_native_queue_request;
_Static_assert(sizeof(utsm_ipc_native_cap) == 16u,
"native IPC capability ABI changed");
_Static_assert(sizeof(utsm_ipc_native_message) ==
UTSM_IPC_NATIVE_MESSAGE_BYTES,
"native IPC message ABI changed");
_Static_assert(sizeof(utsm_ipc_native_queue_request) == 40u,
"native IPC queue request ABI changed");
#endif /* UTSM_IPC_NATIVE_H */
+130
View File
@@ -0,0 +1,130 @@
#ifndef UTSM_SCHED_H
#define UTSM_SCHED_H
#include <utsm/types.h>
/*
* Experimental SAS-R0-PCQ scheduler contract.
*
* This header reserves the first ABI shape for a future per-CPU O(1)
* priority-bitmap scheduler. It is not connected to boot or context-switch
* code. Structures begin with version/size where they may cross component
* boundaries; existing fields must not be reordered and extensions append.
*/
#define UTSM_SCHED_ABI_VERSION 1u
#define UTSM_SCHED_PRIORITY_COUNT 64u
#define UTSM_SCHED_INVALID_SLOT 0xffffffffu
#define UTSM_SCHED_PRIORITY_HIGHEST 0u
#define UTSM_SCHED_PRIORITY_LOWEST (UTSM_SCHED_PRIORITY_COUNT - 1u)
/* Explicit API results. The isolated stub returns UNSUPPORTED for every call. */
typedef enum utsm_sched_result {
UTSM_SCHED_OK = 0,
UTSM_SCHED_ERR_INVALID = -1,
UTSM_SCHED_ERR_STATE = -2,
UTSM_SCHED_ERR_NO_TASK = -3,
UTSM_SCHED_ERR_UNSUPPORTED = -38
} utsm_sched_result;
/* Values are stored in u32 fields so the TCB layout does not depend on enum size. */
typedef enum utsm_sched_task_state {
UTSM_SCHED_TASK_UNUSED = 0,
UTSM_SCHED_TASK_NEW,
UTSM_SCHED_TASK_READY,
UTSM_SCHED_TASK_RUNNING,
UTSM_SCHED_TASK_BLOCKED,
UTSM_SCHED_TASK_SLEEPING,
UTSM_SCHED_TASK_RECOVERING,
UTSM_SCHED_TASK_FAULTED,
UTSM_SCHED_TASK_ZOMBIE
} utsm_sched_task_state;
#define UTSM_SCHED_TASK_F_KERNEL (1u << 0)
#define UTSM_SCHED_TASK_F_IDLE (1u << 1)
#define UTSM_SCHED_TASK_F_NO_MIGRATE (1u << 2)
#define UTSM_SCHED_TASK_F_RECOVERY (1u << 3)
/*
* Minimum x86_64 resume context. Volatile registers belong to the eventual
* interrupt/syscall frame and are deliberately not duplicated here.
*/
typedef struct utsm_sched_arch_context {
u64 rsp;
u64 rip;
u64 rflags;
u64 rbx;
u64 rbp;
u64 r12;
u64 r13;
u64 r14;
u64 r15;
} utsm_sched_arch_context;
/*
* Task control block prefix.
*
* runq_prev/runq_next are task-table slots, not pointers, so queues remain
* relocatable. priority 0 is highest. crypto_context is loaded as a pointer
* during a future switch; switch code must not scan capabilities or perform
* cryptography/checkpoint work.
*/
typedef struct utsm_sched_tcb {
u32 abi_version;
u32 struct_size;
u64 task_id;
u32 process_slot;
u32 flags;
u32 state; /* utsm_sched_task_state */
u32 priority;
u32 assigned_cpu;
u32 last_cpu;
u64 affinity_mask;
u64 timeslice_ns;
u64 wake_deadline_ns;
void *crypto_context;
u32 runq_prev;
u32 runq_next;
utsm_sched_arch_context arch;
u64 reserved[4];
} utsm_sched_tcb;
/*
* One ready bitmap and one intrusive FIFO per priority form the planned O(1)
* run queue. A set bit means that head[priority] is valid. Synchronization is
* an implementation concern and is intentionally not implied by this layout.
*/
typedef struct utsm_sched_run_queue {
u32 abi_version;
u32 struct_size;
u32 cpu_id;
u32 flags;
u64 ready_bitmap;
u32 head[UTSM_SCHED_PRIORITY_COUNT];
u32 tail[UTSM_SCHED_PRIORITY_COUNT];
u32 current_task_slot;
u32 idle_task_slot;
u32 ready_count;
u32 reserved0;
} utsm_sched_run_queue;
int utsm_sched_init(void);
int utsm_sched_cpu_init(u32 cpu_id, utsm_sched_run_queue *queue);
int utsm_sched_task_init(utsm_sched_tcb *task, u64 task_id,
u32 process_slot, u32 priority, u64 affinity_mask);
int utsm_sched_enqueue(u32 cpu_id, utsm_sched_tcb *task);
int utsm_sched_dequeue(utsm_sched_tcb *task);
int utsm_sched_block(utsm_sched_tcb *task, u32 blocked_state);
int utsm_sched_wake(utsm_sched_tcb *task);
int utsm_sched_pick_next(u32 cpu_id, utsm_sched_tcb **out_task);
int utsm_sched_tick(u32 cpu_id, u64 elapsed_ns,
utsm_sched_tcb **out_preempted_task);
#endif /* UTSM_SCHED_H */
+2
View File
@@ -88,5 +88,7 @@ int vmm_self_test(void);
/* VMM 状态查询 */ /* VMM 状态查询 */
int vmm_is_ready(void); int vmm_is_ready(void);
u64 vmm_get_eptp(void); u64 vmm_get_eptp(void);
/* P8.5: VMCS 物理地址(linux_launch 前 VMCLEAR+VMPTRLD 用) */
u64 vmm_get_vmcs_phys(void);
#endif #endif
+35 -19
View File
@@ -9,6 +9,7 @@
#define IA32_VMX_PROCBASED_CTLS 0x482 #define IA32_VMX_PROCBASED_CTLS 0x482
#define IA32_VMX_EXIT_CTLS 0x483 #define IA32_VMX_EXIT_CTLS 0x483
#define IA32_VMX_ENTRY_CTLS 0x484 #define IA32_VMX_ENTRY_CTLS 0x484
#define IA32_VMX_MISC 0x485
#define IA32_VMX_PROCBASED_CTLS2 0x48B #define IA32_VMX_PROCBASED_CTLS2 0x48B
#define IA32_VMX_EPT_VPID_CAP 0x48C #define IA32_VMX_EPT_VPID_CAP 0x48C
#define IA32_VMX_TRUE_PINBASED_CTLS 0x48D #define IA32_VMX_TRUE_PINBASED_CTLS 0x48D
@@ -37,15 +38,9 @@
/* Control fields */ /* Control fields */
#define VMCS_VPID 0x0000 #define VMCS_VPID 0x0000
#define VMCS_GUEST_ES_ADDR 0x0006 /* P8.5: guest interrupt status 真实编码为 0x0810(16-bit guest-state),
#define VMCS_GUEST_CS_ADDR 0x0008 * 旧值 0x0009 是 posted-interrupt 描述符区域,属于错误编码。 */
#define VMCS_GUEST_SS_ADDR 0x000A #define VMCS_GUEST_INTR_STATUS 0x0810
#define VMCS_GUEST_DS_ADDR 0x000C
#define VMCS_GUEST_FS_ADDR 0x000E
#define VMCS_GUEST_GS_ADDR 0x0010
#define VMCS_GUEST_LDTR_ADDR 0x0012
#define VMCS_GUEST_TR_ADDR 0x0014
#define VMCS_GUEST_INTR_STATUS 0x0009
#define VMCS_IO_BITMAP_A 0x2000 #define VMCS_IO_BITMAP_A 0x2000
#define VMCS_IO_BITMAP_B 0x2002 #define VMCS_IO_BITMAP_B 0x2002
#define VMCS_MSR_BITMAP 0x2004 #define VMCS_MSR_BITMAP 0x2004
@@ -54,15 +49,22 @@
/* Read-only data fields */ /* Read-only data fields */
#define VMCS_GUEST_PHYSICAL_ADDR 0x2400 #define VMCS_GUEST_PHYSICAL_ADDR 0x2400
#define VMCS_EXIT_INTR_INFO 0x4404
#define VMCS_EXIT_INTR_ERROR_CODE 0x4406
#define VMCS_EXIT_REASON 0x4402 #define VMCS_EXIT_REASON 0x4402
#define VMCS_EXIT_QUALIFICATION 0x6400 #define VMCS_EXIT_QUALIFICATION 0x6400
#define VMCS_IO_RCX 0x6404 /* P8.5: IO RCX/RSI/RDI/RIP 真实编码为 0x6402/0x6404/0x6406/0x6408,
#define VMCS_IO_RSI 0x6406 * 旧值整体右移了一格。guest-linear-address = 0x640A 不变。 */
#define VMCS_IO_RDI 0x6408 #define VMCS_IO_RCX 0x6402
#define VMCS_IO_RIP 0x640A #define VMCS_IO_RSI 0x6404
#define VMCS_IO_RDI 0x6406
#define VMCS_IO_RIP 0x6408
#define VMCS_GUEST_LINEAR_ADDR 0x640A #define VMCS_GUEST_LINEAR_ADDR 0x640A
#define VMCS_INSTRUCTION_LENGTH 0x440C #define VMCS_INSTRUCTION_LENGTH 0x440C
#define VMCS_VMX_INSTRUCTION_ERROR 0x4406 /* P8.5 关键修复:VM-instruction error 真实编码是 0x4400。
* 旧值 0x4406 是 "VM-exit interruption error code",
* 导致 vmlaunch 失败后错误码永远读出 0。 */
#define VMCS_VMX_INSTRUCTION_ERROR 0x4400
/* Guest-state fields */ /* Guest-state fields */
#define VMCS_GUEST_ES_SELECTOR 0x0800 #define VMCS_GUEST_ES_SELECTOR 0x0800
@@ -82,8 +84,8 @@
#define VMCS_GUEST_LDTR_LIMIT 0x480C #define VMCS_GUEST_LDTR_LIMIT 0x480C
#define VMCS_GUEST_TR_LIMIT 0x480E #define VMCS_GUEST_TR_LIMIT 0x480E
#define VMCS_GUEST_CS_ACCESS 0x4816 #define VMCS_GUEST_CS_ACCESS 0x4816
#define VMCS_GUEST_SS_ACCESS 0x481A #define VMCS_GUEST_SS_ACCESS 0x4818
#define VMCS_GUEST_DS_ACCESS 0x4818 #define VMCS_GUEST_DS_ACCESS 0x481A
#define VMCS_GUEST_ES_ACCESS 0x4814 #define VMCS_GUEST_ES_ACCESS 0x4814
#define VMCS_GUEST_FS_ACCESS 0x481C #define VMCS_GUEST_FS_ACCESS 0x481C
#define VMCS_GUEST_GS_ACCESS 0x481E #define VMCS_GUEST_GS_ACCESS 0x481E
@@ -165,11 +167,13 @@
#define VMCS_VM_EXIT_CONTROLS 0x400C #define VMCS_VM_EXIT_CONTROLS 0x400C
#define VMCS_VM_ENTRY_CONTROLS 0x4012 #define VMCS_VM_ENTRY_CONTROLS 0x4012
#define VMCS_VM_ENTRY_INTERRUPT_INFO 0x4016 #define VMCS_VM_ENTRY_INTERRUPT_INFO 0x4016
#define VMCS_VM_ENTRY_EXCEPTION_ERROR 0x4018
#define VMCS_SECONDARY_VM_EXEC_CONTROL 0x401E #define VMCS_SECONDARY_VM_EXEC_CONTROL 0x401E
/* ===== Pin-based controls ===== */ /* ===== Pin-based controls ===== */
#define PIN_EXT_INTERRUPT_EXITING (1ULL << 0) #define PIN_EXT_INTERRUPT_EXITING (1ULL << 0)
#define PIN_NMI_EXITING (1ULL << 3) #define PIN_NMI_EXITING (1ULL << 3)
#define PIN_VIRTUAL_NMIS (1ULL << 5)
#define PIN_VMX_PREEMPTION_TIMER (1ULL << 6) #define PIN_VMX_PREEMPTION_TIMER (1ULL << 6)
/* ===== Primary processor-based controls ===== */ /* ===== Primary processor-based controls ===== */
@@ -269,11 +273,23 @@ int vmx_vmcs_load(u64 phys);
int vmx_vmcs_clear(u64 phys); /* P8.4: VMCLEAR before VMLAUNCH */ int vmx_vmcs_clear(u64 phys); /* P8.4: VMCLEAR before VMLAUNCH */
u64 vmx_vmcs_read(u64 field); u64 vmx_vmcs_read(u64 field);
void vmx_vmcs_write(u64 field, u64 value); void vmx_vmcs_write(u64 field, u64 value);
/* P8.5: 带状态返回的 VMREAD。
* 返回 0 成功(CF=0 且 ZF=0);-1 = VMfailValid/VMfailInvalid
* (此时 *value 未更新,多为 current-VMCS 无效)。 */
int vmx_vmcs_read_checked(u64 field, u64 *value);
int vmx_vmlaunch(void); int vmx_vmlaunch(void);
int vmx_vmresume(void); int vmx_vmresume(void);
u64 vmx_read_msr(u32 msr); u64 vmx_read_msr(u32 msr);
/* Capability polarity used by KVM and verified under nested KVM:
* low bit=1 => must be 1; high bit=0 => must be 0.
* Formula: (desired | allowed0) & allowed1. */
u64 vmx_adjust_control(u64 value, u32 msr);
u64 vmx_get_host_cr3(void); u64 vmx_get_host_cr3(void);
/* Convert a pointer inside the linked UTSM kernel image to its physical
* address using Limine's kernel-address response. Kernel-image mappings are
* distinct from the HHDM and must never be translated by subtracting HHDM. */
u64 vmx_kernel_virt_to_phys(const void *address);
/* ===== 虚拟中断注入(vmexit.c 实现) ===== */ /* ===== 虚拟中断注入(vmexit.c 实现) ===== */
/* 向 guest 队列注入一个 ISA IRQ 向量(legacy PIC:vector = 0x30 + irq)。 /* 向 guest 队列注入一个 ISA IRQ 向量(legacy PIC:vector = 0x30 + irq)。
@@ -281,9 +297,9 @@ u64 vmx_get_host_cr3(void);
* 否则 arm interrupt-window exiting,待 guest 可接收时注入。 */ * 否则 arm interrupt-window exiting,待 guest 可接收时注入。 */
void vmx_guest_queue_irq(u32 vector); void vmx_guest_queue_irq(u32 vector);
/* VMX preemption timer:返回 1ms 对应的 timer 计数值(按 IA32_VMX_BASIC /* VMX preemption timer:返回 host polling 周期对应的 timer 计数值。
* bits[55:48] 的 scale 换算 TSC 频率)。 */ * 当前周期为 10ms,避免 nested KVM 在 1ms VM-exit 下失去前进性。 */
u64 vmx_preemption_quantum_1ms(void); u64 vmx_preemption_quantum(void);
/* 检查 CPU 是否支持 VMX preemption timer。 */ /* 检查 CPU 是否支持 VMX preemption timer。 */
int vmx_preemption_timer_supported(void); int vmx_preemption_timer_supported(void);
+65
View File
@@ -0,0 +1,65 @@
/*
* Isolated scheduler placeholder.
*
* Deliberately not linked by the UTSM build. These functions make accidental
* use fail explicitly until the per-CPU bitmap scheduler and switch path are
* implemented.
*/
#include <utsm/sched.h>
int utsm_sched_init(void) {
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_cpu_init(u32 cpu_id, utsm_sched_run_queue *queue) {
UTSM_UNUSED(cpu_id);
UTSM_UNUSED(queue);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_task_init(utsm_sched_tcb *task, u64 task_id,
u32 process_slot, u32 priority, u64 affinity_mask) {
UTSM_UNUSED(task);
UTSM_UNUSED(task_id);
UTSM_UNUSED(process_slot);
UTSM_UNUSED(priority);
UTSM_UNUSED(affinity_mask);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_enqueue(u32 cpu_id, utsm_sched_tcb *task) {
UTSM_UNUSED(cpu_id);
UTSM_UNUSED(task);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_dequeue(utsm_sched_tcb *task) {
UTSM_UNUSED(task);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_block(utsm_sched_tcb *task, u32 blocked_state) {
UTSM_UNUSED(task);
UTSM_UNUSED(blocked_state);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_wake(utsm_sched_tcb *task) {
UTSM_UNUSED(task);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_pick_next(u32 cpu_id, utsm_sched_tcb **out_task) {
UTSM_UNUSED(cpu_id);
UTSM_UNUSED(out_task);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
int utsm_sched_tick(u32 cpu_id, u64 elapsed_ns,
utsm_sched_tcb **out_preempted_task) {
UTSM_UNUSED(cpu_id);
UTSM_UNUSED(elapsed_ns);
UTSM_UNUSED(out_preempted_task);
return UTSM_SCHED_ERR_UNSUPPORTED;
}
+10 -8
View File
@@ -76,8 +76,9 @@ static u64 *ept_walk(u64 gpa, int alloc_missing) {
if (!alloc_missing) return (u64 *)0; if (!alloc_missing) return (u64 *)0;
u64 new_phys = ept_alloc_page(); u64 new_phys = ept_alloc_page();
if (new_phys == 0) return (u64 *)0; if (new_phys == 0) return (u64 *)0;
/* 中间页表项:RWX + WB 内存类型 */ /* 非叶 EPT 表项只含物理地址与 RWX。bits 5:3 在非叶项中
table[indices[level]] = new_phys | EPT_RWX | (EPT_MEMORY_TYPE_WB << 3); * 保留,写入 leaf memory type 会触发 EPT misconfiguration。 */
table[indices[level]] = new_phys | EPT_RWX;
/* 切换到新分配的下一级页表 */ /* 切换到新分配的下一级页表 */
table = (u64 *)phys_to_virt(new_phys); table = (u64 *)phys_to_virt(new_phys);
} else { } else {
@@ -145,7 +146,7 @@ int ept_map_2m_page(u64 gpa, u64 hpa, u64 flags, u64 memtype) {
if (!(entry & EPT_READ)) { if (!(entry & EPT_READ)) {
u64 new_phys = ept_alloc_page(); u64 new_phys = ept_alloc_page();
if (new_phys == 0) return -3; if (new_phys == 0) return -3;
table[pml4_idx] = new_phys | EPT_RWX | (EPT_MEMORY_TYPE_WB << 3); table[pml4_idx] = new_phys | EPT_RWX;
table = (u64 *)phys_to_virt(new_phys); table = (u64 *)phys_to_virt(new_phys);
} else { } else {
table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL); table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL);
@@ -156,7 +157,7 @@ int ept_map_2m_page(u64 gpa, u64 hpa, u64 flags, u64 memtype) {
if (!(entry & EPT_READ)) { if (!(entry & EPT_READ)) {
u64 new_phys = ept_alloc_page(); u64 new_phys = ept_alloc_page();
if (new_phys == 0) return -4; if (new_phys == 0) return -4;
table[pdpt_idx] = new_phys | EPT_RWX | (EPT_MEMORY_TYPE_WB << 3); table[pdpt_idx] = new_phys | EPT_RWX;
table = (u64 *)phys_to_virt(new_phys); table = (u64 *)phys_to_virt(new_phys);
} else { } else {
table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL); table = (u64 *)phys_to_virt(entry & 0x000FFFFFFFFFF000ULL);
@@ -210,7 +211,7 @@ void ept_flush_ept(void) {
int err; int err;
__asm__ volatile( __asm__ volatile(
"invept (%2), %1\n" "invept (%2), %1\n"
"jnc 1f\n" "ja 1f\n"
"mov $1, %0\n" "mov $1, %0\n"
"jmp 2f\n" "jmp 2f\n"
"1:\n" "1:\n"
@@ -234,7 +235,7 @@ void ept_flush_vpid(u16 vpid) {
int err; int err;
__asm__ volatile( __asm__ volatile(
"invvpid (%2), %1\n" "invvpid (%2), %1\n"
"jnc 1f\n" "ja 1f\n"
"mov $1, %0\n" "mov $1, %0\n"
"jmp 2f\n" "jmp 2f\n"
"1:\n" "1:\n"
@@ -296,7 +297,8 @@ u64 ept_gpa_to_hpa(u64 gpa) {
u64 ept_get_eptp(void) { u64 ept_get_eptp(void) {
if (!g_ept_ready) return 0; if (!g_ept_ready) return 0;
/* EPTP: bits [5:0] = memtype, bits [7:6] = walk length-1 (3 for 4-level), /* EPTP: bits [2:0]=memory type WB(6), bits [5:3]=walk length-1 (3 => 4-level).
* bits [51:12] = PML4 HPA */ * Nested KVM accepted WB (eptp=...1e) on the passing self-test; forcing UC
* after a misread of EPT_VPID_CAP bit 8 regressed to error 7. */
return g_ept_pml4_phys | EPTP_WALK_LEN_4 | EPTP_MEMTYPE_WB; return g_ept_pml4_phys | EPTP_WALK_LEN_4 | EPTP_MEMTYPE_WB;
} }
+146 -38
View File
@@ -17,6 +17,7 @@
#include <utsm/ept.h> #include <utsm/ept.h>
#include <utsm/vmm.h> #include <utsm/vmm.h>
#include <utsm/virtio_mmio.h> #include <utsm/virtio_mmio.h>
#include <utsm/ipc_shm.h>
#include <utsm/log.h> #include <utsm/log.h>
#include <utsm/types.h> #include <utsm/types.h>
#include "../arch/x86_64/limine.h" #include "../arch/x86_64/limine.h"
@@ -50,14 +51,6 @@ static inline u64 read_cr4_local(void) {
return v; return v;
} }
static u64 vmx_adjust_control(u64 value, u32 msr) {
u32 allowed0 = (u32)vmx_read_msr(msr);
u32 allowed1 = (u32)(vmx_read_msr(msr) >> 32);
u32 adjusted = (u32)value | allowed0;
adjusted &= allowed1;
return adjusted;
}
/* MSR bitmap (4KB, all zeros = don't intercept any MSR) */ /* MSR bitmap (4KB, all zeros = don't intercept any MSR) */
static u8 g_linux_msr_bitmap[4096] __attribute__((aligned(4096))); static u8 g_linux_msr_bitmap[4096] __attribute__((aligned(4096)));
@@ -125,11 +118,13 @@ static void linux_vmcs_setup_host_state(void) {
/* ===== VMCS guest state for Linux ===== */ /* ===== VMCS guest state for Linux ===== */
static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) { static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) {
/* CR0/CR3/CR4: long mode with paging */ /* CR0/CR3/CR4: long mode with paging.
* P8.5: CR4 必须含 VMXE(IA32_VMX_CR4_FIXED0=0x2000 强制,
* 旧代码漏掉会触发 VM-entry invalid guest state)。 */
vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP); vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP);
/* CR3 points to guest PML4 (GPA) */ /* CR3 points to guest PML4 (GPA) */
vmx_vmcs_write(VMCS_GUEST_CR3, gi->pgt_gpa); vmx_vmcs_write(VMCS_GUEST_CR3, gi->pgt_gpa);
vmx_vmcs_write(VMCS_GUEST_CR4, CR4_PAE | CR4_PGE | CR4_PSE); vmx_vmcs_write(VMCS_GUEST_CR4, CR4_VMXE | CR4_PAE | CR4_PGE | CR4_PSE);
/* Segment selectors from guest GDT */ /* Segment selectors from guest GDT */
vmx_vmcs_write(VMCS_GUEST_CS_SELECTOR, 0x08); vmx_vmcs_write(VMCS_GUEST_CS_SELECTOR, 0x08);
@@ -165,14 +160,18 @@ static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) {
vmx_vmcs_write(VMCS_GUEST_GDTR_LIMIT, 0x28); vmx_vmcs_write(VMCS_GUEST_GDTR_LIMIT, 0x28);
vmx_vmcs_write(VMCS_GUEST_IDTR_LIMIT, 0xFFFF); vmx_vmcs_write(VMCS_GUEST_IDTR_LIMIT, 0xFFFF);
/* Access rights */ /* Access rights.
* P8.5: selector=0 的段必须标记 unusable(access rights bit 16),
* 否则 VM-entry guest-state 检查失败:
* FS/GS selector=0 → 0x1C093(unusable)
* LDTR selector=0 → 0x10082(unusable,旧值 0x82 缺 bit 16) */
vmx_vmcs_write(VMCS_GUEST_CS_ACCESS, 0xA09B); /* 64-bit code, L=1 */ vmx_vmcs_write(VMCS_GUEST_CS_ACCESS, 0xA09B); /* 64-bit code, L=1 */
vmx_vmcs_write(VMCS_GUEST_SS_ACCESS, 0xC093); vmx_vmcs_write(VMCS_GUEST_SS_ACCESS, 0xC093);
vmx_vmcs_write(VMCS_GUEST_DS_ACCESS, 0xC093); vmx_vmcs_write(VMCS_GUEST_DS_ACCESS, 0xC093);
vmx_vmcs_write(VMCS_GUEST_ES_ACCESS, 0xC093); vmx_vmcs_write(VMCS_GUEST_ES_ACCESS, 0xC093);
vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0xC093); vmx_vmcs_write(VMCS_GUEST_FS_ACCESS, 0x1C093); /* unusable (selector=0) */
vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0xC093); vmx_vmcs_write(VMCS_GUEST_GS_ACCESS, 0x1C093); /* unusable (selector=0) */
vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x82); /* unusable */ vmx_vmcs_write(VMCS_GUEST_LDTR_ACCESS, 0x10082); /* unusable (selector=0) */
vmx_vmcs_write(VMCS_GUEST_TR_ACCESS, 0x8B); /* busy 64-bit TSS */ vmx_vmcs_write(VMCS_GUEST_TR_ACCESS, 0x8B); /* busy 64-bit TSS */
/* Guest RIP = Linux 64-bit entry (startup_64) */ /* Guest RIP = Linux 64-bit entry (startup_64) */
@@ -204,10 +203,22 @@ static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) {
/* ===== VMCS controls for Linux ===== */ /* ===== VMCS controls for Linux ===== */
static void linux_vmcs_setup_controls(u64 eptp) { static void linux_vmcs_setup_controls(u64 eptp) {
log_hex64("[LINUX] TRUE_PINBASED_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_PINBASED_CTLS));
log_hex64("[LINUX] TRUE_PROCBASED_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_PROCBASED_CTLS));
log_hex64("[LINUX] PROCBASED_CTLS2 raw=",
vmx_read_msr(IA32_VMX_PROCBASED_CTLS2));
log_hex64("[LINUX] TRUE_EXIT_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_EXIT_CTLS));
log_hex64("[LINUX] TRUE_ENTRY_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_ENTRY_CTLS));
/* Pin-based:外部中断 exit + NMI exit + VMX preemption timer(若支持)。 /* Pin-based:外部中断 exit + NMI exit + VMX preemption timer(若支持)。
* preemption timer 提供 1ms 周期 exit,用于轮询 host 串口 RX、 * preemption timer 提供 1ms 周期 exit,用于轮询 host 串口 RX、
* 推进 guest PIT tick、注入 pending virtio/COM1 IRQ。 */ * 推进 guest PIT tick、注入 pending virtio/COM1 IRQ。 */
u64 pin_want = PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING; u64 pin_want = PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING |
PIN_VIRTUAL_NMIS;
if (vmx_preemption_timer_supported()) { if (vmx_preemption_timer_supported()) {
pin_want |= PIN_VMX_PREEMPTION_TIMER; pin_want |= PIN_VMX_PREEMPTION_TIMER;
} }
@@ -222,13 +233,12 @@ static void linux_vmcs_setup_controls(u64 eptp) {
| CPU_BASED_INVLPG_EXITING; | CPU_BASED_INVLPG_EXITING;
cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS); cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
/* Secondary: EPT + VPID */ /* Secondary: EPT + VPID(经 capability MSR 调整;
* EPT 不可用则打日志——Linux guest 的 GPA 布局依赖 EPT)。 */
u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID; u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID;
{ cpu2 = vmx_adjust_control(cpu2, IA32_VMX_PROCBASED_CTLS2);
u64 msr = vmx_read_msr(IA32_VMX_PROCBASED_CTLS2); if (!(cpu2 & SEC_EXEC_ENABLE_EPT)) {
u32 allowed1 = (u32)(msr >> 32); log_warn("[LINUX] EPT not available (shadow paging) - guest GPA map disabled");
cpu2 &= allowed1;
cpu2 |= SEC_EXEC_ENABLE_EPT;
} }
u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS
@@ -249,20 +259,31 @@ static void linux_vmcs_setup_controls(u64 eptp) {
/* MSR bitmap */ /* MSR bitmap */
{ {
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0; u64 msr_bitmap_phys = vmx_kernel_virt_to_phys(g_linux_msr_bitmap);
u64 msr_bitmap_phys = (u64)g_linux_msr_bitmap - hhdm; log_hex64("[LINUX] MSR bitmap phys=", msr_bitmap_phys);
vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys); vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys);
} }
if (cpu2 & SEC_EXEC_ENABLE_EPT) {
vmx_vmcs_write(VMCS_EPT_POINTER, eptp); vmx_vmcs_write(VMCS_EPT_POINTER, eptp);
} else {
vmx_vmcs_write(VMCS_EPT_POINTER, 0);
}
if (cpu2 & SEC_EXEC_ENABLE_VPID) {
vmx_vmcs_write(VMCS_VPID, 2); /* VPID 1 = self-test, VPID 2 = Linux */ vmx_vmcs_write(VMCS_VPID, 2); /* VPID 1 = self-test, VPID 2 = Linux */
} else {
vmx_vmcs_write(VMCS_VPID, 0);
}
/* Exception bitmap: catch #GP, #PF, #UD, #DF (triple fault handled separately) */ /* Linux owns its IDT. Intercepting #PF here livelocks: the guest
vmx_vmcs_write(VMCS_EXCEPTION_BITMAP, (1ULL << 13) | (1ULL << 14) | (1ULL << 6) | (1ULL << 8)); * page-fault handler never runs, so early mm faults spin in VM-exit. */
vmx_vmcs_write(VMCS_EXCEPTION_BITMAP, 0);
} }
/* ===== Global for passing RSI to launch asm ===== */ /* ===== Global for passing RSI to launch asm ===== */
static volatile u64 g_linux_rsi; static volatile u64 g_linux_rsi;
static volatile int g_linux_launch_failed;
static volatile u64 g_linux_launch_flags;
/* ===== Linux launch ===== /* ===== Linux launch =====
* *
@@ -306,17 +327,44 @@ int linux_launch(void) {
linux_vmcs_setup_controls(vmm_get_eptp()); linux_vmcs_setup_controls(vmm_get_eptp());
linux_vmcs_setup_host_state(); linux_vmcs_setup_host_state();
/* 初始 arm preemption timer(1ms 后首次周期 exit) */ /* 仅在 pin-based control 真正启用 timer 时 arm。 */
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms()); if (vmx_vmcs_read(VMCS_PIN_BASED_VM_EXEC_CONTROL) &
PIN_VMX_PREEMPTION_TIMER) {
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER,
vmx_preemption_quantum());
} else {
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, 0);
}
/* Set RSI for Linux boot_params (passed via global, loaded in asm) */ /* Set RSI for Linux boot_params (passed via global, loaded in asm) */
g_linux_rsi = gi->bootparams_gpa; g_linux_rsi = gi->bootparams_gpa;
/* P8.5: VMCLEAR + VMPTRLD —— VMLAUNCH 要求 VMCS 处于 "clear" 态。
* 自检成功后 VMCS 状态为 "launched",不 VMCLEAR 会报
* "VMLAUNCH with non-clear VMCS"(error 4)。
* 内容不受影响:VMCLEAR 只回写内存并改状态,VMPTRLD 再挂载。 */
if (vmx_vmcs_clear(vmm_get_vmcs_phys()) != 0) {
log_error("[LINUX] vmclear failed");
return -4;
}
if (vmx_vmcs_load(vmm_get_vmcs_phys()) != 0) {
log_error("[LINUX] vmptrld after vmclear failed");
return -5;
}
log_hex64("[LINUX] RIP=", gi->kernel_entry); log_hex64("[LINUX] RIP=", gi->kernel_entry);
log_hex64("[LINUX] RSP=", LINUX_GUEST_STACK_GPA + 0x1000); log_hex64("[LINUX] RSP=", LINUX_GUEST_STACK_GPA + 0x1000);
log_hex64("[LINUX] RSI=", gi->bootparams_gpa); log_hex64("[LINUX] RSI=", gi->bootparams_gpa);
log_hex64("[LINUX] CR3=", gi->pgt_gpa); log_hex64("[LINUX] CR3=", gi->pgt_gpa);
log_hex64("[LINUX] GDTR=", gi->gdt_gpa); log_hex64("[LINUX] GDTR=", gi->gdt_gpa);
log_hex64("[LINUX] pin=", vmx_vmcs_read(VMCS_PIN_BASED_VM_EXEC_CONTROL));
log_hex64("[LINUX] cpu=", vmx_vmcs_read(VMCS_CPU_BASED_VM_EXEC_CONTROL));
log_hex64("[LINUX] cpu2=", vmx_vmcs_read(VMCS_SECONDARY_VM_EXEC_CONTROL));
log_hex64("[LINUX] exit=", vmx_vmcs_read(VMCS_VM_EXIT_CONTROLS));
log_hex64("[LINUX] entry=", vmx_vmcs_read(VMCS_VM_ENTRY_CONTROLS));
log_hex64("[LINUX] eptp=", vmx_vmcs_read(VMCS_EPT_POINTER));
log_hex64("[LINUX] msr_bitmap=", vmx_vmcs_read(VMCS_MSR_BITMAP));
log_hex64("[LINUX] vpid=", vmx_vmcs_read(VMCS_VPID));
log_info("[LINUX] vmlaunch"); log_info("[LINUX] vmlaunch");
/* vmlaunch with RSI = boot_params. /* vmlaunch with RSI = boot_params.
@@ -324,38 +372,98 @@ int linux_launch(void) {
* - On failure: failed=1, falls through * - On failure: failed=1, falls through
* - On success: enters guest, VM-Exit → vmx_vm_exit_handler * - On success: enters guest, VM-Exit → vmx_vm_exit_handler
* - On terminate: handler restores RSP, jmps to post_guest label */ * - On terminate: handler restores RSP, jmps to post_guest label */
int failed; g_linux_launch_failed = 1;
g_linux_launch_flags = 0;
__asm__ volatile( __asm__ volatile(
"movq %%rsp, g_saved_host_rsp(%%rip)\n\t" "movq %%rsp, g_saved_host_rsp(%%rip)\n\t"
"leaq 1f(%%rip), %%rax\n\t" "leaq 1f(%%rip), %%rax\n\t"
"movq %%rax, g_saved_return_rip(%%rip)\n\t" "movq %%rax, g_saved_return_rip(%%rip)\n\t"
/* Linux 64-bit boot protocol requires every GPR except RSI zero. */
"xorl %%eax, %%eax\n\t"
"xorl %%ebx, %%ebx\n\t"
"xorl %%ecx, %%ecx\n\t"
"xorl %%edx, %%edx\n\t"
"xorl %%edi, %%edi\n\t"
"xorl %%ebp, %%ebp\n\t"
"xorl %%r8d, %%r8d\n\t"
"xorl %%r9d, %%r9d\n\t"
"xorl %%r10d, %%r10d\n\t"
"xorl %%r11d, %%r11d\n\t"
"xorl %%r12d, %%r12d\n\t"
"xorl %%r13d, %%r13d\n\t"
"xorl %%r14d, %%r14d\n\t"
"xorl %%r15d, %%r15d\n\t"
"movq g_linux_rsi(%%rip), %%rsi\n\t" /* RSI = boot_params GPA */ "movq g_linux_rsi(%%rip), %%rsi\n\t" /* RSI = boot_params GPA */
"vmlaunch\n\t" "vmlaunch\n\t"
/* ---- failure path ---- */ /* ---- failure path ---- */
"movl $1, %0\n\t" "movl $1, g_linux_launch_failed(%%rip)\n\t"
"pushfq\n\t"
"popq g_linux_launch_flags(%%rip)\n\t"
"jmp 2f\n\t" "jmp 2f\n\t"
/* ---- post_guest: terminate path jmps here ---- */ /* ---- post_guest: terminate path jmps here ---- */
"1:\n\t" "1:\n\t"
"movl $0, %0\n\t" "movl $0, g_linux_launch_failed(%%rip)\n\t"
"2:\n\t" "2:\n\t"
: "=r"(failed) :
:: "rax", "rcx", "rdx", "rsi", "rdi", :
"r8", "r9", "r10", "r11", "memory" : "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp",
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
"cc", "memory"
); );
int failed = g_linux_launch_failed;
u64 vm_flags = g_linux_launch_flags;
if (failed) { if (failed) {
u64 error = vmx_vmcs_read(VMCS_VMX_INSTRUCTION_ERROR); /* CF=1 → VMfailInvalid;ZF=1 → VMfailValid(error field 有效)。 */
if (vm_flags & 0x1) {
log_error("[LINUX] vmlaunch VMfailInvalid (invalid current-VMCS)");
} else if (vm_flags & 0x40) {
log_error("[LINUX] vmlaunch VMfailValid");
}
u64 error = 0;
if (vm_flags & 0x40) {
vmx_vmcs_read_checked(VMCS_VMX_INSTRUCTION_ERROR, &error);
}
log_hex64("[LINUX] vmlaunch failed, error=", error); log_hex64("[LINUX] vmlaunch failed, error=", error);
g_linux_guest_active = 0; g_linux_guest_active = 0;
return -3; return -3;
} }
/* Guest parked (HLT) — Linux daemon 已驻留,等待 linux_resume() 唤醒 */ /* Only handle_hlt() may mark the Linux guest parked. A fatal exception,
g_linux_guest_active = 0; /* 回到 host 上下文,清除 active 标志 */ * triple fault, or EPT failure also returns through post_guest, but must
g_guest_parked = 1; /* 标记 guest 已 park,linux_resume() 可唤醒 */ * not make the compatibility layer report a ready daemon. */
log_info("[LINUX] guest parked (daemon ready)"); g_linux_guest_active = 0;
log_hex64("[LINUX] last exit reason=", g_last_exit_reason); log_hex64("[LINUX] last exit reason=", g_last_exit_reason);
log_hex64("[LINUX] vmexit count=", vmexit_get_count()); log_hex64("[LINUX] vmexit count=", vmexit_get_count());
if (!g_guest_parked) {
log_error("[LINUX] guest terminated before daemon park");
return -6;
}
/* A kernel idle/fatal HLT is indistinguishable from the daemon's PARK at
* the VM-exit reason level. The daemon must first publish EXEC_READY
* through /dev/utsm; this proves the driver PING, SHM mapping, userspace
* daemon, and Linux→UTSM ring are all alive. */
{
struct utsm_ipc_msg ready;
int got_ready = 0;
for (int i = 0; i < 8; i++) {
if (ipc_shm_recv(&ready) != 0) break;
if (ready.type == UTSM_MSG_EXEC_READY) {
got_ready = 1;
break;
}
log_hex64("[LINUX] pre-ready message type=", ready.type);
}
if (!got_ready) {
g_guest_parked = 0;
log_error("[LINUX] HLT without EXEC_READY handshake");
return -7;
}
}
log_info("[LINUX] EXEC_READY received from /dev/utsm");
log_info("[LINUX] guest parked (daemon ready)");
return 0; return 0;
} }
+122 -47
View File
@@ -8,26 +8,36 @@
extern volatile struct limine_hhdm_request g_hhdm_request; extern volatile struct limine_hhdm_request g_hhdm_request;
/* ===== VMX 指令错误码 ===== */ /* ===== VMX 指令错误码(Intel SDM Vol 3D, "VM Instruction Error Numbers") ===== */
static const char *vmx_error_str(u64 err) { static const char *vmx_error_str(u64 err) {
switch (err) { switch (err) {
case 1: return "VMCALL/VMCLEAR/VMLAUNCH/VMRESUME with non-launched VMCS"; case 1: return "VMCALL executed in VMX root operation";
case 2: return "VMRESUME after VMXOFF"; case 2: return "VMCLEAR with invalid physical address";
case 3: return "VMRESUME with corrupted VMCS"; case 3: return "VMCLEAR with VMXON pointer";
case 4: return "vmlaunch with non-clear VMCS"; case 4: return "VMLAUNCH with non-clear VMCS";
case 5: return "VM operation invalid"; case 5: return "VMRESUME with non-launched VMCS";
case 6: return "VMCS revision mismatch"; case 6: return "VMRESUME after VMXOFF";
case 7: return "VMCS shadowing mismatch"; case 7: return "VM entry with invalid VM-execution control fields";
case 8: return "invalid guest state"; case 8: return "VM entry with invalid host-state fields";
case 9: return "host state invalid"; case 9: return "VMPTRLD with invalid physical address";
case 10: return "VM-execution control invalid"; case 10: return "VMPTRLD with VMXON pointer";
case 11: return "VM-exit control invalid"; case 11: return "VMPTRLD with incorrect VMCS revision identifier";
case 12: return "VM-entry control invalid"; case 12: return "VMREAD from unsupported VMCS component";
case 13: return "VM-exit control fields invalid"; case 13: return "VMWRITE to read-only VMCS component";
case 15: return "address out of width"; case 15: return "VM entry with invalid guest-state fields";
case 16: return "MSR bitmap addr invalid"; case 16: return "VM entry with invalid executive-VMCS pointer";
case 17: return "VMREAD/VMWRITE from unsupported field"; case 17: return "VM entry with non-launched executive VMCS";
case 18: return "VMCS addr invalid"; case 18: return "VM entry with executive-VMCS inconsistency";
case 19: return "VM entry with invalid VM-exit control fields";
case 20: return "VM entry with invalid MSEG VMCS revision";
case 22: return "VMCALL with non-clear VMCS";
case 23: return "VMCALL with invalid VM-exit control fields";
case 24: return "VMCALL with incorrect MSEG VMCS revision";
case 25: return "VM switch with invalid VM-exit control fields";
case 26: return "VM switch with invalid VM-exit MSEG VMCS revision";
case 28: return "VMRETURN with non-launched VMCS";
case 29: return "VMRETURN with invalid executive VMCS";
case 30: return "VM entry with invalid VM-execution control fields in event injection";
default: return "unknown"; default: return "unknown";
} }
} }
@@ -78,6 +88,17 @@ static u8 g_guest_code[4096] __attribute__((aligned(4096))) = {
/* guest 栈页 */ /* guest 栈页 */
static u8 g_guest_stack[4096] __attribute__((aligned(4096))); static u8 g_guest_stack[4096] __attribute__((aligned(4096)));
/* P8.5: guest 自有页表(EPT 模式下 guest CR3 不能指向 host 页表——
* host 页表页未在 EPT 映射,页表遍历本身就会 EPT violation)。
* 布局(GPA):
* 0x6000 PML4 [0] -> 0x7000 | P|RW
* 0x7000 PDPT [0] -> 0x9000 | P|RW
* 0x9000 PD [0] -> 2MB 大页 | P|RW|PS (GPA 0..2MB 恒等映射)
* guest 代码/栈/GDT/TSS/IDT 页全部落在低 2MB 内。 */
static u64 g_guest_pml4[512] __attribute__((aligned(4096)));
static u64 g_guest_pdpt[512] __attribute__((aligned(4096)));
static u64 g_guest_pd[512] __attribute__((aligned(4096)));
/* MSR bitmap(4KB 全 0 = 不拦截 MSR) */ /* MSR bitmap(4KB 全 0 = 不拦截 MSR) */
static u8 g_msr_bitmap[4096] __attribute__((aligned(4096))); static u8 g_msr_bitmap[4096] __attribute__((aligned(4096)));
@@ -162,14 +183,6 @@ static inline u64 read_cr3_local(void) {
/* ===== VMCS 配置 ===== */ /* ===== VMCS 配置 ===== */
static u64 vmx_adjust_control(u64 value, u32 msr) {
u32 allowed0 = (u32)vmx_read_msr(msr);
u32 allowed1 = (u32)(vmx_read_msr(msr) >> 32);
u32 adjusted = (u32)value | allowed0;
adjusted &= allowed1;
return adjusted;
}
static void vmcs_setup_host_state(void) { static void vmcs_setup_host_state(void) {
vmx_vmcs_write(VMCS_HOST_CR0, read_cr0_local() | CR0_PE | CR0_NE | CR0_PG); vmx_vmcs_write(VMCS_HOST_CR0, read_cr0_local() | CR0_PE | CR0_NE | CR0_PG);
vmx_vmcs_write(VMCS_HOST_CR3, read_cr3_local()); vmx_vmcs_write(VMCS_HOST_CR3, read_cr3_local());
@@ -235,10 +248,11 @@ static void vmcs_setup_host_state(void) {
} }
static void vmcs_setup_guest_state_phase1(void) { static void vmcs_setup_guest_state_phase1(void) {
/* Guest CR0/CR3/CR4:启用 PE/NE/PG/PAE,复用 host 页表。 /* Guest CR0/CR3/CR4:启用 PE/NE/PG/PAE。
* P8.4: CR4 must include VMXE (CR4_FIXED0 requires it under KVM). */ * P8.5: CR3 指向 guest 自有页表(GPA 0x6000),
* 不再借用 host CR3(EPT 下 host 页表页未映射,遍历会失败)。 */
vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP); vmx_vmcs_write(VMCS_GUEST_CR0, CR0_PE | CR0_NE | CR0_PG | CR0_WP);
vmx_vmcs_write(VMCS_GUEST_CR3, read_cr3_local()); vmx_vmcs_write(VMCS_GUEST_CR3, 0x6000);
vmx_vmcs_write(VMCS_GUEST_CR4, CR4_VMXE | CR4_PAE | CR4_PGE | CR4_PSE); vmx_vmcs_write(VMCS_GUEST_CR4, CR4_VMXE | CR4_PAE | CR4_PGE | CR4_PSE);
/* 段选择子:flat 模型。 /* 段选择子:flat 模型。
@@ -324,7 +338,19 @@ static void vmcs_setup_guest_state_phase1(void) {
} }
static void vmcs_setup_controls(u64 eptp) { static void vmcs_setup_controls(u64 eptp) {
u64 pin = vmx_adjust_control(PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING, log_hex64("[VMM] TRUE_PINBASED_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_PINBASED_CTLS));
log_hex64("[VMM] TRUE_PROCBASED_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_PROCBASED_CTLS));
log_hex64("[VMM] PROCBASED_CTLS2 raw=",
vmx_read_msr(IA32_VMX_PROCBASED_CTLS2));
log_hex64("[VMM] TRUE_EXIT_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_EXIT_CTLS));
log_hex64("[VMM] TRUE_ENTRY_CTLS raw=",
vmx_read_msr(IA32_VMX_TRUE_ENTRY_CTLS));
u64 pin = vmx_adjust_control(PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING |
PIN_VIRTUAL_NMIS,
IA32_VMX_TRUE_PINBASED_CTLS); IA32_VMX_TRUE_PINBASED_CTLS);
u64 cpu = CPU_BASED_HLT_EXITING u64 cpu = CPU_BASED_HLT_EXITING
@@ -334,11 +360,12 @@ static void vmcs_setup_controls(u64 eptp) {
| CPU_BASED_INVLPG_EXITING; | CPU_BASED_INVLPG_EXITING;
cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS); cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
/* P8.4d: Try without EPT first to isolate nested VMX issue under KVM. /* P8.5: 请求 EPT(KVM 报告的 allowed-1 集合含 EPT 位)。
* If KVM requires EPT, vmx_adjust_control will OR it back in. */ * 自检 guest 用 GPA 0x1000..0x9000 + 自有页表,必须开 EPT
u64 cpu2 = 0; /* no EPT, no VPID */ * 才能让 ept_map_range 的映射真正生效。
* EPT 不可用时 cpu2 会失去 EPT 位并打日志,自检将失败。 */
u64 cpu2 = SEC_EXEC_ENABLE_EPT;
cpu2 = vmx_adjust_control(cpu2, IA32_VMX_PROCBASED_CTLS2); cpu2 = vmx_adjust_control(cpu2, IA32_VMX_PROCBASED_CTLS2);
log_hex64("[VMM] IA32_VMX_PROCBASED_CTLS2 raw=", vmx_read_msr(IA32_VMX_PROCBASED_CTLS2));
u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS
| VM_EXIT_HOST_ADDR_SPACE_SIZE | VM_EXIT_HOST_ADDR_SPACE_SIZE
@@ -358,8 +385,8 @@ static void vmcs_setup_controls(u64 eptp) {
/* MSR bitmap 物理地址 */ /* MSR bitmap 物理地址 */
{ {
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0; u64 msr_bitmap_phys = vmx_kernel_virt_to_phys(g_msr_bitmap);
u64 msr_bitmap_phys = (u64)g_msr_bitmap - hhdm; log_hex64("[VMM] MSR bitmap phys=", msr_bitmap_phys);
vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys); vmx_vmcs_write(VMCS_MSR_BITMAP, msr_bitmap_phys);
} }
@@ -371,6 +398,8 @@ static void vmcs_setup_controls(u64 eptp) {
} }
if (cpu2 & SEC_EXEC_ENABLE_VPID) { if (cpu2 & SEC_EXEC_ENABLE_VPID) {
vmx_vmcs_write(VMCS_VPID, 1); vmx_vmcs_write(VMCS_VPID, 1);
} else {
vmx_vmcs_write(VMCS_VPID, 0);
} }
/* Exception bitmap:捕获 #GP(13)/#PF(14)/#UD(6) */ /* Exception bitmap:捕获 #GP(13)/#PF(14)/#UD(6) */
@@ -430,6 +459,9 @@ void vmm_shutdown(void) {
int vmm_is_ready(void) { return g_vmm_ready; } int vmm_is_ready(void) { return g_vmm_ready; }
u64 vmm_get_eptp(void) { return ept_get_eptp(); } u64 vmm_get_eptp(void) { return ept_get_eptp(); }
/* P8.5: 暴露 VMCS 物理地址,供 linux_launch() 在 vmlaunch 前
* 执行 VMCLEAR+VMPTRLD(VMCS 状态机:VMLAUNCH 要求 clear 态)。 */
u64 vmm_get_vmcs_phys(void) { return g_vmcs_phys; }
/* P8.4: Dump all guest-state VMCS fields for KVM nested VMX debugging. /* P8.4: Dump all guest-state VMCS fields for KVM nested VMX debugging.
* Called before VMLAUNCH to identify field encoding/value errors. */ * Called before VMLAUNCH to identify field encoding/value errors. */
@@ -532,12 +564,12 @@ int vmm_self_test(void) {
/* EPT 映射 guest 代码页(GPA 0x1000)、栈页(GPA 0x8000)、 /* EPT 映射 guest 代码页(GPA 0x1000)、栈页(GPA 0x8000)、
* GDT 页(GPA 0x3000)、TSS 页(GPA 0x4000)。 * GDT 页(GPA 0x3000)、TSS 页(GPA 0x4000)。
* g_guest_* 是 UTSM 虚拟地址,物理地址 = virt - HHDM */ * g_guest_* 属于高半 UTSM 内核映像,必须使用 Limine 提供的
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0; * kernel virtual/physical base 转换;它们不是 HHDM 指针。 */
u64 guest_code_phys = (u64)g_guest_code - hhdm; u64 guest_code_phys = vmx_kernel_virt_to_phys(g_guest_code);
u64 guest_stack_phys = (u64)g_guest_stack - hhdm; u64 guest_stack_phys = vmx_kernel_virt_to_phys(g_guest_stack);
u64 guest_gdt_phys = (u64)g_guest_gdt - hhdm; u64 guest_gdt_phys = vmx_kernel_virt_to_phys(g_guest_gdt);
u64 guest_tss_phys = (u64)g_guest_tss - hhdm; u64 guest_tss_phys = vmx_kernel_virt_to_phys(g_guest_tss);
if (ept_map_range(0x1000, guest_code_phys, 4096, EPT_RWX) != 0) { if (ept_map_range(0x1000, guest_code_phys, 4096, EPT_RWX) != 0) {
log_error("[VMM] map guest code failed"); log_error("[VMM] map guest code failed");
@@ -557,7 +589,7 @@ int vmm_self_test(void) {
} }
/* P8.4: Map IDT page for KVM nested VMX (IDTR base = 0x5000) */ /* P8.4: Map IDT page for KVM nested VMX (IDTR base = 0x5000) */
{ {
u64 guest_idt_phys = (u64)g_guest_idt - hhdm; u64 guest_idt_phys = vmx_kernel_virt_to_phys(g_guest_idt);
for (int i = 0; i < 4096; i++) g_guest_idt[i] = 0; for (int i = 0; i < 4096; i++) g_guest_idt[i] = 0;
if (ept_map_range(0x5000, guest_idt_phys, 4096, EPT_READ | EPT_WRITE) != 0) { if (ept_map_range(0x5000, guest_idt_phys, 4096, EPT_READ | EPT_WRITE) != 0) {
log_error("[VMM] map guest IDT failed"); log_error("[VMM] map guest IDT failed");
@@ -565,6 +597,29 @@ int vmm_self_test(void) {
} }
} }
/* P8.5: 构建 guest 自有页表(GPA 低 2MB 恒等,2MB 大页)并 EPT 映射。
* 页表项:P(bit0)|RW(bit1),大页加 PS(bit7);NX=0(可执行)。 */
{
for (int i = 0; i < 512; i++) {
g_guest_pml4[i] = 0;
g_guest_pdpt[i] = 0;
g_guest_pd[i] = 0;
}
g_guest_pml4[0] = 0x7000 | 0x3; /* P|RW -> PDPT @ GPA 0x7000 */
g_guest_pdpt[0] = 0x9000 | 0x3; /* P|RW -> PD @ GPA 0x9000 */
g_guest_pd[0] = 0x0000 | 0x83; /* P|RW|PS -> 2MB page @ GPA 0 */
u64 pml4_phys = vmx_kernel_virt_to_phys(g_guest_pml4);
u64 pdpt_phys = vmx_kernel_virt_to_phys(g_guest_pdpt);
u64 pd_phys = vmx_kernel_virt_to_phys(g_guest_pd);
if (ept_map_range(0x6000, pml4_phys, 4096, EPT_READ | EPT_WRITE) != 0 ||
ept_map_range(0x7000, pdpt_phys, 4096, EPT_READ | EPT_WRITE) != 0 ||
ept_map_range(0x9000, pd_phys, 4096, EPT_READ | EPT_WRITE) != 0) {
log_error("[VMM] map guest page tables failed");
return -12;
}
}
/* 配置 VMCS */ /* 配置 VMCS */
vmcs_setup_guest_state_phase1(); vmcs_setup_guest_state_phase1();
vmcs_setup_controls(ept_get_eptp()); vmcs_setup_controls(ept_get_eptp());
@@ -590,36 +645,56 @@ int vmm_self_test(void) {
log_hex64("[VMM] exit=", vmx_vmcs_read(VMCS_VM_EXIT_CONTROLS)); log_hex64("[VMM] exit=", vmx_vmcs_read(VMCS_VM_EXIT_CONTROLS));
log_hex64("[VMM] entry=", vmx_vmcs_read(VMCS_VM_ENTRY_CONTROLS)); log_hex64("[VMM] entry=", vmx_vmcs_read(VMCS_VM_ENTRY_CONTROLS));
log_hex64("[VMM] eptp=", vmx_vmcs_read(VMCS_EPT_POINTER)); log_hex64("[VMM] eptp=", vmx_vmcs_read(VMCS_EPT_POINTER));
log_hex64("[VMM] msr_bitmap=", vmx_vmcs_read(VMCS_MSR_BITMAP));
log_hex64("[VMM] vpid=", vmx_vmcs_read(VMCS_VPID));
log_hex64("[VMM] link=", vmx_vmcs_read(VMCS_GUEST_LINK_POINTER)); log_hex64("[VMM] link=", vmx_vmcs_read(VMCS_GUEST_LINK_POINTER));
/* P8.4: Dump all guest-state fields for KVM debug */ /* P8.4: Dump all guest-state fields for KVM debug */
vmm_dump_guest_state(); vmm_dump_guest_state();
/* vmlaunch 控制流: /* vmlaunch 控制流:
* - 失败:执行下一条指令,failed=1 * - 失败:执行下一条指令,failed=1(同时捕获 RFLAGS 区分
* VMfailValid CF=1 / VMfailInvalid ZF=1)
* - 成功:进入 guest,不返回。VM-Exit 后跳到 vmx_vm_exit_handler。 * - 成功:进入 guest,不返回。VM-Exit 后跳到 vmx_vm_exit_handler。
* - guest 终止时,handler 恢复 saved_rsp 并 jmp 到 post_guest 标签。 * - guest 终止时,handler 恢复 saved_rsp 并 jmp 到 post_guest 标签。
* *
* 我们在 vmlaunch 前保存 RSP 与"vmlaunch 后"的 RIP(= post_guest 标签地址)。 */ * 我们在 vmlaunch 前保存 RSP 与"vmlaunch 后"的 RIP(= post_guest 标签地址)。 */
int failed; int failed;
u64 vm_flags = 0;
__asm__ volatile( __asm__ volatile(
"movq %%rsp, g_saved_host_rsp(%%rip)\n\t" /* 保存当前 RSP */ "movq %%rsp, g_saved_host_rsp(%%rip)\n\t" /* 保存当前 RSP */
"leaq 1f(%%rip), %%rax\n\t" /* 取 post_guest 标签地址 */ "leaq 1f(%%rip), %%rax\n\t" /* 取 post_guest 标签地址 */
"movq %%rax, g_saved_return_rip(%%rip)\n\t" /* 保存返回 RIP */ "movq %%rax, g_saved_return_rip(%%rip)\n\t" /* 保存返回 RIP */
"vmlaunch\n\t" /* 启动 guest */ "vmlaunch\n\t" /* 启动 guest */
/* ---- 失败路径 ---- */ /* ---- 失败路径(mov 不改 flags,先存 failed 再抓 RFLAGS) ---- */
"movl $1, %0\n\t" "movl $1, %0\n\t"
"pushfq\n\t"
"popq %1\n\t"
"jmp 2f\n\t" "jmp 2f\n\t"
/* ---- post_guest 标签:终止路径 jmp 到这里 ---- */ /* ---- post_guest 标签:终止路径 jmp 到这里 ---- */
"1:\n\t" "1:\n\t"
"movl $0, %0\n\t" "movl $0, %0\n\t"
"2:\n\t" "2:\n\t"
: "=r"(failed) : "=r"(failed), "=r"(vm_flags)
:: "rax", "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "memory" :: "rax", "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "memory"
); );
if (failed) { if (failed) {
u64 error = vmx_vmcs_read(VMCS_VMX_INSTRUCTION_ERROR); /* CF(bit0)=1 → VMfailInvalid(current-VMCS 无效);
* ZF(bit6)=1 → VMfailValid(错误码在 VMCS 0x4400)。 */
if (vm_flags & 0x1) {
log_error("[VMM] vmlaunch VMfailInvalid (invalid current-VMCS)");
} else if (vm_flags & 0x40) {
log_error("[VMM] vmlaunch VMfailValid");
} else {
log_error("[VMM] vmlaunch failed (unexpected flags)");
}
u64 error = 0;
if (!(vm_flags & 0x40)) {
log_error("[VMM] VM-instruction error unavailable");
} else if (vmx_vmcs_read_checked(VMCS_VMX_INSTRUCTION_ERROR, &error) != 0) {
log_error("[VMM] VMREAD error field failed (current-VMCS invalid)");
}
log_hex64("[VMM] vmlaunch failed, error=", error); log_hex64("[VMM] vmlaunch failed, error=", error);
serial_write("[VMM] "); serial_write("[VMM] ");
serial_write(vmx_error_str(error)); serial_write(vmx_error_str(error));
+60 -5
View File
@@ -6,6 +6,7 @@
#include "../arch/x86_64/limine.h" #include "../arch/x86_64/limine.h"
extern volatile struct limine_hhdm_request g_hhdm_request; extern volatile struct limine_hhdm_request g_hhdm_request;
extern volatile struct limine_kernel_address_request g_kernel_address_request;
/* ===== MSR / CR helpers ===== */ /* ===== MSR / CR helpers ===== */
@@ -15,6 +16,41 @@ u64 vmx_read_msr(u32 msr) {
return ((u64)hi << 32) | lo; return ((u64)hi << 32) | lo;
} }
u64 vmx_adjust_control(u64 value, u32 msr) {
u64 raw = vmx_read_msr(msr);
u32 allowed0 = (u32)raw;
u32 allowed1 = (u32)(raw >> 32);
/* KVM / nested-KVM polarity (verified 2026-09-06):
* low bit=1 => must be 1
* high bit=0 => must be 0
* Secondary CTLS2 reports allowed0=0; inverting low would force every
* allowed1 feature on and fail VM-entry check 7. */
u32 adjusted = ((u32)value | allowed0) & allowed1;
log_hex64("[VMX] adjust msr=", msr);
log_hex64("[VMX] adjust desired=", value);
log_hex64("[VMX] adjust allowed0=", allowed0);
log_hex64("[VMX] adjust allowed1=", allowed1);
log_hex64("[VMX] adjust result=", adjusted);
return adjusted;
}
u64 vmx_kernel_virt_to_phys(const void *address) {
const struct limine_kernel_address_response *response =
g_kernel_address_request.response;
u64 virt = (u64)address;
if (!response) {
log_error("[VMX] Limine kernel-address response missing");
return 0;
}
if (virt < response->virtual_base) {
log_error("[VMX] address is below kernel virtual base");
return 0;
}
return response->physical_base + (virt - response->virtual_base);
}
static __attribute__((unused)) void vmx_write_msr(u32 msr, u64 value) { static __attribute__((unused)) void vmx_write_msr(u32 msr, u64 value) {
u32 lo = (u32)(value & 0xFFFFFFFFULL); u32 lo = (u32)(value & 0xFFFFFFFFULL);
u32 hi = (u32)(value >> 32); u32 hi = (u32)(value >> 32);
@@ -176,6 +212,8 @@ static int vmx_truly_available(void) {
return 0; return 0;
} }
/* P8.5: VMX 指令成功条件是 CF=0 且 ZF=0(即 ja)。
* 旧代码只查 CF(jnc):VMfailInvalid(ZF=1, CF=0)会被误判为成功。 */
int vmx_enable(void) { int vmx_enable(void) {
if (g_vmx_enabled) return 0; if (g_vmx_enabled) return 0;
@@ -221,7 +259,7 @@ int vmx_enable(void) {
int err; int err;
__asm__ volatile( __asm__ volatile(
"vmxon %1\n" "vmxon %1\n"
"jnc 1f\n" "ja 1f\n"
"jmp 2f\n" "jmp 2f\n"
"1:\n" "1:\n"
"mov $0, %0\n" "mov $0, %0\n"
@@ -276,7 +314,7 @@ int vmx_vmcs_load(u64 phys) {
int err; int err;
__asm__ volatile( __asm__ volatile(
"vmptrld %1\n" "vmptrld %1\n"
"jnc 1f\n" "ja 1f\n"
"jmp 2f\n" "jmp 2f\n"
"1:\n" "1:\n"
"mov $0, %0\n" "mov $0, %0\n"
@@ -300,7 +338,7 @@ int vmx_vmcs_clear(u64 phys) {
int err; int err;
__asm__ volatile( __asm__ volatile(
"vmclear %1\n" "vmclear %1\n"
"jnc 1f\n" "ja 1f\n"
"jmp 2f\n" "jmp 2f\n"
"1:\n" "1:\n"
"mov $0, %0\n" "mov $0, %0\n"
@@ -330,6 +368,23 @@ u64 vmx_vmcs_read(u64 field) {
return value; return value;
} }
int vmx_vmcs_read_checked(u64 field, u64 *value) {
u64 v;
u8 ok;
__asm__ volatile(
"vmread %2, %1\n"
"seta %0\n" /* CF=0 且 ZF=0 才是 VMsucceed */
: "=q"(ok), "=r"(v)
: "r"(field)
: "memory"
);
if (ok) {
if (value) *value = v;
return 0;
}
return -1;
}
void vmx_vmcs_write(u64 field, u64 value) { void vmx_vmcs_write(u64 field, u64 value) {
__asm__ volatile( __asm__ volatile(
"vmwrite %1, %0\n" "vmwrite %1, %0\n"
@@ -342,7 +397,7 @@ int vmx_vmlaunch(void) {
int err; int err;
__asm__ volatile( __asm__ volatile(
"vmlaunch\n" "vmlaunch\n"
"jnc 1f\n" "ja 1f\n"
"jmp 2f\n" "jmp 2f\n"
"1:\n" "1:\n"
"mov $0, %0\n" "mov $0, %0\n"
@@ -360,7 +415,7 @@ int vmx_vmresume(void) {
int err; int err;
__asm__ volatile( __asm__ volatile(
"vmresume\n" "vmresume\n"
"jnc 1f\n" "ja 1f\n"
"jmp 2f\n" "jmp 2f\n"
"1:\n" "1:\n"
"mov $0, %0\n" "mov $0, %0\n"
+6 -6
View File
@@ -48,7 +48,7 @@ static inline u64 read_cr4_local(void) {
return v; return v;
} }
static u64 vmx_adjust_control(u64 value, u32 msr) { static u64 xj380_adjust_control(u64 value, u32 msr) {
u32 allowed0 = (u32)vmx_read_msr(msr); u32 allowed0 = (u32)vmx_read_msr(msr);
u32 allowed1 = (u32)(vmx_read_msr(msr) >> 32); u32 allowed1 = (u32)(vmx_read_msr(msr) >> 32);
u32 adjusted = (u32)value | allowed0; u32 adjusted = (u32)value | allowed0;
@@ -186,14 +186,14 @@ static void xj380_vmcs_setup_controls(u64 eptp) {
if (vmx_preemption_timer_supported()) { if (vmx_preemption_timer_supported()) {
pin_want |= PIN_VMX_PREEMPTION_TIMER; pin_want |= PIN_VMX_PREEMPTION_TIMER;
} }
u64 pin = vmx_adjust_control(pin_want, IA32_VMX_TRUE_PINBASED_CTLS); u64 pin = xj380_adjust_control(pin_want, IA32_VMX_TRUE_PINBASED_CTLS);
u64 cpu = CPU_BASED_HLT_EXITING u64 cpu = CPU_BASED_HLT_EXITING
| CPU_BASED_ACTIVATE_SECONDARY | CPU_BASED_ACTIVATE_SECONDARY
| CPU_BASED_USE_MSR_BITMAPS | CPU_BASED_USE_MSR_BITMAPS
| CPU_BASED_UNCOND_IO_EXITING | CPU_BASED_UNCOND_IO_EXITING
| CPU_BASED_INVLPG_EXITING; | CPU_BASED_INVLPG_EXITING;
cpu = vmx_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS); cpu = xj380_adjust_control(cpu, IA32_VMX_TRUE_PROCBASED_CTLS);
u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID; u64 cpu2 = SEC_EXEC_ENABLE_EPT | SEC_EXEC_ENABLE_VPID;
{ {
@@ -206,12 +206,12 @@ static void xj380_vmcs_setup_controls(u64 eptp) {
u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS u64 exit_ctrl = VM_EXIT_SAVE_DEBUG_CONTROLS
| VM_EXIT_HOST_ADDR_SPACE_SIZE | VM_EXIT_HOST_ADDR_SPACE_SIZE
| VM_EXIT_LOAD_HOST_EFER; | VM_EXIT_LOAD_HOST_EFER;
exit_ctrl = vmx_adjust_control(exit_ctrl, IA32_VMX_TRUE_EXIT_CTLS); exit_ctrl = xj380_adjust_control(exit_ctrl, IA32_VMX_TRUE_EXIT_CTLS);
u64 entry_ctrl = VM_ENTRY_LOAD_DEBUG_CONTROLS u64 entry_ctrl = VM_ENTRY_LOAD_DEBUG_CONTROLS
| VM_ENTRY_IA32E_MODE_GUEST | VM_ENTRY_IA32E_MODE_GUEST
| VM_ENTRY_LOAD_GUEST_EFER; | VM_ENTRY_LOAD_GUEST_EFER;
entry_ctrl = vmx_adjust_control(entry_ctrl, IA32_VMX_TRUE_ENTRY_CTLS); entry_ctrl = xj380_adjust_control(entry_ctrl, IA32_VMX_TRUE_ENTRY_CTLS);
vmx_vmcs_write(VMCS_PIN_BASED_VM_EXEC_CONTROL, pin); vmx_vmcs_write(VMCS_PIN_BASED_VM_EXEC_CONTROL, pin);
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL, cpu); vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL, cpu);
@@ -270,7 +270,7 @@ int xj380_launch(void) {
xj380_vmcs_setup_host_state(); xj380_vmcs_setup_host_state();
/* 初始 arm preemption timer(1ms 后首次周期 exit) */ /* 初始 arm preemption timer(1ms 后首次周期 exit) */
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms()); vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum());
/* KernelMain 3 个 SysV 参数(HHDM 虚拟地址) */ /* KernelMain 3 个 SysV 参数(HHDM 虚拟地址) */
g_xj380_rdi = XJ380_FBC_GPA + XJ380_HHDM_OFFSET; g_xj380_rdi = XJ380_FBC_GPA + XJ380_HHDM_OFFSET;
+104
View File
@@ -0,0 +1,104 @@
# KDE Plasma guest rootfs
`build_extra_rootfs.sh` has two modes:
- Default (`KDE_FULL=0`): build the existing 1 GiB persistence volume.
- Opt-in (`KDE_FULL=1`): use Arch `pacstrap` to build a complete KDE Plasma
environment inside `linux-extra-rootfs.img`.
No KDE packages are downloaded unless `KDE_FULL=1` is explicitly set.
## Build
Run the full build from an Arch Linux environment with
`arch-install-scripts`, working pacman mirrors, and a valid keyring:
```bash
sudo pacman -S --needed arch-install-scripts
sudo KDE_FULL=1 IMG_SIZE_MB=8192 \
bash CODE/linux/build_extra_rootfs.sh
```
The default size for `KDE_FULL=1` is 8192 MiB. The complete
`kde-applications-meta` suite can grow over time, so a larger value such as
12288 MiB may be needed. Values below 8192 MiB are allowed but produce a
warning.
Useful build settings:
```text
IMG_SIZE_MB=12288 image capacity in MiB
EXTRA_ROOTFS_WORK=/native/fs native-Linux work directory
KDE_PACSTRAP=/usr/bin/pacstrap
KDE_PACMAN_CONF=/path/to/pacman.conf
KDE_USER=deshab
KDE_UID=1000
KDE_EXTRA_PACKAGES="pkg1 pkg2"
```
`KDE_PACMAN_CONF` can point at a project-controlled mirror/snapshot
configuration when package-version reproducibility is required. Without it,
the build intentionally follows the Arch host's current repositories and
keyring.
The package set includes `plasma-meta`, the full
`kde-applications-meta`, `plasma-workspace`, SDDM, Wayland, XWayland,
Mesa/LLVM llvmpipe, Vulkan lavapipe (`vulkan-swrast`), D-Bus, systemd user
units, input/seat support, portals, and Noto CJK/emoji fonts. Current Arch
ships the Wayland session in `plasma-workspace`; the obsolete
`plasma-wayland-session` package is intentionally not requested.
## Storage and attachment
The output remains:
```text
SYSTEM/boot/linux-extra-rootfs.img
```
That path is the existing build staging convention, not a claim that the full
image fits in the boot partition. A full KDE image must **not** be copied into
the 2 GiB ESP or enabled as a large Limine boot module. It must later be backed
by external storage and attached to the Linux guest through the existing
virtio-blk extra-rootfs path (normally `/dev/vdc`). Transporting that large
backing image is separate from this preparation script.
Allocate at least 4 GiB of guest RAM for Plasma; 6-8 GiB is preferable when
running the complete application suite.
## Starting the session
The current guest mounts the extra volume at `/mnt/persist` while retaining
its minimal read-only root. Start the prepared chroot from the guest console:
```bash
sudo /mnt/persist/usr/local/bin/deshab-kde-session
```
The launcher:
- bind-mounts `/dev`, `/proc`, `/sys`, and `/run` into the prepared root;
- creates the user's `XDG_RUNTIME_DIR` with safe ownership and permissions;
- starts a private D-Bus session using Plasma's current Arch wrapper;
- starts `startplasma-wayland` as the unprivileged `deshab` user;
- defaults to Mesa llvmpipe/lavapipe software rendering; and
- returns to the existing guest console on failure or logout.
Logs are written to:
```text
/mnt/persist/var/log/deshab-kde-session.log
```
Software rendering can be disabled for a working accelerated virtual GPU:
```bash
sudo DESHAB_KDE_SOFTWARE_RENDERING=0 \
/mnt/persist/usr/local/bin/deshab-kde-session
```
The image contains SDDM and systemd user-session support, but neither replaces
the current custom guest PID 1. The prepared user selects Plasma's script-based
startup fallback for that environment. SDDM is deliberately not enabled, so a
broken graphical session cannot remove the guest console or Deshab native
desktop fallback.
+344 -38
View File
@@ -1,80 +1,386 @@
#!/bin/bash #!/bin/bash
# #
# Build the persistent rw ext4 volume for the UTSM Linux guest (VSCode Phase 4). # Build the persistent rw ext4 volume for the UTSM Linux guest.
# #
# Produces a sparse-friendly ext4 image exposed to the guest as a WRITABLE # The default path creates the existing lightweight persistence volume.
# virtio-blk device (/dev/vdc, slot6 @ GPA 0xF4006000 IRQ11) by the UTSM # KDE_FULL=1 additionally pacstraps a self-contained Arch/KDE tree into the
# extra_rootfs backend. The Arch /sbin/init mounts it at /mnt/persist and # volume. The running guest keeps its current read-only root and can enter the
# bind-mounts its subtrees over /opt, /home and /root, giving VSCode and # KDE tree with /mnt/persist/usr/local/bin/deshab-kde-session. This keeps the
# user data a persistent home across the read-only rootfs. # native desktop and the guest console available as emergency fallbacks.
# #
# Output: # Output:
# SYSTEM/boot/linux-extra-rootfs.img (ext4 image, default 1024MB) # SYSTEM/boot/linux-extra-rootfs.img
#
# After generating, uncomment the two extra-rootfs module lines in
# SYSTEM/limine/limine.conf so Limine preloads it as a boot module.
#
# Prerequisites:
# - WSL or native Linux with root access
# - mkfs.ext4, losetup, mount (or debugfs for unprivileged population)
# #
# Usage: # Usage:
# sudo bash CODE/linux/build_extra_rootfs.sh # 1GB default # sudo bash CODE/linux/build_extra_rootfs.sh
# sudo IMG_SIZE_MB=2048 bash CODE/linux/build_extra_rootfs.sh # sudo IMG_SIZE_MB=2048 bash CODE/linux/build_extra_rootfs.sh
# sudo KDE_FULL=1 IMG_SIZE_MB=8192 bash CODE/linux/build_extra_rootfs.sh
#
# KDE_FULL=1 prerequisites:
# - An Arch Linux build environment with arch-install-scripts (pacstrap)
# - Working pacman mirrors and keyring
# - Enough free space for the full KDE Applications suite
set -e set -Eeuo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
OUTPUT_DIR="$PROJECT_ROOT/SYSTEM/boot" OUTPUT_DIR="$PROJECT_ROOT/SYSTEM/boot"
OUTPUT_IMG="$OUTPUT_DIR/linux-extra-rootfs.img" OUTPUT_IMG="$OUTPUT_DIR/linux-extra-rootfs.img"
IMG_SIZE_MB=${IMG_SIZE_MB:-1024}
KDE_FULL="${KDE_FULL:-0}"
case "$KDE_FULL" in
0|1) ;;
*)
echo "[extra-rootfs] ERROR: KDE_FULL must be 0 or 1" >&2
exit 2
;;
esac
if [ -z "${IMG_SIZE_MB+x}" ]; then
if [ "$KDE_FULL" -eq 1 ]; then
IMG_SIZE_MB=8192
else
IMG_SIZE_MB=1024
fi
fi
if ! [[ "$IMG_SIZE_MB" =~ ^[1-9][0-9]*$ ]]; then
echo "[extra-rootfs] ERROR: IMG_SIZE_MB must be a positive integer" >&2
exit 2
fi
if [ "$KDE_FULL" -eq 1 ] && [ "$IMG_SIZE_MB" -lt 8192 ]; then
echo "[extra-rootfs] WARNING: full KDE is expected to need at least 8192MB" >&2
fi
WORK_DIR="${EXTRA_ROOTFS_WORK:-/root/extra_rootfs_work}" WORK_DIR="${EXTRA_ROOTFS_WORK:-/root/extra_rootfs_work}"
MNT_DIR="$WORK_DIR/mnt" MNT_DIR="$WORK_DIR/mnt"
NATIVE_IMG="$WORK_DIR/extra-rootfs.img"
KDE_PACSTRAP="${KDE_PACSTRAP:-pacstrap}"
KDE_PACMAN_CONF="${KDE_PACMAN_CONF:-}"
KDE_USER="${KDE_USER:-deshab}"
KDE_UID="${KDE_UID:-1000}"
if ! [[ "$KDE_USER" =~ ^[a-z_][a-z0-9_-]*$ ]]; then
echo "[extra-rootfs] ERROR: KDE_USER is not a valid user name" >&2
exit 2
fi
if ! [[ "$KDE_UID" =~ ^[1-9][0-9]*$ ]]; then
echo "[extra-rootfs] ERROR: KDE_UID must be a positive integer" >&2
exit 2
fi
# Explicit groups make review and future package-name updates straightforward.
# plasma-workspace now owns the Wayland session and replaces the obsolete
# plasma-wayland-session package on current Arch Linux.
KDE_DESKTOP_PACKAGES=(
plasma-meta
kde-applications-meta
plasma-workspace
sddm
)
KDE_WAYLAND_PACKAGES=(
wayland
wayland-protocols
xorg-xwayland
xdg-desktop-portal
xdg-desktop-portal-kde
libinput
seatd
)
KDE_GRAPHICS_PACKAGES=(
mesa
mesa-utils
libdrm
libglvnd
llvm-libs
vulkan-icd-loader
vulkan-swrast
)
KDE_SESSION_PACKAGES=(
dbus
dbus-broker
systemd
systemd-libs
polkit
pam
)
KDE_FONT_PACKAGES=(
noto-fonts
noto-fonts-cjk
noto-fonts-emoji
ttf-dejavu
)
KDE_BASE_PACKAGES=(
base
bash
coreutils
shadow
util-linux
)
KDE_PACKAGES=(
"${KDE_BASE_PACKAGES[@]}"
"${KDE_DESKTOP_PACKAGES[@]}"
"${KDE_WAYLAND_PACKAGES[@]}"
"${KDE_GRAPHICS_PACKAGES[@]}"
"${KDE_SESSION_PACKAGES[@]}"
"${KDE_FONT_PACKAGES[@]}"
)
if [ -n "${KDE_EXTRA_PACKAGES:-}" ]; then
read -r -a KDE_EXTRA_PACKAGE_ARRAY <<< "$KDE_EXTRA_PACKAGES"
KDE_PACKAGES+=("${KDE_EXTRA_PACKAGE_ARRAY[@]}")
fi
echo "[extra-rootfs] Building persistent rw volume" echo "[extra-rootfs] Building persistent rw volume"
echo "[extra-rootfs] Work dir: $WORK_DIR (native FS)" echo "[extra-rootfs] Work dir: $WORK_DIR (native FS)"
echo "[extra-rootfs] Output: $OUTPUT_IMG (${IMG_SIZE_MB}MB)" echo "[extra-rootfs] Output: $OUTPUT_IMG (${IMG_SIZE_MB}MB)"
echo "[extra-rootfs] KDE full: $KDE_FULL"
mkdir -p "$WORK_DIR" "$MNT_DIR" "$OUTPUT_DIR"
if [ "$(id -u)" -ne 0 ]; then if [ "$(id -u)" -ne 0 ]; then
echo "[extra-rootfs] ERROR: must run as root (loop mount required)" >&2 echo "[extra-rootfs] ERROR: must run as root (loop mount required)" >&2
exit 1 exit 1
fi fi
if [ "$KDE_FULL" -eq 1 ] && ! command -v "$KDE_PACSTRAP" >/dev/null 2>&1; then
echo "[extra-rootfs] ERROR: KDE_FULL=1 requires pacstrap" >&2
echo "[extra-rootfs] Install arch-install-scripts in an Arch build environment," >&2
echo "[extra-rootfs] or set KDE_PACSTRAP to its absolute path." >&2
exit 1
fi
if [ -n "$KDE_PACMAN_CONF" ] && [ ! -r "$KDE_PACMAN_CONF" ]; then
echo "[extra-rootfs] ERROR: KDE_PACMAN_CONF is not readable: $KDE_PACMAN_CONF" >&2
exit 1
fi
# 1. Allocate the image file (seek-truncate: sparse on supporting filesystems) mkdir -p "$WORK_DIR" "$MNT_DIR" "$OUTPUT_DIR"
LOOP_DEV=""
MOUNTED=0
cleanup() {
set +e
if [ "$MOUNTED" -eq 1 ]; then
umount -R "$MNT_DIR" 2>/dev/null || umount "$MNT_DIR" 2>/dev/null || true
fi
if [ -n "$LOOP_DEV" ]; then
losetup -d "$LOOP_DEV" 2>/dev/null || true
fi
}
trap cleanup EXIT INT TERM
# 1. Allocate a sparse-friendly image and create its ext4 filesystem.
echo "[extra-rootfs] Allocating ${IMG_SIZE_MB}MB image..." echo "[extra-rootfs] Allocating ${IMG_SIZE_MB}MB image..."
dd if=/dev/zero of="$WORK_DIR/extra-rootfs.img" bs=1M count=0 seek="$IMG_SIZE_MB" status=none rm -f "$NATIVE_IMG"
dd if=/dev/zero of="$NATIVE_IMG" bs=1M count=0 seek="$IMG_SIZE_MB" status=none
# 2. Create the ext4 filesystem.
# -N 1048576 caps inode count (1M inodes is plenty for VSCode + user data
# and keeps the inode table small); -O ^has_journal skips the journal —
# the volume is memory-backed (writes are volatile until Phase 7 writeback),
# so journal overhead buys nothing.
echo "[extra-rootfs] mkfs.ext4..." echo "[extra-rootfs] mkfs.ext4..."
mkfs.ext4 -q -F -N 1048576 -O ^has_journal -L deshab-persist "$WORK_DIR/extra-rootfs.img" mkfs.ext4 -q -F -N 1048576 -O ^has_journal -L deshab-persist "$NATIVE_IMG"
# 3. Populate the base directory skeleton via loop mount. # 2. Mount and populate the lightweight persistence skeleton.
echo "[extra-rootfs] Populating directory skeleton..." echo "[extra-rootfs] Populating directory skeleton..."
LOOP_DEV="$(losetup --find --show "$WORK_DIR/extra-rootfs.img")" LOOP_DEV="$(losetup --find --show "$NATIVE_IMG")"
trap 'losetup -d "$LOOP_DEV" 2>/dev/null || true' EXIT
mount -t ext4 "$LOOP_DEV" "$MNT_DIR" mount -t ext4 "$LOOP_DEV" "$MNT_DIR"
MOUNTED=1
mkdir -p "$MNT_DIR/opt" "$MNT_DIR/home" "$MNT_DIR/root" "$MNT_DIR/vscode" mkdir -p "$MNT_DIR/opt" "$MNT_DIR/home" "$MNT_DIR/root" "$MNT_DIR/vscode"
# /mnt/persist/vscode will hold the VSCode tarball extraction target marker;
# /opt bind-mount covers /opt/vscode at runtime.
touch "$MNT_DIR/vscode/.keep" touch "$MNT_DIR/vscode/.keep"
if [ "$KDE_FULL" -eq 1 ]; then
echo "[extra-rootfs] Installing full Arch/KDE suite with pacstrap..."
PACSTRAP_ARGS=(-K -c)
if [ -n "$KDE_PACMAN_CONF" ]; then
PACSTRAP_ARGS+=(-C "$KDE_PACMAN_CONF")
fi
"$KDE_PACSTRAP" "${PACSTRAP_ARGS[@]}" "$MNT_DIR" "${KDE_PACKAGES[@]}"
# The image is a complete chroot kept separate from the active minimal
# guest root. A marker lets the launcher distinguish wrapper and chroot
# execution without relying on package presence in the outer root.
mkdir -p "$MNT_DIR/etc" "$MNT_DIR/usr/local/bin" "$MNT_DIR/var/log"
cat > "$MNT_DIR/etc/deshab-kde-root" <<'EOF'
Deshab Arch/KDE guest root
EOF
: > "$MNT_DIR/etc/machine-id"
if ! chroot "$MNT_DIR" /usr/bin/id "$KDE_USER" >/dev/null 2>&1; then
chroot "$MNT_DIR" /usr/sbin/useradd \
--create-home --uid "$KDE_UID" --shell /bin/bash "$KDE_USER"
fi
for group in audio input video wheel; do
if chroot "$MNT_DIR" /usr/bin/getent group "$group" >/dev/null 2>&1; then
chroot "$MNT_DIR" /usr/sbin/usermod --append --groups "$group" "$KDE_USER"
fi
done
chroot "$MNT_DIR" /usr/bin/passwd --lock "$KDE_USER" >/dev/null
# The current guest has a custom PID 1, so Plasma's supported legacy boot
# path is selected. systemd and its user units remain installed for a
# future systemd/logind boot path.
mkdir -p "$MNT_DIR/home/$KDE_USER/.config"
cat > "$MNT_DIR/home/$KDE_USER/.config/startkderc" <<'EOF'
[General]
systemdBoot=false
EOF
chroot "$MNT_DIR" /usr/bin/chown -R "$KDE_USER:$KDE_USER" "/home/$KDE_USER"
cat > "$MNT_DIR/usr/local/bin/deshab-kde-session" <<'SESSION_SCRIPT'
#!/bin/bash
set -Eeuo pipefail
KDE_ROOT="${DESHAB_KDE_ROOT:-/mnt/persist}"
SESSION_USER="${DESHAB_KDE_USER:-deshab}"
SOFTWARE_RENDERING="${DESHAB_KDE_SOFTWARE_RENDERING:-1}"
BOUND_MOUNTS=()
SEATD_PID=""
log() {
printf '[deshab-kde] %s\n' "$*" | tee -a "$LOG_FILE" >&2
}
cleanup_session() {
local i
set +e
if [ -n "$SEATD_PID" ]; then
kill "$SEATD_PID" 2>/dev/null || true
wait "$SEATD_PID" 2>/dev/null || true
fi
for ((i=${#BOUND_MOUNTS[@]} - 1; i >= 0; i--)); do
umount -R "${BOUND_MOUNTS[$i]}" 2>/dev/null || \
umount "${BOUND_MOUNTS[$i]}" 2>/dev/null || true
done
}
trap cleanup_session EXIT INT TERM
if [ ! -e /etc/deshab-kde-root ]; then
LOG_FILE="$KDE_ROOT/var/log/deshab-kde-session.log"
if [ "$(id -u)" -ne 0 ]; then
echo "[deshab-kde] ERROR: entering the KDE chroot requires root" >&2
exit 1
fi
if [ ! -x "$KDE_ROOT/usr/bin/startplasma-wayland" ]; then
echo "[deshab-kde] ERROR: no prepared KDE root at $KDE_ROOT" >&2
exit 1
fi
mkdir -p "$KDE_ROOT"/{dev,proc,sys,run,var/log}
touch "$LOG_FILE"
log "entering KDE root at $KDE_ROOT"
for fs in dev proc sys run; do
target="$KDE_ROOT/$fs"
if mountpoint -q "$target"; then
continue
fi
if ! mount --rbind "/$fs" "$target"; then
log "ERROR: could not bind /$fs into the KDE root"
exit 1
fi
mount --make-rslave "$target" 2>/dev/null || true
BOUND_MOUNTS+=("$target")
done
set +e
chroot "$KDE_ROOT" /usr/bin/env \
DESHAB_KDE_USER="$SESSION_USER" \
DESHAB_KDE_SOFTWARE_RENDERING="$SOFTWARE_RENDERING" \
/usr/local/bin/deshab-kde-session --inside
status=$?
set -e
log "chroot session exited with status $status"
exit "$status"
fi
LOG_FILE="/var/log/deshab-kde-session.log"
touch "$LOG_FILE"
if [ "$(id -u)" -ne 0 ]; then
log "ERROR: session setup must start as root"
exit 1
fi
if ! id "$SESSION_USER" >/dev/null 2>&1; then
log "ERROR: session user does not exist: $SESSION_USER"
exit 1
fi
if [ ! -e /dev/dri/card0 ]; then
log "ERROR: /dev/dri/card0 is unavailable; leaving the fallback console intact"
exit 1
fi
dbus-uuidgen --ensure=/etc/machine-id
session_uid="$(id -u "$SESSION_USER")"
session_gid="$(id -g "$SESSION_USER")"
runtime_dir="/run/user/$session_uid"
install -d -m 0700 -o "$session_uid" -g "$session_gid" "$runtime_dir"
if command -v seatd >/dev/null 2>&1 && [ ! -S /run/seatd.sock ]; then
log "starting seatd for the non-systemd guest session"
seatd -g video >>"$LOG_FILE" 2>&1 &
SEATD_PID=$!
fi
if [ -x /usr/lib/plasma-dbus-run-session-if-needed ]; then
SESSION_COMMAND=(
/usr/lib/plasma-dbus-run-session-if-needed
/usr/bin/startplasma-wayland
)
elif command -v dbus-run-session >/dev/null 2>&1; then
SESSION_COMMAND=(/usr/bin/dbus-run-session -- /usr/bin/startplasma-wayland)
else
log "ERROR: no Plasma D-Bus session launcher is installed"
exit 1
fi
SOFTWARE_ENV=()
if [ "$SOFTWARE_RENDERING" -eq 1 ]; then
SOFTWARE_ENV=(
LIBGL_ALWAYS_SOFTWARE=1
GALLIUM_DRIVER=llvmpipe
MESA_LOADER_DRIVER_OVERRIDE=llvmpipe
QT_QUICK_BACKEND=software
)
log "software rendering fallback enabled (llvmpipe/lavapipe)"
fi
log "starting Plasma Wayland as $SESSION_USER"
set +e
runuser --user "$SESSION_USER" -- /usr/bin/env \
-u DBUS_SESSION_BUS_ADDRESS \
HOME="/home/$SESSION_USER" \
USER="$SESSION_USER" \
LOGNAME="$SESSION_USER" \
XDG_RUNTIME_DIR="$runtime_dir" \
XDG_SESSION_TYPE=wayland \
XDG_SESSION_CLASS=user \
XDG_CURRENT_DESKTOP=KDE \
DESKTOP_SESSION=plasma \
KDE_FULL_SESSION=true \
QT_QPA_PLATFORM=wayland \
LIBSEAT_BACKEND=seatd \
"${SOFTWARE_ENV[@]}" \
"${SESSION_COMMAND[@]}" >>"$LOG_FILE" 2>&1
status=$?
set -e
if [ "$status" -ne 0 ]; then
log "Plasma failed with status $status; see $LOG_FILE"
else
log "Plasma session ended normally"
fi
exit "$status"
SESSION_SCRIPT
chmod 0755 "$MNT_DIR/usr/local/bin/deshab-kde-session"
# pacstrap -c uses the host cache; remove any target cache left by hooks.
rm -rf "$MNT_DIR/var/cache/pacman/pkg/"*
fi
sync sync
umount "$MNT_DIR" umount "$MNT_DIR"
MOUNTED=0
losetup -d "$LOOP_DEV" losetup -d "$LOOP_DEV"
trap - EXIT LOOP_DEV=""
trap - EXIT INT TERM
# 4. Copy the final image to the Windows side (SYSTEM/boot). # 3. Copy only the final image back to the project tree.
echo "[extra-rootfs] Copying to $OUTPUT_IMG ..." echo "[extra-rootfs] Copying to $OUTPUT_IMG ..."
cp "$WORK_DIR/extra-rootfs.img" "$OUTPUT_IMG" cp --sparse=always "$NATIVE_IMG" "$OUTPUT_IMG"
echo "[extra-rootfs] Done: $OUTPUT_IMG ($(du -h "$OUTPUT_IMG" | cut -f1) on disk)" echo "[extra-rootfs] Done: $OUTPUT_IMG ($(du -h "$OUTPUT_IMG" | cut -f1) on disk)"
echo "[extra-rootfs] Next: uncomment the extra-rootfs module lines in" if [ "$KDE_FULL" -eq 1 ]; then
echo "[extra-rootfs] SYSTEM/limine/limine.conf, then rebuild the ESP image." echo "[extra-rootfs] Guest launcher:"
echo "[extra-rootfs] /mnt/persist/usr/local/bin/deshab-kde-session"
fi
echo "[extra-rootfs] Attach this image to the Linux guest through virtio-blk."
+148
View File
@@ -0,0 +1,148 @@
# CPython 应用(Linux guest)
## 当前架构
Deshab 的 CPython 路径只运行在 Arch Linux guest 中。原生 DSK 应用是
freestanding Ring 0 ELF,没有可供 CPython 使用的完整 libc、动态链接器、
解释器和标准库,因此不能把 CPython 当作原生 DSK 运行时。
`SYSTEM/lib` 中的 `libpython` 和扩展模块只是兼容层库集合的一部分,不构成
完整 Python 安装,也不应作为此路径的主运行时。解释器、标准库和扩展模块
必须来自同一套 Arch 软件包,使 `/usr/bin/python3` 与其 distro 库版本保持
一致。
运行前需要:
- Linux guest 内核、initrd 和 Arch `linux-rootfs.img` 已构建并在 Limine
配置中启用;
- guest 已启动并且 `linux_compat` daemon 处于 ready/parked 状态;
- Arch rootfs 中已安装 distro Python;
- 示例源码已复制到 guest 的固定路径
`/usr/local/share/deshab/python/hello.py`。
## 在 Arch rootfs 安装 Python
Arch 的安装命令是:
```sh
pacman -S python
```
当前 guest 把基础 rootfs 以只读方式挂载,所以不要在已启动 guest 中直接向
`/usr` 安装软件包。应在 WSL/Linux 的可写 rootfs staging tree 中安装,然后
重新打包。使用 `build_arch_rootfs.sh` 的默认工作目录时,可以执行:
```sh
cd /mnt/d/Code/DEAICUP/Deshab
# 首次运行会创建 /root/arch_rootfs_work/rootfs staging tree。
sudo bash CODE/linux/build_arch_rootfs.sh
ROOTFS=/root/arch_rootfs_work/rootfs
sudo mkdir -p "$ROOTFS/tmp/pacman/lib/sync" "$ROOTFS/tmp/pacman/cache/pkg"
printf 'nameserver 1.1.1.1\n' | sudo tee "$ROOTFS/tmp/resolv.conf" >/dev/null
sudo chroot "$ROOTFS" /usr/bin/pacman -Sy --noconfirm
sudo chroot "$ROOTFS" /usr/bin/pacman -S --noconfirm python
sudo install -D -m 0644 \
SYSTEM/user/python/hello.py \
"$ROOTFS/usr/local/share/deshab/python/hello.py"
# staging tree 已存在,第二次运行会保留以上内容并重新生成 rootfs image。
sudo bash CODE/linux/build_arch_rootfs.sh
```
若设置了 `ARCH_ROOTFS_WORK`,相应调整 `ROOTFS`。更新 Arch Python 时应通过
pacman 一起更新解释器和标准库;不要从 `SYSTEM/lib` 拼装或覆盖 distro
`libpython`。
## 脚本如何进入 guest
`SYSTEM/user/python/hello.py` 是系统盘中的示例源码。当前构建不会把
`SYSTEM/user/python/` 任意目录自动同步到 Linux rootfs;`linux_compat`
启动时的自动同步只覆盖专用内容(例如兼容库和 VSCode 模块)。因此,上节的
`install` 命令是当前确定性的发布步骤:在打包前把脚本写入 Arch staging
tree。
开发时也可以使用 Deshab shell 的 `dsl push`,但它的来源只支持 FAT32
根目录中的 8.3 文件名。先把脚本临时放到系统盘根目录并重建镜像:
```powershell
Copy-Item .\SYSTEM\user\python\hello.py .\SYSTEM\HELLO.PY
.\build.bat -Variant dev
```
启动后,在 Deshab shell 中复制到 guest 的可写目录:
```text
dsl /bin/mkdir -p /root/deshab/python
dsl push HELLO.PY /root/deshab/python/hello.py
dsl /usr/bin/env -u LD_LIBRARY_PATH /usr/bin/python3 /root/deshab/python/hello.py
```
此开发路径不是任意目录自动推送,且 `/root` 需要可写的 extra-rootfs。正式
注册项仍使用打包进基础 rootfs 的 `/usr/local/share/...` 路径。
## 桌面注册格式
当前 `SYSTEM/LINUXAPP.CNF` 由 `desktop.elf` 按以下格式解析:
```text
NAME|DISPLAY|/guest/executable|arg1 arg2
```
- `NAME` 是内部名,最多 11 个字符;只有精确的 `VSCODE` 有共享 IDE 特殊语义。
- `DISPLAY` 是图标名,最多 15 个字符。
- executable 必须是 guest 绝对路径;path 和 args 各最多 95 个字符。
- args 只按 ASCII 空格拆分,最多形成 14 个附加参数;不支持引号、转义或含
空格的单个参数。
- `#` 必须位于行首才会被识别为注释。
- 当前 desktop 最多注册 4 个 Linux 应用。
Python 示例注册项为:
```text
PYTHON|Python|/usr/bin/env|-u LD_LIBRARY_PATH /usr/bin/python3 /usr/local/share/deshab/python/hello.py
```
desktop 的 `exec_async` 会注入
`LD_LIBRARY_PATH=/usr/lib/deshab`。注册项先运行 distro
`/usr/bin/env` 删除该变量,再 exec `/usr/bin/python3`,避免同步到
`SYSTEM/lib` 的局部 Python/glibc 文件优先于 Arch distro 库。`DISPLAY=:0`
仍会保留。
## 验证与限制
桌面图标走面向 GUI 的异步启动路径:它会准备 X server、切换到 IDE attach
页,并把子进程 stdout/stderr 重定向到 `/dev/null`。因此点击 Python 图标
不会显示 `hello.py` 的文本输出,也不代表脚本最终成功退出。
请用同步 `dsl` 路径验证,这会捕获 stdout/stderr 和退出码:
```text
dsl /usr/bin/env -u LD_LIBRARY_PATH /usr/bin/python3 /usr/local/share/deshab/python/hello.py
```
成功输出至少包含:
```text
DESHAB_PYTHON_READY
version=...
platform=...
```
其他当前限制:
- 只有 guest CPython;没有原生 DSK CPython ABI。
- `LINUXAPP.CNF` 没有 shell quoting,复杂命令应放进 guest 侧脚本。
- desktop 异步启动不返回应用输出和最终退出状态。
- 尚无 Python 专用 Deshab GUI、窗口、输入、通知或文件选择 API。
- CPython 版本由 Arch 仓库决定,应用不应假设 `SYSTEM/lib` 中的版本。
## 未来 IPC GUI 方向
后续可在 guest 提供版本化的 `deshab` Python 包,通过现有共享内存/ring
IPC 与 host desktop 服务通信。建议逐步提供窗口/Surface 生命周期、批量绘制
缓冲、键鼠事件队列、剪贴板、文件选择和通知 API。CPython 继续由 distro
维护;Python 包只封装稳定 IPC 消息,不直接链接 DSK,也不依赖
`SYSTEM/lib` 的局部 `libpython`。
+111
View File
@@ -0,0 +1,111 @@
/* deshab/syscall.h - experimental native syscall ABI
*
* This header only reserves numbers and emits the x86_64 SYSCALL instruction.
* Deshab does not currently install a native syscall entry point or dispatcher.
* Calling these wrappers on a current image is unsupported and may fault.
*/
#ifndef DESHAB_SYSCALL_H
#define DESHAB_SYSCALL_H
#include "types.h"
#define DESHAB_SYSCALL_ABI_MAJOR 1u
#define DESHAB_SYSCALL_ABI_MINOR 0u
/*
* Reserved v1 number ranges:
* 0x0000-0x001f core
* 0x0100-0x011f native capability IPC
* 0x0120-0x013f task/scheduler
* 0x0140-0x015f memory
*
* Values are explicit and must never be renumbered after implementation ships.
*/
typedef enum deshab_syscall_number {
DESHAB_SYS_ABI_QUERY = 0x0000,
DESHAB_SYS_EXIT = 0x0001,
DESHAB_SYS_YIELD = 0x0002,
DESHAB_SYS_CLOCK_MONOTONIC = 0x0003,
DESHAB_SYS_DEBUG_WRITE = 0x0004,
DESHAB_SYS_IPC_QUEUE_CREATE = 0x0100,
DESHAB_SYS_IPC_QUEUE_DESTROY = 0x0101,
DESHAB_SYS_IPC_SEND = 0x0102,
DESHAB_SYS_IPC_RECEIVE = 0x0103,
DESHAB_SYS_IPC_CALL = 0x0104,
DESHAB_SYS_IPC_REPLY = 0x0105,
DESHAB_SYS_IPC_WAIT = 0x0106,
DESHAB_SYS_IPC_SIGNAL = 0x0107,
DESHAB_SYS_TASK_CREATE = 0x0120,
DESHAB_SYS_TASK_EXIT = 0x0121,
DESHAB_SYS_TASK_SET_PRIORITY = 0x0122,
DESHAB_SYS_TASK_SET_AFFINITY = 0x0123,
DESHAB_SYS_MEM_MAP = 0x0140,
DESHAB_SYS_MEM_UNMAP = 0x0141,
DESHAB_SYS_MEM_PROTECT = 0x0142
} deshab_syscall_number;
/*
* Proposed x86_64 register ABI:
* RAX = number
* RDI, RSI, RDX, R10, R8, R9 = arguments 0..5
* RAX = signed result (negative values are errors)
* RCX and R11 are destroyed by SYSCALL
*/
#if (defined(__x86_64__) || defined(_M_X64)) && \
(defined(__clang__) || defined(__GNUC__))
#define DESHAB_SYSCALL_INLINE_AVAILABLE 1
static inline i64 deshab_syscall6(u64 number, u64 arg0, u64 arg1, u64 arg2,
u64 arg3, u64 arg4, u64 arg5) {
register u64 syscall_arg3 __asm__("r10") = arg3;
register u64 syscall_arg4 __asm__("r8") = arg4;
register u64 syscall_arg5 __asm__("r9") = arg5;
u64 result;
__asm__ volatile(
"syscall"
: "=a"(result)
: "a"(number), "D"(arg0), "S"(arg1), "d"(arg2),
"r"(syscall_arg3), "r"(syscall_arg4), "r"(syscall_arg5)
: "rcx", "r11", "memory", "cc");
return (i64)result;
}
static inline i64 deshab_syscall0(u64 number) {
return deshab_syscall6(number, 0, 0, 0, 0, 0, 0);
}
static inline i64 deshab_syscall1(u64 number, u64 arg0) {
return deshab_syscall6(number, arg0, 0, 0, 0, 0, 0);
}
static inline i64 deshab_syscall2(u64 number, u64 arg0, u64 arg1) {
return deshab_syscall6(number, arg0, arg1, 0, 0, 0, 0);
}
static inline i64 deshab_syscall3(u64 number, u64 arg0, u64 arg1, u64 arg2) {
return deshab_syscall6(number, arg0, arg1, arg2, 0, 0, 0);
}
static inline i64 deshab_syscall4(u64 number, u64 arg0, u64 arg1, u64 arg2,
u64 arg3) {
return deshab_syscall6(number, arg0, arg1, arg2, arg3, 0, 0);
}
static inline i64 deshab_syscall5(u64 number, u64 arg0, u64 arg1, u64 arg2,
u64 arg3, u64 arg4) {
return deshab_syscall6(number, arg0, arg1, arg2, arg3, arg4, 0);
}
#else
#define DESHAB_SYSCALL_INLINE_AVAILABLE 0
#endif
#endif /* DESHAB_SYSCALL_H */
+1
View File
@@ -11,3 +11,4 @@
# 其它实例只经桌面图标启动,IDE tab 标签显示最近启动实例的 DISPLAY)。 # 其它实例只经桌面图标启动,IDE tab 标签显示最近启动实例的 DISPLAY)。
VSCODE|VSCode|/opt/vscode/bin/code|--no-sandbox --disable-gpu --user-data-dir /root/.vscode-data VSCODE|VSCode|/opt/vscode/bin/code|--no-sandbox --disable-gpu --user-data-dir /root/.vscode-data
VSCODE2|VSCode2|/opt/vscode/bin/code|--no-sandbox --disable-gpu --user-data-dir /root/.vscode-data2 /root/workspace2 VSCODE2|VSCode2|/opt/vscode/bin/code|--no-sandbox --disable-gpu --user-data-dir /root/.vscode-data2 /root/workspace2
PYTHON|Python|/usr/bin/env|-u LD_LIBRARY_PATH /usr/bin/python3 /usr/local/share/deshab/python/hello.py
+17
View File
@@ -0,0 +1,17 @@
"""Minimal CPython readiness check for the Deshab Linux guest."""
from __future__ import annotations
import platform
import sys
def main() -> None:
version = " ".join(sys.version.split())
print("DESHAB_PYTHON_READY")
print(f"version={version}")
print(f"platform={platform.platform()}")
if __name__ == "__main__":
main()
+177
View File
@@ -0,0 +1,177 @@
# XNU parity and “surpass” gates
This document is a gap analysis, not a claim that Deshab currently has XNU
parity. XNU is a production kernel with decades of hardware support, security
hardening, compatibility work, and performance tuning. Deshab is an
experimental x86_64 SAS-R0 system. “Surpass” below means passing a named,
reproducible gate on a narrow axis; it does not mean overall superiority.
## Evidence boundary
### Implemented strengths
- Limine boots `utsm.elf`, which loads staged ELF64 DKM modules and transfers
control to the DSK kernel.
- DKM has a versioned descriptor/API, manifest-driven boot stages, an ELF64
ET_REL loader, and working platform, storage, filesystem, network, console,
and input modules. The current loader reaches 14 external drivers in its
tested boot configuration.
- UTSM has segment descriptors, generation/epoch checks, capabilities,
per-CPU key-cache structures, encrypted-buffer read/write paths, dirty-page
accounting, and self-tests.
- DRR has an in-memory emergency pool, recovery log, CRC64 metadata, and A/B
checkpoint metadata primitives. This is not yet durable payload rollback,
an independent watchdog, or complete device recovery.
- The storage path includes AHCI block I/O, a small block-provider registry,
bootfs/devfs, read-only DKM FAT32, and a limited DSK FAT32 write path.
- The e1000 path and small network stack provide a boot-time/DSK data path for
Ethernet, ARP, DHCP, UDP, and DNS. They are not a socket stack.
- Linux compatibility code implements a VMX/EPT guest design, virtio-mmio
backends, and a guest-specific shared-memory/hypercall protocol. The full
nested-VMX test matrix remains pending in the repository.
- PE32+ loading and a PE32 interpreter exist with a limited Win32 shim.
Repository test matrices still list unverified cases and known failure
paths, so this is not general Windows binary compatibility.
### Aspirational designs
- SAS-R0-PCQ per-CPU bitmap scheduling is a design. `sched.h` and
`sched_stub.c` define an isolated contract only; there is no scheduler boot
hookup or context switch.
- `ipc_native.h` defines a possible native capability/message ABI. It has no
queue implementation, syscall transport, policy, or app integration.
- `deshab/syscall.h` reserves an experimental x86_64 native syscall surface.
There is no syscall entry point, dispatcher, user mode, or `libSystem`
equivalent.
- UTSM’s current xorshift-derived XOR stream and XOR capability tag are not
cryptographic confidentiality or integrity. The DRR root key is compiled
into the image. Claims of sealed-memory security must wait for real key
management, reviewed primitives, anti-replay checks, and adversarial tests.
- DKM unload, hotplug, driver fault domains, and DRR-driven device
quiesce/reset/reinit are designs, not demonstrated runtime properties.
## XNU comparison
| XNU area | What XNU provides | Current Deshab state | Parity requirement |
| --- | --- | --- | --- |
| Mach IPC | Kernel-managed ports, port rights, messages, notifications, port sets, RPC patterns, and mature scheduling/accounting behavior | Linux-guest SPSC rings and hypercalls exist. They are transport-specific and are not native task IPC. The new native ABI is declarations only. | Implement lifecycle-safe capability queues, transfer/attenuation, blocking and deadlines, cancellation, notifications, accounting, and stress/fuzz tests. |
| BSD/POSIX | Processes, credentials, signals, file descriptors, sockets, users/groups, resource controls, and a broad syscall contract | Custom Ring0 programs and services; no complete native process, descriptor, signal, credential, or POSIX syscall model | Publish a supported native contract and either implement a measured POSIX subset or explicitly route unsupported Linux behavior through the guest. |
| IOKit/DriverKit | Object/service registry, device matching, lifecycle and power management, user-space driver options, entitlements, and broad hardware support | DKM can stage and relocate native Ring0 modules through a kernel API. Runtime matching, power management, hotplug, and fault isolation are absent. | Add device/service registry, deterministic bind/unbind, hotplug, power transitions, resource revocation, and measured failure recovery. |
| Mach VM/pmap | Per-task VM maps, objects/pagers, copy-on-write, protections, wired memory, shared mappings, architecture pmap, TLB coherency, and mature accounting | HHDM/MMIO mappings, DMA allocation, UTSM segments, and capability checks exist. SAS-R0 intentionally does not provide XNU-style task isolation. There is no mature VM object/pager/COW layer. | Define SAS-R0 protection semantics, complete page lifecycle and SMP TLB rules, support mapped objects/COW where promised, and prove bounds/fault behavior. |
| VFS | Uniform vnode/name cache/mount model, permissions, locking, many filesystems, journaling integration, and scalable concurrent I/O | Small bootfs/devfs/VFS paths and limited FAT32 support; path, mount, locking, cache, and durability semantics are incomplete | Specify vnode-like object and mount contracts, concurrent lookup/I/O, cache invalidation, permissions policy, recovery, and filesystem conformance tests. |
| Networking | Sockets, TCP/IP and IPv6, routing, interfaces, packet filters, QoS, observability, and many drivers | e1000 plus minimal Ethernet/ARP/DHCP/UDP/DNS functionality; virtio-net is incomplete and there is no general socket/TCP/IPv6 API | Deliver sockets, TCP, IPv6, routing, DNS behavior, packet filtering, interrupt-driven drivers, loss/reorder tests, and sustained-load benchmarks. |
| kext | Historically loadable kernel extensions with dependency, code-signing, policy, lifecycle, and diagnostics; modern macOS moves many drivers out of the kernel | DKM’s small versioned ABI and staged ET_REL loader are a useful narrow base. Signing, policy, unload/hotplug, ABI evolution tests, and containment are absent. | Add authenticated modules, compatibility negotiation, dependency/lifecycle tests, unload safety, and recoverable driver fault domains. |
| dyld/libSystem | Stable process startup, dynamic loader, shared cache, libc/POSIX runtime, Objective-C/Swift runtime integration, and tooling | PIE loading exists in selected internal paths, but native apps have no general dynamic loader, libc, stable syscall dispatcher, or ABI toolchain contract | Provide crt startup, ELF dynamic linking/TLS, libc/runtime services, versioning, diagnostics, and an SDK conformance suite. |
Architectural difference matters: XNU uses protected user tasks and multiple
security boundaries, while Deshab prioritizes a single-address-space Ring0
model. Lower transition overhead in SAS-R0 cannot be counted as a win unless
fault propagation, revocation, recovery, and whole-system tail latency are
measured too.
## Measurable “surpass” targets
Every result must record source revision, compiler flags, firmware, CPU model,
core count, memory, timer source, warm-up, sample count, and raw data. XNU
comparisons must use the same physical machine where possible and equivalent
release builds. Passing one gate permits only the corresponding narrow claim.
### S1 — DRR recovery and rollback
- Run at least 10,000 deterministic fault-injection trials per class: dirty
page corruption, torn inactive metadata slot, torn active-slot switch,
exhausted normal allocator, task fault, and recoverable driver fault.
- Resume a protected service with correct pre-fault state at p99.9 within
10 ms for page rollback, 50 ms for segment rollback, and 2 s for system-slot
rollback; report maximum as well as percentiles.
- Produce zero undetected corruptions, zero selection of a bad checkpoint, and
zero allocations from the normal heap on the recovery path.
- On the same workload, beat XNU’s closest service restart or OS recovery path
in p99.9 unavailable time by at least 2x. If the semantics are not
equivalent, publish only the absolute Deshab result.
### S2 — UTSM confidentiality and integrity
- Remove the xorshift/XOR stream, XOR `auth_tag`, and compiled root key.
Adopt reviewed KDF/encryption/authentication constructions with unique
nonces or tweaks, explicit key erasure, boot-derived root-key handling, and
replay-protected epochs.
- Pass published known-answer tests plus at least 10 million randomized
read/write/rekey/rollback operations under ASan/UBSan in a host harness.
- Detect 100% of injected ciphertext, metadata, tag, and stale-epoch changes
before plaintext release; demonstrate no plaintext in UTSM-managed backing
pages outside documented access windows. DMA and framebuffer exceptions
must be enumerated.
- Keep p99 4 KiB read and write overhead below 15% versus an unsealed Deshab
buffer on hardware with the selected acceleration enabled. Report a scalar
fallback separately; never trade away verification to meet this number.
### S3 — O(1) per-CPU scheduler
- Demonstrate by instrumentation that enqueue, dequeue, wake, and pick-next
touch a bounded number of bitmap/list words independent of runnable-task
count; no capability scan, MAC, checkpoint, allocation, or re-encryption may
occur on the switch path.
- Across 1, 64, 1,024, and 65,536 synthetic runnable tasks, keep p99
pick-next below 250 cycles and variation between the smallest and largest
set below 5% on the reference CPU.
- Keep p99 same-CPU context switch below 1 microsecond and publish fairness,
starvation, affinity, timer, and 24-hour stress results.
### S4 — Native capability IPC
- Validate malformed sizes, stale generations, forged object IDs, rights
escalation, queue destruction races, cancellation, and capability transfer
with fuzzing and at least 100 million stress operations.
- For 64-byte inline request/reply on one core, achieve median below 1
microsecond, p99 below 3 microseconds, and at least 2 million round trips per
second per core without dropping messages.
- Against `mach_msg` with equivalent payload, queue depth, copy semantics, and
synchronization on the same machine, achieve at least 1.5x throughput and
at most 0.67x p99 latency. Publish copy, transfer, and cross-core cases
separately.
### S5 — DKM hotplug and fault containment
- Complete 1,000 attach/use/detach cycles for every hotpluggable test device
with zero leaked mappings, vectors, DMA pages, capabilities, or callbacks.
- Inject faults at every driver entry point and DMA completion boundary.
Restore service at p99 within 100 ms without reboot and without modifying
memory outside the driver’s declared UTSM segments.
- Because all native code currently runs Ring0, document the mechanism that
makes the containment claim true (for example checked entry gates plus
recoverable memory domains, or a stronger execution boundary). A caught
exception alone is not containment.
### S6 — Linux and PE compatibility breadth
- Linux: on a published Arch package corpus of at least 100 CLI applications,
install and pass upstream smoke tests for at least 99%, including filesystem,
process, signal, networking, and terminal workloads. Complete 1,000
park/resume cycles and a 24-hour mixed workload with no Deshab crash.
- PE: on a published corpus of at least 100 PE32/PE32+ applications, load at
least 95% and pass functional tests for at least 90%; include malformed
binary rejection, TLS, exception, GUI/input, file, and network cases.
- A Linux guest success is reported as guest compatibility, not a native
Linux syscall implementation. A PE image that only reaches its entry point
is reported as “loads,” not “works.”
- This gate can support a broader-binary-coverage claim than stock XNU only
for the exact tested corpus; it does not establish general Linux or Windows
compatibility.
## First isolated slice
This slice intentionally changes no boot order or build integration:
- `CODE/UTSM/include/utsm/sched.h` reserves a scheduler contract and TCB/run
queue state.
- `CODE/UTSM/sched/sched_stub.c` makes every scheduler operation fail with an
explicit unsupported result.
- `CODE/UTSM/include/utsm/ipc_native.h` defines a bounded native IPC wire ABI,
distinct from `CODE/utsm-ipc/ipc_proto.h`.
- `CODE/sdk/include/deshab/syscall.h` reserves experimental native syscall
numbers and an x86_64 instruction wrapper.
None of these files demonstrates a scheduler, native IPC, syscall entry, XNU
parity, or a “surpass” result. Those claims begin only after the gates above
are implemented and reproduced.