FUCKYOU
This commit is contained in:
@@ -79,3 +79,18 @@ void serial_write(const char *s) {
|
||||
serial_putc(*s++);
|
||||
}
|
||||
}
|
||||
|
||||
/* 非阻塞读取 host 串口一个字节(QEMU stdio → guest COM1 输入桥)。
|
||||
* 返回 0-255,无数据返回 -1。供 vmexit 在 guest resume 前轮询。 */
|
||||
int serial_try_read(void) {
|
||||
if (inb(COM1 + 5) & 0x01) {
|
||||
return inb(COM1);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 导出 PIT 校准的 TSC 频率(kHz 量级,单位 ticks/ms),
|
||||
* 供 VMX preemption timer 与 guest PIT tick 注入使用。 */
|
||||
u64 serial_tsc_per_ms(void) {
|
||||
return g_tsc_per_ms;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -173,8 +173,8 @@ typedef long long du_i64;
|
||||
|
||||
#define DU_RADIUS_NONE 0u
|
||||
#define DU_RADIUS_SM 4u /* 小按钮、标签 */
|
||||
#define DU_RADIUS_MD 8u /* 卡片、输入框 */
|
||||
#define DU_RADIUS_LG 12u /* 大卡片、面板 */
|
||||
#define DU_RADIUS_MD 0u /* 卡片、输入框(性能优化:改为直角) */
|
||||
#define DU_RADIUS_LG 0u /* 大卡片、面板(性能优化:改为直角) */
|
||||
#define DU_RADIUS_XL 16u /* 模态框 */
|
||||
#define DU_RADIUS_FULL 9999u /* 完全圆角(药丸形) */
|
||||
|
||||
@@ -722,9 +722,9 @@ static inline du_u32 du_theme_accent(du_theme t) {
|
||||
/* --- 强调色:霓虹青 (Neon Cyan) --- */
|
||||
#define DP_KATE_ACCENT 0xFF00E5FFu /* --accent: #00e5ff */
|
||||
#define DP_KATE_ACCENT_DIM 0x2E00E5FFu /* --accent-dim: rgba(0,229,255,0.18) */
|
||||
#define DP_KATE_ACCENT_DIM_SOLID 0xFF1A3A4Au /* 不透明近似 */
|
||||
#define DP_KATE_ACCENT_DIM_SOLID 0xFF2A4A5Au /* 不透明近似(提升亮度改善对比度) */
|
||||
#define DP_KATE_PANEL_BORDER 0x5900E5FFu /* --panel-border: rgba(0,229,255,0.35) */
|
||||
#define DP_KATE_PANEL_BORDER_SOLID 0xFF005A6Au /* 不透明近似 */
|
||||
#define DP_KATE_PANEL_BORDER_SOLID 0xFF2A6070u /* 不透明近似(提升亮度) */
|
||||
|
||||
/* --- 辅助色:霓虹绿 (Neon Green) --- */
|
||||
#define DP_KATE_ACCENT2 0xFF39FF14u /* --accent2: #39ff14 */
|
||||
@@ -781,28 +781,12 @@ static inline du_u32 du_theme_accent(du_theme t) {
|
||||
* Winux-Kate 特效函数
|
||||
* =================================================================== */
|
||||
|
||||
/* 绘制扫描线纹理( repeating-linear-gradient 0deg, 3px 周期)
|
||||
* 在指定矩形区域内叠加半透明青色水平细线 */
|
||||
/* 绘制扫描线纹理(已禁用以提升性能)
|
||||
* 原效果:在指定矩形区域内叠加半透明青色水平细线 */
|
||||
static inline void du_kate_scanlines(du_context *ctx, du_i64 x, du_i64 y,
|
||||
du_i64 w, du_i64 h) {
|
||||
for (du_i64 ry = 0; ry < h; ry++) {
|
||||
du_i64 yy = y + ry;
|
||||
if (yy < 0 || (du_u64)yy >= ctx->height) continue; /* 每 3 像素一条线 */
|
||||
du_u32 *line = (du_u32 *)((du_u8 *)ctx->fb + (du_u64)yy * ctx->pitch);
|
||||
for (du_i64 rx = 0; rx < w; rx++) {
|
||||
du_i64 xx = x + rx;
|
||||
if (xx < 0 || (du_u64)xx >= ctx->width) continue;
|
||||
/* 叠加极淡青色(alpha ~0.03 ≈ 8/255)*/
|
||||
du_u32 cur = line[(du_u64)xx];
|
||||
du_u32 b = (cur >> 16) & 0xFF;
|
||||
du_u32 g = (cur >> 8) & 0xFF;
|
||||
du_u32 r = cur & 0xFF;
|
||||
b = (b + 2 < 255) ? b + 2 : 255;
|
||||
g = (g + 4 < 255) ? g + 4 : 255;
|
||||
r = (r + 2 < 255) ? r + 2 : 255;
|
||||
line[(du_u64)xx] = 0xFF000000u | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
}
|
||||
/* 性能优化:禁用扫描线纹理,减少约50%的像素处理开销 */
|
||||
(void)ctx; (void)x; (void)y; (void)w; (void)h;
|
||||
}
|
||||
|
||||
/* 绘制带光晕的矩形边框(box-shadow 效果)
|
||||
|
||||
@@ -121,6 +121,25 @@ struct linux_setup_header {
|
||||
/* General RAM starts after IPC region */
|
||||
#define LINUX_GUEST_RAM_GPA 0x05000000ULL /* 80 MB */
|
||||
|
||||
/* ===== virtio-mmio device GPA layout =====
|
||||
* virtio-mmio 设备放在 GPA 高位 MMIO 区(guest RAM 之上,不占用 e820 RAM)。
|
||||
* guest 通过 cmdline 参数 virtio_mmio.device= 发现这些设备。
|
||||
* 每个设备占 0x200 字节寄存器空间(virtio-mmio 规范要求 4KB 对齐)。
|
||||
*
|
||||
* 这些 GPA 在 EPT 中【故意不映射】,访问时触发 EPT violation,
|
||||
* 由 vmexit handler 路由到 virtio_mmio 后端模拟寄存器读写。
|
||||
*
|
||||
* GPA 选择 0xF4000000 起始(接近 4GB,远离 guest RAM 0x05000000-0x09000000)。 */
|
||||
#define VIRTIO_MMIO_GPA_BASE 0xF4000000ULL
|
||||
#define VIRTIO_MMIO_GPA_STRIDE 0x1000ULL /* 每个设备 4KB */
|
||||
|
||||
#define VIRTIO_MMIO_BLK_GPA (VIRTIO_MMIO_GPA_BASE + 0 * VIRTIO_MMIO_GPA_STRIDE) /* 0xF4000000 */
|
||||
#define VIRTIO_MMIO_NET_GPA (VIRTIO_MMIO_GPA_BASE + 1 * VIRTIO_MMIO_GPA_STRIDE) /* 0xF4001000 */
|
||||
|
||||
/* 判断 GPA 是否落在 virtio-mmio 设备区(含两端) */
|
||||
#define VIRTIO_MMIO_GPA_END (VIRTIO_MMIO_GPA_BASE + 2 * VIRTIO_MMIO_GPA_STRIDE)
|
||||
#define IS_VIRTIO_MMIO_GPA(gpa) ((gpa) >= VIRTIO_MMIO_GPA_BASE && (gpa) < VIRTIO_MMIO_GPA_END)
|
||||
|
||||
/* ===== Linux loader API ===== */
|
||||
|
||||
/* Loaded Linux guest state */
|
||||
@@ -165,9 +184,16 @@ int linux_launch(void);
|
||||
/* Default Linux command line (can be overridden).
|
||||
* nohlt: prevent kernel idle loop from using HLT — only the exec daemon's
|
||||
* explicit ioctl(PARK) HLT should trigger VM-Exit park, so that
|
||||
* linux_launch() returns only after the daemon is ready. */
|
||||
* linux_launch() returns only after the daemon is ready.
|
||||
* noapic/nolapic/nosmp: UTSM VMM 只模拟 legacy PIC(8259) + PIT(8254),
|
||||
* 不提供 LAPIC/IOAPIC;guest 走 XT-PIC 模式,IRQ vector = 0x30+irq。
|
||||
* virtio_mmio.device=: 向 guest 声明 virtio-mmio 设备(blk@0xF4000000 IRQ5,
|
||||
* net@0xF4001000 IRQ6),需 CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y。 */
|
||||
#define LINUX_DEFAULT_CMDLINE \
|
||||
"console=ttyS0,115200 earlyprintk=serial nokaslr " \
|
||||
"no_timer_check loglevel=7 nohlt idle=poll"
|
||||
"no_timer_check loglevel=7 nohlt idle=poll " \
|
||||
"noapic nolapic nosmp " \
|
||||
"virtio_mmio.device=4K@0xF4000000:5 " \
|
||||
"virtio_mmio.device=4K@0xF4001000:6"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
#ifndef UTSM_VIRTIO_MMIO_H
|
||||
#define UTSM_VIRTIO_MMIO_H
|
||||
|
||||
/* virtio_mmio.h — UTSM virtio-mmio 设备模拟后端框架。
|
||||
*
|
||||
* 为 Linux guest 提供 virtio-mmio 设备(block + net)。
|
||||
* 设备 GPA 位于 VIRTIO_MMIO_GPA_BASE 起的高位 MMIO 区,EPT 故意不映射,
|
||||
* guest 访问时触发 EPT violation,vmexit handler 调用本模块的
|
||||
* virtio_mmio_read/write 模拟寄存器行为。
|
||||
*
|
||||
* 参考 Virtio Spec 1.1 §4.2 (virtio-mmio)。
|
||||
* guest 通过 cmdline virtio_mmio.device= 参数发现设备(无需 PCI/ACPI)。
|
||||
*/
|
||||
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* virtio-mmio 寄存器偏移(Virtio Spec 1.1 §4.2.2.1,均为 32 位寄存器) */
|
||||
#define VIRTIO_MMIO_MAGIC_VALUE 0x000 /* R "virt" = 0x74726976 */
|
||||
#define VIRTIO_MMIO_VERSION 0x004 /* R 1=legacy, 2=modern */
|
||||
#define VIRTIO_MMIO_DEVICE_ID 0x008 /* R 1=net, 2=blk, 3=console... */
|
||||
#define VIRTIO_MMIO_VENDOR_ID 0x00C /* R */
|
||||
#define VIRTIO_MMIO_DEVICE_FEATURES 0x010 /* R features[31:0] (sel) */
|
||||
#define VIRTIO_MMIO_DEVICE_FEATURES_SEL 0x014 /* W */
|
||||
#define VIRTIO_MMIO_DRIVER_FEATURES 0x020 /* W features[31:0] (sel) */
|
||||
#define VIRTIO_MMIO_DRIVER_FEATURES_SEL 0x024 /* W */
|
||||
#define VIRTIO_MMIO_GUEST_PAGE_SIZE 0x028 /* W legacy only */
|
||||
#define VIRTIO_MMIO_QUEUE_SEL 0x030 /* W */
|
||||
#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 /* R */
|
||||
#define VIRTIO_MMIO_QUEUE_NUM 0x038 /* W */
|
||||
#define VIRTIO_MMIO_QUEUE_ALIGN 0x03C /* W legacy only */
|
||||
#define VIRTIO_MMIO_QUEUE_PFN 0x040 /* RW legacy only */
|
||||
#define VIRTIO_MMIO_QUEUE_READY 0x044 /* RW modern */
|
||||
#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 /* W */
|
||||
#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 /* R */
|
||||
#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 /* W */
|
||||
#define VIRTIO_MMIO_STATUS 0x070 /* RW */
|
||||
#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080 /* W modern */
|
||||
#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084 /* W modern */
|
||||
#define VIRTIO_MMIO_QUEUE_DRIVER_LOW 0x090 /* W modern */
|
||||
#define VIRTIO_MMIO_QUEUE_DRIVER_HIGH 0x094 /* W modern */
|
||||
#define VIRTIO_MMIO_QUEUE_DEVICE_LOW 0x0A0 /* W modern */
|
||||
#define VIRTIO_MMIO_QUEUE_DEVICE_HIGH 0x0A4 /* W modern */
|
||||
#define VIRTIO_MMIO_CONFIG_GENERATION 0x0FC /* R modern */
|
||||
#define VIRTIO_MMIO_CONFIG 0x100 /* RW device-specific config */
|
||||
|
||||
/* device IDs */
|
||||
#define VIRTIO_ID_NET 1
|
||||
#define VIRTIO_ID_BLOCK 2
|
||||
#define VIRTIO_ID_CONSOLE 3
|
||||
|
||||
/* device status bits */
|
||||
#define VIRTIO_STATUS_ACKNOWLEDGE 1
|
||||
#define VIRTIO_STATUS_DRIVER 2
|
||||
#define VIRTIO_STATUS_DRIVER_OK 4
|
||||
#define VIRTIO_STATUS_FEATURES_OK 8
|
||||
#define VIRTIO_STATUS_FAILED 128
|
||||
|
||||
/* 通用 feature bits */
|
||||
#define VIRTIO_F_VERSION_1 32 /* modern 设备必须提供(bit32=高32位word bit0) */
|
||||
|
||||
/* ===== 后端框架 API ===== */
|
||||
|
||||
/* 初始化 virtio-mmio 后端框架(注册 block + net 设备)。
|
||||
* 在 linux_loader_init 之后、linux_launch 之前调用。 */
|
||||
void virtio_mmio_init(void);
|
||||
|
||||
/* 处理一次 virtio-mmio 寄存器读/写(由 vmexit EPT violation 调用)。
|
||||
* gpa : 触发访问的 GPA(落在某设备 4KB 寄存器区)
|
||||
* width : 访问宽度(字节),通常为 4
|
||||
* value : 写操作的数据
|
||||
* 返回读操作的数据(写操作返回 0)。 */
|
||||
u64 virtio_mmio_read(u64 gpa, int width);
|
||||
void virtio_mmio_write(u64 gpa, u64 value, int width);
|
||||
|
||||
/* 后端驱动注册接口(virtio_blk.c / virtio_net.c 调用) */
|
||||
struct virtio_backend {
|
||||
u32 device_id; /* VIRTIO_ID_* */
|
||||
u64 gpa_base; /* 该设备的 MMIO GPA 起始 */
|
||||
u32 irq; /* guest ISA IRQ 号(cmdline virtio_mmio.device=:IRQ,
|
||||
* legacy PIC 下 vector = 0x30 + irq;0 = 不注入中断) */
|
||||
u32 num_queues; /* 队列数(blk=1, net=2) */
|
||||
u32 queue_size; /* 每队列描述符数 */
|
||||
u64 device_features; /* device 提供的 feature 位 */
|
||||
u32 config_len; /* config 空间长度(从 0x100 起) */
|
||||
|
||||
/* 读取 device config 空间(offset 相对 0x100)。返回 32 位值。 */
|
||||
u32 (*read_config)(u32 offset, int width);
|
||||
/* 通知:guest 写入 QUEUE_NOTIFY,后端处理该队列的请求。 */
|
||||
void (*queue_notify)(u32 queue_idx);
|
||||
/* 复位设备。 */
|
||||
void (*reset)(void);
|
||||
};
|
||||
|
||||
/* 注册一个后端设备(内部使用,按 gpa_base 排序查找)。 */
|
||||
int virtio_mmio_register(struct virtio_backend *dev);
|
||||
|
||||
/* 提供 guest GPA→host 虚拟地址转换(后端访问 virtqueue/描述符用)。
|
||||
* 通过 EPT 翻译 GPA 到 HPA,再加 HHDM offset 得到 host 虚拟地址。
|
||||
* 返回 NULL 表示 GPA 未映射。 */
|
||||
void *virtio_gpa_to_host(u64 gpa);
|
||||
|
||||
/* ===== virtqueue 结构(Virtio Spec 1.1 §2.6 split ring) ===== */
|
||||
|
||||
/* 描述符:16 字节 */
|
||||
struct virtq_desc {
|
||||
u64 addr; /* GPA of data buffer */
|
||||
u32 len; /* data length */
|
||||
u16 flags; /* VIRTQ_DESC_F_* */
|
||||
u16 next; /* next descriptor if F_NEXT */
|
||||
} __attribute__((packed));
|
||||
|
||||
#define VIRTQ_DESC_F_NEXT 1
|
||||
#define VIRTQ_DESC_F_WRITE 2
|
||||
#define VIRTQ_DESC_F_INDIRECT 4
|
||||
|
||||
/* avail ring(driver → device) */
|
||||
struct virtq_avail {
|
||||
u16 flags;
|
||||
u16 idx;
|
||||
u16 ring[1]; /* queue_size entries */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* used ring 元素 */
|
||||
struct virtq_used_elem {
|
||||
u32 id; /* descriptor chain head index */
|
||||
u32 len; /* bytes written (device→driver) */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* used ring(device → driver) */
|
||||
struct virtq_used {
|
||||
u16 flags;
|
||||
u16 idx;
|
||||
struct virtq_used_elem ring[1];
|
||||
} __attribute__((packed));
|
||||
|
||||
/* ===== 队列状态访问 API(后端 queue_notify 用) =====
|
||||
*
|
||||
* 后端在其 queue_notify(queue_idx) 回调中需要:
|
||||
* 1. 读 avail ring 取请求描述符链
|
||||
* 2. 处理请求(读/写 guest 数据 buffer)
|
||||
* 3. 写 used ring 回执
|
||||
* 以下函数按 device_id + queue_idx 定位队列,操作 guest 内存。 */
|
||||
|
||||
/* 取队列的 desc/avail/used 表 host 地址 + 队列大小。
|
||||
* 返回 0 成功(指针填充),负值失败(队列未 ready/未映射)。 */
|
||||
int virtio_queue_get_ptrs(u32 device_id, u32 queue_idx,
|
||||
struct virtq_desc **desc_out,
|
||||
struct virtq_avail **avail_out,
|
||||
struct virtq_used **used_out,
|
||||
u32 *queue_num_out);
|
||||
|
||||
/* 更新 used ring idx(后端处理完一批请求后调用,让 guest 看到新回执)。
|
||||
* 由后端在写 used ring 元素后调用,内部做内存屏障。 */
|
||||
void virtio_queue_bump_used(struct virtq_used *used, u16 new_idx);
|
||||
|
||||
#endif /* UTSM_VIRTIO_MMIO_H */
|
||||
@@ -169,8 +169,10 @@
|
||||
/* ===== Pin-based controls ===== */
|
||||
#define PIN_EXT_INTERRUPT_EXITING (1ULL << 0)
|
||||
#define PIN_NMI_EXITING (1ULL << 3)
|
||||
#define PIN_VMX_PREEMPTION_TIMER (1ULL << 6)
|
||||
|
||||
/* ===== Primary processor-based controls ===== */
|
||||
#define CPU_BASED_INTR_WINDOW_EXITING (1ULL << 2)
|
||||
#define CPU_BASED_HLT_EXITING (1ULL << 7)
|
||||
#define CPU_BASED_INVLPG_EXITING (1ULL << 9)
|
||||
#define CPU_BASED_MWAIT_EXITING (1ULL << 10)
|
||||
@@ -208,6 +210,7 @@
|
||||
#define EXIT_EXTERNAL_INTERRUPT 1
|
||||
#define EXIT_TRIPLE_FAULT 2
|
||||
#define EXIT_INIT_SIGNAL 3
|
||||
#define EXIT_INTERRUPT_WINDOW 7
|
||||
#define EXIT_CPUID 10
|
||||
#define EXIT_INVD 13
|
||||
#define EXIT_RDMSR 31
|
||||
@@ -232,8 +235,13 @@
|
||||
#define EXIT_INVLPG 14
|
||||
#define EXIT_EPT_VIOLATION 48
|
||||
#define EXIT_EPT_MISCONFIG 49
|
||||
#define EXIT_VMX_PREEMPTION_TIMER 52
|
||||
#define EXIT_APIC_ACCESS 44
|
||||
|
||||
/* ===== VM-entry interruption-information field 编码 ===== */
|
||||
#define VM_ENTRY_INTR_INFO_VALID 0x80000000ULL
|
||||
#define VM_ENTRY_INTR_TYPE_HW_IRQ 0 /* external interrupt (bits 10:8) */
|
||||
|
||||
/* ===== EPT memory types ===== */
|
||||
#define EPT_MEMORY_TYPE_UC 0ULL
|
||||
#define EPT_MEMORY_TYPE_WC 1ULL
|
||||
@@ -265,4 +273,16 @@ int vmx_vmresume(void);
|
||||
u64 vmx_read_msr(u32 msr);
|
||||
u64 vmx_get_host_cr3(void);
|
||||
|
||||
/* ===== 虚拟中断注入(vmexit.c 实现) ===== */
|
||||
/* 向 guest 队列注入一个 ISA IRQ 向量(legacy PIC:vector = 0x30 + irq)。
|
||||
* 仅排队;实际在下一次 vmresume 前(guest RFLAGS.IF=1 且无阻塞时)注入,
|
||||
* 否则 arm interrupt-window exiting,待 guest 可接收时注入。 */
|
||||
void vmx_guest_queue_irq(u32 vector);
|
||||
|
||||
/* VMX preemption timer:返回 1ms 对应的 timer 计数值(按 IA32_VMX_BASIC
|
||||
* bits[55:48] 的 scale 换算 TSC 频率)。 */
|
||||
u64 vmx_preemption_quantum_1ms(void);
|
||||
/* 检查 CPU 是否支持 VMX preemption timer。 */
|
||||
int vmx_preemption_timer_supported(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/vmm.h>
|
||||
#include <utsm/virtio_mmio.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
@@ -203,8 +204,14 @@ static void linux_vmcs_setup_guest_state(const struct linux_guest_info *gi) {
|
||||
/* ===== VMCS controls for Linux ===== */
|
||||
|
||||
static void linux_vmcs_setup_controls(u64 eptp) {
|
||||
u64 pin = vmx_adjust_control(PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING,
|
||||
IA32_VMX_TRUE_PINBASED_CTLS);
|
||||
/* Pin-based:外部中断 exit + NMI exit + VMX preemption timer(若支持)。
|
||||
* preemption timer 提供 1ms 周期 exit,用于轮询 host 串口 RX、
|
||||
* 推进 guest PIT tick、注入 pending virtio/COM1 IRQ。 */
|
||||
u64 pin_want = PIN_EXT_INTERRUPT_EXITING | PIN_NMI_EXITING;
|
||||
if (vmx_preemption_timer_supported()) {
|
||||
pin_want |= PIN_VMX_PREEMPTION_TIMER;
|
||||
}
|
||||
u64 pin = vmx_adjust_control(pin_want, IA32_VMX_TRUE_PINBASED_CTLS);
|
||||
|
||||
/* CPU-based: exit on HLT, INVLPG, MWAIT, IO, use MSR bitmap,
|
||||
* activate secondary controls. Don't intercept RDTSC (Linux needs it). */
|
||||
@@ -281,6 +288,10 @@ int linux_launch(void) {
|
||||
|
||||
log_info("[LINUX] launch begin");
|
||||
|
||||
/* 注册 virtio-mmio 后端(block + net),guest 经 cmdline
|
||||
* virtio_mmio.device= 发现。幂等:重复调用直接返回。 */
|
||||
virtio_mmio_init();
|
||||
|
||||
/* Reset VM-Exit state */
|
||||
g_guest_terminated = 0;
|
||||
g_last_exit_reason = 0xFFFFFFFFULL;
|
||||
@@ -292,6 +303,9 @@ int linux_launch(void) {
|
||||
linux_vmcs_setup_controls(vmm_get_eptp());
|
||||
linux_vmcs_setup_host_state();
|
||||
|
||||
/* 初始 arm preemption timer(1ms 后首次周期 exit) */
|
||||
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms());
|
||||
|
||||
/* Set RSI for Linux boot_params (passed via global, loaded in asm) */
|
||||
g_linux_rsi = gi->bootparams_gpa;
|
||||
|
||||
|
||||
@@ -270,6 +270,45 @@ void *linux_find_bzimage_module(u64 *size_out) {
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
/* ===== initrd module finder =====
|
||||
*
|
||||
* 在 Limine boot module 中查找 initramfs(路径含 "initrd" 或 "initramfs")。
|
||||
* 返回模块数据指针,*size_out 为大小;未找到返回 NULL。 */
|
||||
static void *linux_find_initrd_module(u64 *size_out) {
|
||||
if (!g_module_request.response) return (void *)0;
|
||||
|
||||
struct limine_module_response *resp = g_module_request.response;
|
||||
for (u64 i = 0; i < resp->module_count; i++) {
|
||||
struct limine_file *file = resp->modules[i];
|
||||
if (!file || !file->path) continue;
|
||||
|
||||
const char *p = file->path;
|
||||
int match = 0;
|
||||
for (const char *s = p; *s; s++) {
|
||||
/* 匹配 "initrd"(如 linux-initrd.img) */
|
||||
if ((s[0] == 'i' || s[0] == 'I') &&
|
||||
(s[1] == 'n' || s[1] == 'N') &&
|
||||
(s[2] == 'i' || s[2] == 'I') &&
|
||||
(s[3] == 't' || s[3] == 'T') &&
|
||||
(s[4] == 'r' || s[4] == 'R') &&
|
||||
(s[5] == 'd' || s[5] == 'D')) {
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) continue;
|
||||
|
||||
if (size_out) *size_out = file->size;
|
||||
log_info("[LINUX] found initrd module:");
|
||||
log_info(file->path);
|
||||
log_hex64("[LINUX] initrd size=", file->size);
|
||||
return file->address;
|
||||
}
|
||||
|
||||
log_warn("[LINUX] no initrd module found");
|
||||
return (void *)0;
|
||||
}
|
||||
|
||||
/* ===== bzImage parser ===== */
|
||||
|
||||
int linux_parse_bzimage(const void *bzimage, u64 size,
|
||||
@@ -516,9 +555,39 @@ int linux_loader_init(void) {
|
||||
g_guest.cmdline_gpa = LINUX_GUEST_CMDLINE_GPA;
|
||||
g_guest.cmdline_size = cmdline_len;
|
||||
|
||||
/* For now, no initrd (Phase 1.3+ will add it) */
|
||||
/* 8.5. Load initrd (busybox initramfs) if present.
|
||||
* initrd 放在 GPA 0x03000000(initrd 区,上限 16MB 到 IPC 区 0x04000000)。 */
|
||||
g_guest.initrd_gpa = 0;
|
||||
g_guest.initrd_size = 0;
|
||||
{
|
||||
u64 initrd_size = 0;
|
||||
void *initrd = linux_find_initrd_module(&initrd_size);
|
||||
if (initrd && initrd_size > 0) {
|
||||
u64 initrd_max = LINUX_GUEST_IPC_SHM_GPA - LINUX_GUEST_INITRD_GPA; /* 16MB */
|
||||
if (initrd_size > initrd_max) {
|
||||
log_error("[LINUX] initrd too large (>16MB), skipped");
|
||||
} else {
|
||||
u64 initrd_pages = (initrd_size + 4095) / 4096;
|
||||
dkm_dma_buffer initrd_buf;
|
||||
if (dma_alloc_pages(initrd_pages, 4096, 0, &initrd_buf) != 0) {
|
||||
log_error("[LINUX] failed to alloc initrd memory");
|
||||
return -17;
|
||||
}
|
||||
mem_copy(initrd_buf.virt, initrd, initrd_size);
|
||||
if (ept_map_range(LINUX_GUEST_INITRD_GPA, initrd_buf.phys,
|
||||
initrd_pages * 4096, EPT_READ | EPT_WRITE) != 0) {
|
||||
log_error("[LINUX] EPT map initrd failed");
|
||||
return -18;
|
||||
}
|
||||
g_guest.initrd_gpa = LINUX_GUEST_INITRD_GPA;
|
||||
g_guest.initrd_size = initrd_size;
|
||||
log_hex64("[LINUX] initrd mapped GPA=", g_guest.initrd_gpa);
|
||||
log_hex64("[LINUX] initrd size=", g_guest.initrd_size);
|
||||
}
|
||||
} else {
|
||||
log_warn("[LINUX] booting without initrd (no userland!)");
|
||||
}
|
||||
}
|
||||
|
||||
setup_boot_params(bp_buf.virt, g_guest.bootparams_gpa,
|
||||
g_guest.kernel_entry,
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
/* virtio_blk.c — UTSM virtio-blk 设备模拟后端。
|
||||
*
|
||||
* 为 Linux guest 提供 virtio-blk 块设备。guest 的块读写请求经
|
||||
* virtqueue 传入,本后端桥接到 UTSM 真实块设备(block API,AHCI/SATA)。
|
||||
*
|
||||
* Virtio Spec 1.1 §5.2 (Block Device)。
|
||||
* 请求描述符链: [header(16B, R)] [data(N*512B, R/W)] [status(1B, W)]
|
||||
* 支持的操作:VIRTIO_BLK_T_IN(读)、VIRTIO_BLK_T_OUT(写)、VIRTIO_BLK_T_GET_ID。
|
||||
*
|
||||
* 设计说明:
|
||||
* - 桥接到 UTSM block API 的第 0 个设备(ahci0 SATA 盘)。
|
||||
* - guest 把 Arch rootfs 镜像当作 /dev/vda 挂载;镜像需预先写入 SATA 盘
|
||||
* 的某个分区/LBA 区间(由 mkfat32/build 流程或独立 raw 区提供)。
|
||||
* - 当前为最小可用:扇区 512B,单队列,无 flush/discard。
|
||||
*/
|
||||
|
||||
#include <utsm/virtio_mmio.h>
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/block.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* virtio-blk 请求头(Virtio Spec 1.1 §5.2.6.2) */
|
||||
struct virtio_blk_outhdr {
|
||||
u32 type; /* VIRTIO_BLK_T_* */
|
||||
u32 reserved; /* (ioprio) */
|
||||
u64 sector; /* 起始扇区(512B 单位) */
|
||||
} __attribute__((packed));
|
||||
|
||||
#define VIRTIO_BLK_T_IN 0 /* 读 */
|
||||
#define VIRTIO_BLK_T_OUT 1 /* 写 */
|
||||
#define VIRTIO_BLK_T_FLUSH 4
|
||||
#define VIRTIO_BLK_T_GET_ID 8
|
||||
|
||||
#define VIRTIO_BLK_S_OK 0
|
||||
#define VIRTIO_BLK_S_IOERR 1
|
||||
#define VIRTIO_BLK_S_UNSUPP 2
|
||||
|
||||
/* feature bits */
|
||||
#define VIRTIO_BLK_F_SIZE_MAX 1
|
||||
#define VIRTIO_BLK_F_SEG_MAX 2
|
||||
#define VIRTIO_BLK_F_BLK_SIZE 6
|
||||
#define VIRTIO_BLK_F_FLUSH 9
|
||||
|
||||
/* device config 空间(VIRTIO_MMIO_CONFIG 起) */
|
||||
struct virtio_blk_config {
|
||||
u64 capacity; /* 总扇区数(512B) */
|
||||
u32 size_max;
|
||||
u32 seg_max;
|
||||
u16 geometry[4];
|
||||
u32 blk_size;
|
||||
/* ... 其余字段省略 */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* 后端状态 */
|
||||
static u64 g_blk_capacity; /* 扇区数 */
|
||||
static u32 g_blk_sector_size = 512;
|
||||
static int g_blk_bound = -1; /* 绑定的 UTSM block 设备索引 */
|
||||
|
||||
/* 读取 device config 空间 */
|
||||
static u32 blk_read_config(u32 offset, int width) {
|
||||
(void)width;
|
||||
struct virtio_blk_config cfg;
|
||||
/* 填零后逐字段读取 */
|
||||
u8 *c = (u8 *)&cfg;
|
||||
for (u32 i = 0; i < sizeof(cfg); i++) c[i] = 0;
|
||||
cfg.capacity = g_blk_capacity;
|
||||
cfg.size_max = 128; /* 单请求最大 128 扇区 */
|
||||
cfg.seg_max = 32;
|
||||
cfg.blk_size = g_blk_sector_size;
|
||||
|
||||
/* 按 offset 返回 32 位窗口 */
|
||||
if (offset + 4 <= sizeof(cfg)) {
|
||||
u32 v;
|
||||
u8 *src = (u8 *)&cfg + offset;
|
||||
v = (u32)src[0] | ((u32)src[1] << 8) | ((u32)src[2] << 16) | ((u32)src[3] << 24);
|
||||
return v;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 处理一个块请求描述符链。返回写入 guest 的字节数(用于 used.len)。 */
|
||||
static u32 blk_handle_chain(struct virtq_desc *desc, u16 head, u32 qnum) {
|
||||
(void)qnum;
|
||||
if (!desc) return 0;
|
||||
|
||||
/* 第一个描述符:请求头(driver→device,16 字节) */
|
||||
struct virtio_blk_outhdr *hdr =
|
||||
(struct virtio_blk_outhdr *)virtio_gpa_to_host(desc[head].addr);
|
||||
if (!hdr) return 0;
|
||||
|
||||
u32 type = hdr->type;
|
||||
u64 sector = hdr->sector;
|
||||
|
||||
/* 沿描述符链找到 data 与 status 描述符 */
|
||||
u16 cur = head;
|
||||
u16 data_idx = 0xFFFF, status_idx = 0xFFFF;
|
||||
int hops = 0;
|
||||
if (desc[cur].flags & VIRTQ_DESC_F_NEXT) {
|
||||
data_idx = desc[cur].next;
|
||||
cur = data_idx;
|
||||
/* data 之后可能还有 status */
|
||||
while ((desc[cur].flags & VIRTQ_DESC_F_NEXT) && hops < 16) {
|
||||
cur = desc[cur].next;
|
||||
hops++;
|
||||
}
|
||||
/* 最后一个是 status(device write,1 字节) */
|
||||
status_idx = cur;
|
||||
}
|
||||
|
||||
u8 *status = (u8 *)0;
|
||||
if (status_idx != 0xFFFF && (desc[status_idx].flags & VIRTQ_DESC_F_WRITE)) {
|
||||
status = (u8 *)virtio_gpa_to_host(desc[status_idx].addr);
|
||||
}
|
||||
|
||||
u32 bytes_done = 0;
|
||||
u8 result = VIRTIO_BLK_S_OK;
|
||||
|
||||
const dkm_block_api *blk = block_get_api();
|
||||
|
||||
switch (type) {
|
||||
case VIRTIO_BLK_T_IN: { /* 读:块设备 → guest data buffer */
|
||||
if (data_idx == 0xFFFF || g_blk_bound < 0 || !blk) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
break;
|
||||
}
|
||||
u8 *dbuf = (u8 *)virtio_gpa_to_host(desc[data_idx].addr);
|
||||
u32 dlen = desc[data_idx].len;
|
||||
if (!dbuf) { result = VIRTIO_BLK_S_IOERR; break; }
|
||||
u32 nsec = dlen / 512;
|
||||
if (nsec == 0) nsec = 1;
|
||||
if (blk->read((u32)g_blk_bound, sector, nsec, dbuf) != 0) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
} else {
|
||||
bytes_done = dlen;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VIRTIO_BLK_T_OUT: { /* 写:guest data buffer → 块设备 */
|
||||
if (data_idx == 0xFFFF || g_blk_bound < 0 || !blk) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
break;
|
||||
}
|
||||
const u8 *dbuf = (const u8 *)virtio_gpa_to_host(desc[data_idx].addr);
|
||||
u32 dlen = desc[data_idx].len;
|
||||
if (!dbuf) { result = VIRTIO_BLK_S_IOERR; break; }
|
||||
u32 nsec = dlen / 512;
|
||||
if (nsec == 0) nsec = 1;
|
||||
if (blk->write((u32)g_blk_bound, sector, nsec, dbuf) != 0) {
|
||||
result = VIRTIO_BLK_S_IOERR;
|
||||
} else {
|
||||
bytes_done = 0; /* 写操作 used.len=0 */
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VIRTIO_BLK_T_GET_ID: {
|
||||
if (data_idx != 0xFFFF) {
|
||||
char *idbuf = (char *)virtio_gpa_to_host(desc[data_idx].addr);
|
||||
if (idbuf) {
|
||||
const char *id = "DESHAB-VIRTIO-BLK";
|
||||
u32 i = 0;
|
||||
for (; id[i] && i < desc[data_idx].len - 1; i++) idbuf[i] = id[i];
|
||||
idbuf[i] = 0;
|
||||
bytes_done = i + 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VIRTIO_BLK_T_FLUSH:
|
||||
result = VIRTIO_BLK_S_OK; /* 无写缓存,直接成功 */
|
||||
break;
|
||||
default:
|
||||
result = VIRTIO_BLK_S_UNSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
/* 写状态字节 */
|
||||
if (status) *status = result;
|
||||
return bytes_done;
|
||||
}
|
||||
|
||||
/* queue_notify:guest 提交了块请求,处理队列。 */
|
||||
static u16 g_blk_last_avail;
|
||||
|
||||
static void blk_queue_notify(u32 queue_idx) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
|
||||
if (virtio_queue_get_ptrs(VIRTIO_ID_BLOCK, queue_idx,
|
||||
&desc, &avail, &used, &qnum) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* 处理自上次以来的所有新 avail 请求 */
|
||||
u16 cur = g_blk_last_avail;
|
||||
u16 used_idx = used->idx;
|
||||
while (cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
u32 len = blk_handle_chain(desc, head, qnum);
|
||||
/* 写 used 回执 */
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = len;
|
||||
used_idx++;
|
||||
cur++;
|
||||
}
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
g_blk_last_avail = cur;
|
||||
}
|
||||
|
||||
static void blk_reset(void) {
|
||||
g_blk_last_avail = 0;
|
||||
}
|
||||
|
||||
/* 后端结构体 */
|
||||
static struct virtio_backend g_blk_backend = {
|
||||
.device_id = VIRTIO_ID_BLOCK,
|
||||
.gpa_base = VIRTIO_MMIO_BLK_GPA,
|
||||
.irq = 5, /* guest ISA IRQ5(cmdline :5,vector 0x35) */
|
||||
.num_queues = 1,
|
||||
.queue_size = 128,
|
||||
.device_features = (1ULL << VIRTIO_F_VERSION_1) |
|
||||
(1ULL << VIRTIO_BLK_F_SIZE_MAX) |
|
||||
(1ULL << VIRTIO_BLK_F_SEG_MAX) |
|
||||
(1ULL << VIRTIO_BLK_F_BLK_SIZE) |
|
||||
(1ULL << VIRTIO_BLK_F_FLUSH),
|
||||
.config_len = sizeof(struct virtio_blk_config),
|
||||
.read_config = blk_read_config,
|
||||
.queue_notify = blk_queue_notify,
|
||||
.reset = blk_reset,
|
||||
};
|
||||
|
||||
void virtio_blk_backend_init(void) {
|
||||
/* 绑定 UTSM 第 0 个块设备(ahci0 SATA 盘)作为后端存储 */
|
||||
const dkm_block_api *blk = block_get_api();
|
||||
if (blk && blk->device_count() > 0) {
|
||||
g_blk_bound = 0;
|
||||
u64 ssize = blk->sector_size(0);
|
||||
if (ssize > 0) g_blk_sector_size = (u32)ssize;
|
||||
/* 从设备名推算容量不可得,用默认值(guest 端可再探测)。
|
||||
* 实际 capacity 需读 ATA IDENTIFY;这里给 4GB 占位,
|
||||
* guest mkfs/mount 会按此上限工作。 */
|
||||
g_blk_capacity = 8ULL * 1024 * 1024 * 1024 / 512; /* 8GB in sectors */
|
||||
log_info("[VBLK] bound to UTSM block device 0");
|
||||
log_hex64("[VBLK] capacity(sectors)=", g_blk_capacity);
|
||||
} else {
|
||||
g_blk_bound = -1;
|
||||
g_blk_capacity = 0;
|
||||
log_warn("[VBLK] no UTSM block device, blk backend degraded");
|
||||
}
|
||||
|
||||
virtio_mmio_register(&g_blk_backend);
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
/* virtio_mmio.c — UTSM virtio-mmio 设备模拟后端框架实现。
|
||||
*
|
||||
* 管理一组 virtio_backend 设备(block + net),处理 guest 对 MMIO 寄存器
|
||||
* 的读/写。寄存器语义遵循 Virtio Spec 1.1 §4.2(modern,version=2)。
|
||||
*
|
||||
* guest 设备发现:cmdline virtio_mmio.device=SIZE@GPA:IRQ 参数(见
|
||||
* linux_loader 的 cmdline 注入)。QUEUE_NOTIFY 处理完后经
|
||||
* vmx_guest_queue_irq 注入虚拟中断(legacy PIC,vector=0x30+irq)。
|
||||
*/
|
||||
|
||||
#include <utsm/virtio_mmio.h>
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/ept.h>
|
||||
#include <utsm/vmx.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
|
||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||
|
||||
#define VIRTIO_MMIO_MAGIC 0x74726976 /* "virt" */
|
||||
#define VIRTIO_MMIO_VERSION_MODERN 2
|
||||
#define MAX_VIRTIO_DEVICES 4
|
||||
|
||||
/* 每个 virtio 设备的运行状态(框架层维护,后端无关) */
|
||||
struct virtio_dev_state {
|
||||
struct virtio_backend *be; /* 后端驱动(NULL = 未注册) */
|
||||
u32 status; /* device status */
|
||||
u32 drv_features_sel; /* DRIVER_FEATURES_SEL */
|
||||
u32 dev_features_sel; /* DEVICE_FEATURES_SEL */
|
||||
u64 driver_features; /* guest 协商的 features */
|
||||
u32 queue_sel; /* 当前选中队列 */
|
||||
u32 interrupt_status; /* INTERRUPT_STATUS */
|
||||
u32 config_generation;
|
||||
|
||||
/* 每队列状态 */
|
||||
struct {
|
||||
u32 num; /* QUEUE_NUM(实际启用大小) */
|
||||
u32 ready; /* QUEUE_READY */
|
||||
u64 desc_gpa; /* 描述符表 GPA */
|
||||
u64 driver_gpa; /* avail ring GPA */
|
||||
u64 device_gpa; /* used ring GPA */
|
||||
} q[4];
|
||||
};
|
||||
|
||||
static struct virtio_dev_state g_vdevs[MAX_VIRTIO_DEVICES];
|
||||
static int g_vdev_count;
|
||||
static int g_vio_ready;
|
||||
|
||||
/* ===== guest 内存访问辅助 ===== */
|
||||
|
||||
void *virtio_gpa_to_host(u64 gpa) {
|
||||
u64 hpa = ept_gpa_to_hpa(gpa);
|
||||
if (hpa == 0) return (void *)0;
|
||||
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
|
||||
return (void *)(hhdm + hpa);
|
||||
}
|
||||
|
||||
/* ===== 设备注册 ===== */
|
||||
|
||||
int virtio_mmio_register(struct virtio_backend *dev) {
|
||||
if (!dev || g_vdev_count >= MAX_VIRTIO_DEVICES) return -1;
|
||||
struct virtio_dev_state *s = &g_vdevs[g_vdev_count];
|
||||
s->be = dev;
|
||||
s->status = 0;
|
||||
s->drv_features_sel = 0;
|
||||
s->dev_features_sel = 0;
|
||||
s->driver_features = 0;
|
||||
s->queue_sel = 0;
|
||||
s->interrupt_status = 0;
|
||||
s->config_generation = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
s->q[i].num = 0;
|
||||
s->q[i].ready = 0;
|
||||
s->q[i].desc_gpa = 0;
|
||||
s->q[i].driver_gpa = 0;
|
||||
s->q[i].device_gpa = 0;
|
||||
}
|
||||
g_vdev_count++;
|
||||
log_hex64("[VIO] registered device id=", dev->device_id);
|
||||
log_hex64("[VIO] gpa=", dev->gpa_base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 按 GPA 查找设备(含该设备的 4KB 寄存器区)。 */
|
||||
static struct virtio_dev_state *find_dev(u64 gpa, u32 *offset_out) {
|
||||
for (int i = 0; i < g_vdev_count; i++) {
|
||||
u64 base = g_vdevs[i].be->gpa_base;
|
||||
if (gpa >= base && gpa < base + 0x1000) {
|
||||
if (offset_out) *offset_out = (u32)(gpa - base);
|
||||
return &g_vdevs[i];
|
||||
}
|
||||
}
|
||||
return (struct virtio_dev_state *)0;
|
||||
}
|
||||
|
||||
/* ===== feature 读取(按 sel 返回 32 位窗口) ===== */
|
||||
|
||||
static u32 read_features(struct virtio_dev_state *s) {
|
||||
if (s->dev_features_sel == 0) {
|
||||
return (u32)(s->be->device_features & 0xFFFFFFFFULL);
|
||||
} else if (s->dev_features_sel == 1) {
|
||||
return (u32)(s->be->device_features >> 32);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== 寄存器读 ===== */
|
||||
|
||||
u64 virtio_mmio_read(u64 gpa, int width) {
|
||||
(void)width; /* virtio-mmio 寄存器固定 32 位访问 */
|
||||
u32 off;
|
||||
struct virtio_dev_state *s = find_dev(gpa, &off);
|
||||
if (!s || !s->be) {
|
||||
log_hex64("[VIO] read unmapped gpa=", gpa);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (off) {
|
||||
case VIRTIO_MMIO_MAGIC_VALUE: return VIRTIO_MMIO_MAGIC;
|
||||
case VIRTIO_MMIO_VERSION: return VIRTIO_MMIO_VERSION_MODERN;
|
||||
case VIRTIO_MMIO_DEVICE_ID: return s->be->device_id;
|
||||
case VIRTIO_MMIO_VENDOR_ID: return 0x55444D55; /* "UMDU" 自定义 */
|
||||
case VIRTIO_MMIO_DEVICE_FEATURES:return read_features(s);
|
||||
case VIRTIO_MMIO_QUEUE_NUM_MAX: return s->be->queue_size;
|
||||
case VIRTIO_MMIO_QUEUE_READY: return s->q[s->queue_sel].ready;
|
||||
case VIRTIO_MMIO_INTERRUPT_STATUS:return s->interrupt_status;
|
||||
case VIRTIO_MMIO_STATUS: return s->status;
|
||||
case VIRTIO_MMIO_CONFIG_GENERATION:return s->config_generation;
|
||||
default:
|
||||
/* device config 空间(0x100 起) */
|
||||
if (off >= VIRTIO_MMIO_CONFIG && off < VIRTIO_MMIO_CONFIG + s->be->config_len) {
|
||||
if (s->be->read_config) {
|
||||
return s->be->read_config(off - VIRTIO_MMIO_CONFIG, width);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 寄存器写 ===== */
|
||||
|
||||
void virtio_mmio_write(u64 gpa, u64 value, int width) {
|
||||
(void)width;
|
||||
u32 off;
|
||||
struct virtio_dev_state *s = find_dev(gpa, &off);
|
||||
if (!s || !s->be) {
|
||||
log_hex64("[VIO] write unmapped gpa=", gpa);
|
||||
return;
|
||||
}
|
||||
u32 v32 = (u32)value;
|
||||
|
||||
switch (off) {
|
||||
case VIRTIO_MMIO_DEVICE_FEATURES_SEL:
|
||||
s->dev_features_sel = v32;
|
||||
break;
|
||||
case VIRTIO_MMIO_DRIVER_FEATURES:
|
||||
if (s->drv_features_sel == 0) {
|
||||
s->driver_features = (s->driver_features & 0xFFFFFFFF00000000ULL) | v32;
|
||||
} else if (s->drv_features_sel == 1) {
|
||||
s->driver_features = (s->driver_features & 0xFFFFFFFFULL) | ((u64)v32 << 32);
|
||||
}
|
||||
break;
|
||||
case VIRTIO_MMIO_DRIVER_FEATURES_SEL:
|
||||
s->drv_features_sel = v32;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_SEL:
|
||||
if (v32 < 4) s->queue_sel = v32;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_NUM: {
|
||||
/* 限制在后端 queue_size 内 */
|
||||
if (v32 == 0 || v32 > s->be->queue_size) {
|
||||
s->q[s->queue_sel].num = s->be->queue_size;
|
||||
} else {
|
||||
s->q[s->queue_sel].num = v32;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VIRTIO_MMIO_QUEUE_READY:
|
||||
s->q[s->queue_sel].ready = v32 ? 1 : 0;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_DESC_LOW:
|
||||
s->q[s->queue_sel].desc_gpa = (s->q[s->queue_sel].desc_gpa & 0xFFFFFFFF00000000ULL) | v32;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_DESC_HIGH:
|
||||
s->q[s->queue_sel].desc_gpa = (s->q[s->queue_sel].desc_gpa & 0xFFFFFFFFULL) | ((u64)v32 << 32);
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_DRIVER_LOW:
|
||||
s->q[s->queue_sel].driver_gpa = (s->q[s->queue_sel].driver_gpa & 0xFFFFFFFF00000000ULL) | v32;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_DRIVER_HIGH:
|
||||
s->q[s->queue_sel].driver_gpa = (s->q[s->queue_sel].driver_gpa & 0xFFFFFFFFULL) | ((u64)v32 << 32);
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_DEVICE_LOW:
|
||||
s->q[s->queue_sel].device_gpa = (s->q[s->queue_sel].device_gpa & 0xFFFFFFFF00000000ULL) | v32;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_DEVICE_HIGH:
|
||||
s->q[s->queue_sel].device_gpa = (s->q[s->queue_sel].device_gpa & 0xFFFFFFFFULL) | ((u64)v32 << 32);
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_NOTIFY:
|
||||
/* guest 通知设备处理队列 v32。调用后端处理请求。 */
|
||||
if (s->be->queue_notify) {
|
||||
s->be->queue_notify(v32);
|
||||
}
|
||||
/* 设置中断状态位(used buffer notification)并注入虚拟 IRQ。
|
||||
* legacy PIC 模式:vector = 0x30 + irq(master PIC 基址 0x30)。
|
||||
* 是否真正投递由 vmexit 的 maybe_inject_irq 按 IMR/IF 决定。 */
|
||||
s->interrupt_status |= 1;
|
||||
if (s->be->irq != 0) {
|
||||
vmx_guest_queue_irq(0x30 + s->be->irq);
|
||||
}
|
||||
break;
|
||||
case VIRTIO_MMIO_INTERRUPT_ACK:
|
||||
s->interrupt_status &= ~v32;
|
||||
break;
|
||||
case VIRTIO_MMIO_STATUS:
|
||||
s->status = v32;
|
||||
if (v32 == 0) {
|
||||
/* 复位 */
|
||||
if (s->be->reset) s->be->reset();
|
||||
s->driver_features = 0;
|
||||
s->interrupt_status = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
s->q[i].ready = 0;
|
||||
s->q[i].num = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* device config 空间写(多数设备 config 只读,忽略) */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 队列访问 API 实现 =====
|
||||
*
|
||||
* 后端 queue_notify 调用 virtio_queue_get_ptrs 取队列三个表的 host 地址,
|
||||
* 自行遍历 avail ring 处理请求,写 used ring 回执。 */
|
||||
|
||||
int virtio_queue_get_ptrs(u32 device_id, u32 queue_idx,
|
||||
struct virtq_desc **desc_out,
|
||||
struct virtq_avail **avail_out,
|
||||
struct virtq_used **used_out,
|
||||
u32 *queue_num_out) {
|
||||
struct virtio_dev_state *s = (struct virtio_dev_state *)0;
|
||||
for (int i = 0; i < g_vdev_count; i++) {
|
||||
if (g_vdevs[i].be && g_vdevs[i].be->device_id == device_id) {
|
||||
s = &g_vdevs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!s || queue_idx >= 4) return -1;
|
||||
if (!s->q[queue_idx].ready) return -2;
|
||||
|
||||
struct virtq_desc *desc = (struct virtq_desc *)virtio_gpa_to_host(s->q[queue_idx].desc_gpa);
|
||||
struct virtq_avail *avail = (struct virtq_avail *)virtio_gpa_to_host(s->q[queue_idx].driver_gpa);
|
||||
struct virtq_used *used = (struct virtq_used *)virtio_gpa_to_host(s->q[queue_idx].device_gpa);
|
||||
if (!desc || !avail || !used) return -3;
|
||||
|
||||
if (desc_out) *desc_out = desc;
|
||||
if (avail_out) *avail_out = avail;
|
||||
if (used_out) *used_out = used;
|
||||
if (queue_num_out) *queue_num_out = s->q[queue_idx].num ? s->q[queue_idx].num : s->be->queue_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void virtio_queue_bump_used(struct virtq_used *used, u16 new_idx) {
|
||||
if (!used) return;
|
||||
__asm__ volatile("mfence" ::: "memory");
|
||||
used->idx = new_idx;
|
||||
__asm__ volatile("mfence" ::: "memory");
|
||||
}
|
||||
|
||||
/* ===== 框架初始化 =====
|
||||
*
|
||||
* 注册 block 与 net 后端。后端结构体由 virtio_blk.c / virtio_net.c 提供。 */
|
||||
extern struct virtio_backend g_virtio_blk_backend;
|
||||
extern struct virtio_backend g_virtio_net_backend;
|
||||
|
||||
/* 后端初始化符号(定义于 virtio_blk.c / virtio_net.c)。
|
||||
* 这里声明以便链接;具体注册在各自文件完成。 */
|
||||
void virtio_blk_backend_init(void);
|
||||
void virtio_net_backend_init(void);
|
||||
|
||||
void virtio_mmio_init(void) {
|
||||
if (g_vio_ready) return;
|
||||
g_vdev_count = 0;
|
||||
|
||||
virtio_blk_backend_init();
|
||||
virtio_net_backend_init();
|
||||
|
||||
g_vio_ready = 1;
|
||||
log_info("[VIO] virtio-mmio backend init ok");
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
/* virtio_net.c — UTSM virtio-net 设备模拟后端。
|
||||
*
|
||||
* 为 Linux guest 提供 virtio-net 网卡。guest 的收发包请求经 virtqueue
|
||||
* 传入,本后端桥接到 UTSM 真实网卡(net API,e1000/virtio_net DKM 驱动)。
|
||||
*
|
||||
* Virtio Spec 1.1 §5.1 (Network Device)。
|
||||
* 队列:0=RX(device→driver 收包),1=TX(driver→device 发包)。
|
||||
* 每帧前置 virtio_net_hdr(10/12 字节,无 GSO 时全零)。
|
||||
*
|
||||
* 最小可用实现:
|
||||
* - 无 GSO/TSO/checksum offload(feature 不声明,guest 走软件路径)
|
||||
* - RX 轮询:guest RX 队列提交空 buffer,queue_notify 时尝试 rx_poll
|
||||
* - TX:queue_notify 时取帧直接 net->tx 发出
|
||||
*/
|
||||
|
||||
#include <utsm/virtio_mmio.h>
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/net.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
|
||||
/* virtio_net_hdr(VIRTIO_NET_F_CSUM/GSO 未协商时为 10 字节 legacy) */
|
||||
struct virtio_net_hdr {
|
||||
u8 flags;
|
||||
u8 gso_type;
|
||||
u16 hdr_len;
|
||||
u16 gso_size;
|
||||
u16 csum_start;
|
||||
u16 csum_offset;
|
||||
/* u16 num_buffers; // 仅 VIRTIO_NET_F_MRG_RXBUF 协商后存在 */
|
||||
} __attribute__((packed));
|
||||
|
||||
#define VIRTIO_NET_HDR_LEN 10
|
||||
|
||||
/* feature bits(我们都不声明,保持最简) */
|
||||
#define VIRTIO_NET_F_CSUM 0
|
||||
#define VIRTIO_NET_F_GUEST_CSUM 1
|
||||
#define VIRTIO_NET_F_MAC 5
|
||||
#define VIRTIO_NET_F_STATUS 16
|
||||
|
||||
/* device config(VIRTIO_MMIO_CONFIG 起) */
|
||||
struct virtio_net_config {
|
||||
u8 mac[6];
|
||||
u16 status; /* bit0 = link up */
|
||||
u16 max_virtqueue_pairs;
|
||||
} __attribute__((packed));
|
||||
|
||||
static u8 g_net_mac[6] = { 0x52, 0x54, 0x00, 0xDE, 0x5A, 0x01 };
|
||||
static int g_net_bound = -1;
|
||||
|
||||
static u32 net_read_config(u32 offset, int width) {
|
||||
(void)width;
|
||||
struct virtio_net_config cfg;
|
||||
u8 *c = (u8 *)&cfg;
|
||||
for (u32 i = 0; i < sizeof(cfg); i++) c[i] = 0;
|
||||
for (int i = 0; i < 6; i++) cfg.mac[i] = g_net_mac[i];
|
||||
cfg.status = 1; /* link up */
|
||||
cfg.max_virtqueue_pairs = 1;
|
||||
|
||||
if (offset + 4 <= sizeof(cfg)) {
|
||||
u8 *src = (u8 *)&cfg + offset;
|
||||
return (u32)src[0] | ((u32)src[1] << 8) | ((u32)src[2] << 16) | ((u32)src[3] << 24);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 从描述符链拼接一个连续帧(virtio-net 帧通常 2 个描述符:hdr + data)。
|
||||
* 返回帧长度,数据拷入 out_buf(out_cap 容量)。 */
|
||||
static u32 net_gather_frame(struct virtq_desc *desc, u16 head,
|
||||
u8 *out_buf, u32 out_cap, int skip_hdr) {
|
||||
u32 total = 0;
|
||||
u16 cur = head;
|
||||
int hops = 0;
|
||||
while (hops < 32) {
|
||||
u8 *src = (u8 *)virtio_gpa_to_host(desc[cur].addr);
|
||||
u32 len = desc[cur].len;
|
||||
if (!src) break;
|
||||
|
||||
/* 跳过第一个描述符中的 virtio_net_hdr */
|
||||
if (hops == 0 && skip_hdr && len > VIRTIO_NET_HDR_LEN) {
|
||||
u32 dl = len - VIRTIO_NET_HDR_LEN;
|
||||
if (total + dl <= out_cap) {
|
||||
u8 *d = src + VIRTIO_NET_HDR_LEN;
|
||||
for (u32 i = 0; i < dl; i++) out_buf[total + i] = d[i];
|
||||
total += dl;
|
||||
}
|
||||
} else {
|
||||
if (total + len <= out_cap) {
|
||||
for (u32 i = 0; i < len; i++) out_buf[total + i] = src[i];
|
||||
total += len;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(desc[cur].flags & VIRTQ_DESC_F_NEXT)) break;
|
||||
cur = desc[cur].next;
|
||||
hops++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/* TX queue(idx=1):guest 发包。 */
|
||||
static u16 g_tx_last_avail;
|
||||
static u8 g_tx_scratch[2048];
|
||||
|
||||
static void net_handle_tx(void) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
if (virtio_queue_get_ptrs(VIRTIO_ID_NET, 1, &desc, &avail, &used, &qnum) != 0)
|
||||
return;
|
||||
|
||||
const dkm_net_api *net = net_get_api();
|
||||
u16 cur = g_tx_last_avail;
|
||||
u16 used_idx = used->idx;
|
||||
while (cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
u32 flen = net_gather_frame(desc, head, g_tx_scratch, sizeof(g_tx_scratch), 1);
|
||||
if (flen > 0 && net && g_net_bound >= 0) {
|
||||
net->tx((u32)g_net_bound, g_tx_scratch, flen);
|
||||
}
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = 0;
|
||||
used_idx++;
|
||||
cur++;
|
||||
}
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
g_tx_last_avail = cur;
|
||||
}
|
||||
|
||||
/* RX queue(idx=0):guest 提交空 buffer,我们尝试 rx_poll 填充。
|
||||
* guest 每提交一批 buffer 就 notify 一次;有包则填充 + used,无包则
|
||||
* 保留 buffer 等下次 notify(简化:直接返回 0 长度 used,让 guest 重投)。 */
|
||||
static u16 g_rx_last_avail;
|
||||
|
||||
static void net_handle_rx(void) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
if (virtio_queue_get_ptrs(VIRTIO_ID_NET, 0, &desc, &avail, &used, &qnum) != 0)
|
||||
return;
|
||||
|
||||
const dkm_net_api *net = net_get_api();
|
||||
if (!net || g_net_bound < 0) return;
|
||||
|
||||
u16 cur = g_rx_last_avail;
|
||||
u16 used_idx = used->idx;
|
||||
while (cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
/* RX 描述符链:hdr(W) + data(W)。尝试收一包。 */
|
||||
u16 curd = head;
|
||||
u8 *hdrp = (u8 *)virtio_gpa_to_host(desc[curd].addr);
|
||||
u8 *datap = (u8 *)0;
|
||||
u32 datalen = 0;
|
||||
if (desc[curd].flags & VIRTQ_DESC_F_NEXT) {
|
||||
u16 di = desc[curd].next;
|
||||
datap = (u8 *)virtio_gpa_to_host(desc[di].addr);
|
||||
datalen = desc[di].len;
|
||||
}
|
||||
if (!hdrp || !datap || datalen == 0) { cur++; continue; }
|
||||
|
||||
u32 got = 0;
|
||||
if (net->rx_poll((u32)g_net_bound, datap, datalen, &got) == 0 && got > 0) {
|
||||
/* 填 virtio_net_hdr 全零(无 GSO) */
|
||||
for (int i = 0; i < VIRTIO_NET_HDR_LEN; i++) hdrp[i] = 0;
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = VIRTIO_NET_HDR_LEN + got;
|
||||
used_idx++;
|
||||
} else {
|
||||
/* 无包:不消耗该 buffer,留待下次(但为避免死循环,跳过) */
|
||||
}
|
||||
cur++;
|
||||
}
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
g_rx_last_avail = cur;
|
||||
}
|
||||
|
||||
static void net_queue_notify(u32 queue_idx) {
|
||||
if (queue_idx == 1) net_handle_tx();
|
||||
else if (queue_idx == 0) net_handle_rx();
|
||||
}
|
||||
|
||||
static void net_reset(void) {
|
||||
g_tx_last_avail = 0;
|
||||
g_rx_last_avail = 0;
|
||||
}
|
||||
|
||||
static struct virtio_backend g_net_backend = {
|
||||
.device_id = VIRTIO_ID_NET,
|
||||
.gpa_base = VIRTIO_MMIO_NET_GPA,
|
||||
.irq = 6, /* guest ISA IRQ6(cmdline :6,vector 0x36) */
|
||||
.num_queues = 2,
|
||||
.queue_size = 128,
|
||||
.device_features = (1ULL << VIRTIO_F_VERSION_1) |
|
||||
(1ULL << VIRTIO_NET_F_MAC) | (1ULL << VIRTIO_NET_F_STATUS),
|
||||
.config_len = sizeof(struct virtio_net_config),
|
||||
.read_config = net_read_config,
|
||||
.queue_notify = net_queue_notify,
|
||||
.reset = net_reset,
|
||||
};
|
||||
|
||||
void virtio_net_backend_init(void) {
|
||||
/* 绑定 UTSM 第 0 个网卡(e1000 或 virtio_net DKM 驱动) */
|
||||
const dkm_net_api *net = net_get_api();
|
||||
if (net && net->device_count() > 0) {
|
||||
g_net_bound = 0;
|
||||
dkm_net_device_info info;
|
||||
if (net->device_info(0, &info) == 0) {
|
||||
for (int i = 0; i < 6; i++) g_net_mac[i] = info.mac[i];
|
||||
}
|
||||
log_info("[VNET] bound to UTSM net device 0");
|
||||
} else {
|
||||
g_net_bound = -1;
|
||||
log_warn("[VNET] no UTSM net device, net backend degraded");
|
||||
}
|
||||
virtio_mmio_register(&g_net_backend);
|
||||
}
|
||||
+638
-15
@@ -5,15 +5,35 @@
|
||||
#include <utsm/ipc_shm.h>
|
||||
#include <utsm/log.h>
|
||||
#include <utsm/types.h>
|
||||
#include <utsm/dma.h>
|
||||
#include <utsm/linux_loader.h>
|
||||
#include <utsm/virtio_mmio.h>
|
||||
#include "../arch/x86_64/limine.h"
|
||||
|
||||
/* VM-Exit 处理器:从 VMCS 读取 exit reason 与 guest 状态,分发处理。
|
||||
*
|
||||
* Phase 1.1:处理 HLT(终止 guest)、EPT violation(动态映射)、异常。
|
||||
* Phase 1.3:增加 VMCALL hypercall 处理(读 guest GPR、分发、写返回值)。
|
||||
* Phase 1.4:EPT violation 区分 RAM(按需分配 HPA)与 virtio-mmio
|
||||
* (路由到设备模拟寄存器读写,不映射真实内存)。
|
||||
* Phase 1.5:guest 平台设备模拟(legacy 模式,配合 noapic/nolapic):
|
||||
* - COM1 8250 UART(console=ttyS0 输出 + host 串口输入桥)
|
||||
* - 8259 PIC(IMR 跟踪,EOI/ICW 吞掉)
|
||||
* - 8254 PIT ch0(divisor 跟踪,按 guest 编程频率注入 IRQ0 tick)
|
||||
* - CPUID passthrough(屏蔽 VMX/hypervisor 位)
|
||||
* - 虚拟中断注入(pending queue + interrupt-window exiting)
|
||||
* - VMX preemption timer 周期 exit(轮询 host 串口 RX + tick)
|
||||
*/
|
||||
|
||||
extern volatile struct limine_hhdm_request g_hhdm_request;
|
||||
|
||||
extern void vmx_vmresume_wrapper(void);
|
||||
|
||||
/* serial.c 导出的 host 串口与 TSC 频率接口 */
|
||||
extern int serial_try_read(void);
|
||||
extern u64 serial_tsc_per_ms(void);
|
||||
extern void serial_putc(char c);
|
||||
|
||||
static u64 g_vmexit_count;
|
||||
|
||||
static void log_exit_diagnostics(u64 reason, u64 qualification, u64 rip, u64 len) {
|
||||
@@ -41,16 +61,595 @@ static int handle_hlt(u64 rip, u64 instr_len, int *out_resume) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 处理 EPT violation:动态映射缺失的 GPA。
|
||||
* Phase 1.1:1:1 映射缺失的页面,权限 RWX。 */
|
||||
static int handle_ept_violation(u64 qualification, int *out_resume) {
|
||||
/* ===== EPT violation: guest RAM 按需分配 =====
|
||||
*
|
||||
* guest 首次访问某 GPA 页时触发 EPT violation。我们按需分配一个
|
||||
* 4KB HPA 页并建立 GPA→HPA 映射(不再是错误的 1:1 恒等映射——
|
||||
* 那会把 guest GPA 直接当 host 物理地址,可能与 UTSM 自身内存冲突)。
|
||||
*
|
||||
* 已映射区域的访问权限不足(如先 R 映射后写)也走这里:升级为 RWX。
|
||||
*/
|
||||
|
||||
/* 按需为普通 guest RAM GPA 分配一个 HPA 页并映射。返回 0 成功。 */
|
||||
static int ept_map_guest_ram_page(u64 gpa_page) {
|
||||
dkm_dma_buffer buf;
|
||||
if (dma_alloc_pages(1, EPT_PAGE_SIZE, 0, &buf) != 0) {
|
||||
log_error("[VMEXIT] OOM for guest RAM page");
|
||||
return -1;
|
||||
}
|
||||
if (ept_map_range(gpa_page, buf.phys, EPT_PAGE_SIZE, EPT_RWX) != 0) {
|
||||
log_error("[VMEXIT] EPT map guest RAM page failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== 虚拟中断注入(legacy PIC 模式,vector = 0x30 + irq) =====
|
||||
*
|
||||
* Linux 以 noapic/nolapic 启动时走 XT-PIC:master PIC 向量基址 0x30。
|
||||
* 我们向 guest 注入的外部中断只能是这些 ISA IRQ 向量:
|
||||
* IRQ0 (0x30) PIT tick、IRQ4 (0x34) COM1、IRQ5 (0x35) virtio-blk、
|
||||
* IRQ6 (0x36) virtio-net。
|
||||
*
|
||||
* 注入条件:guest RFLAGS.IF=1 且 interruptibility=0 且 IMR 未 mask。
|
||||
* 不满足时 arm CPU_BASED_INTR_WINDOW_EXITING,guest 一旦开中断
|
||||
* 立即产生 interrupt-window exit,届时再注入。
|
||||
*/
|
||||
|
||||
#define IRQ_PENDING_MAX 16
|
||||
static u8 g_pending_vectors[IRQ_PENDING_MAX];
|
||||
static int g_pending_head, g_pending_tail;
|
||||
|
||||
/* guest 8259 PIC 状态(master) */
|
||||
static u8 g_pic_imr = 0xFF; /* 初始全 mask */
|
||||
static int g_pic_icw_state = 0; /* 0=就绪(OCW), 1=等ICW2, 2=等ICW3, 3=等ICW4 */
|
||||
static int g_pic_icw_sngl = 0;
|
||||
static int g_pic_icw_need4 = 0;
|
||||
|
||||
void vmx_guest_queue_irq(u32 vector) {
|
||||
/* 合并连续相同向量(PIT tick 合并 / COM1 RX 去重) */
|
||||
if (g_pending_tail != g_pending_head) {
|
||||
int last = (g_pending_tail + IRQ_PENDING_MAX - 1) % IRQ_PENDING_MAX;
|
||||
if (g_pending_vectors[last] == (u8)vector) return;
|
||||
}
|
||||
int next = (g_pending_tail + 1) % IRQ_PENDING_MAX;
|
||||
if (next == g_pending_head) return; /* 队列满:丢弃 */
|
||||
g_pending_vectors[g_pending_tail] = (u8)vector;
|
||||
g_pending_tail = next;
|
||||
}
|
||||
|
||||
static void maybe_inject_irq(void) {
|
||||
if (g_pending_head == g_pending_tail) return;
|
||||
|
||||
u8 vec = g_pending_vectors[g_pending_head];
|
||||
|
||||
/* IMR 检查:master PIC vector 0x30+n → IMR bit n */
|
||||
if (vec >= 0x30 && vec < 0x38) {
|
||||
if (g_pic_imr & (1u << (vec - 0x30))) {
|
||||
/* 被 guest mask:丢弃(级别触发设备下次 notify 会重发) */
|
||||
g_pending_head = (g_pending_head + 1) % IRQ_PENDING_MAX;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
u64 rflags = vmx_vmcs_read(VMCS_GUEST_RFLAGS);
|
||||
u64 intr_st = vmx_vmcs_read(VMCS_GUEST_INTERRUPTIBILITY);
|
||||
u64 cpu = vmx_vmcs_read(VMCS_CPU_BASED_VM_EXEC_CONTROL);
|
||||
|
||||
if ((rflags & 0x200) && intr_st == 0) {
|
||||
/* 可注入:写 VM-entry interruption-information field */
|
||||
vmx_vmcs_write(VMCS_VM_ENTRY_INTERRUPT_INFO,
|
||||
VM_ENTRY_INTR_INFO_VALID |
|
||||
(VM_ENTRY_INTR_TYPE_HW_IRQ << 8) | vec);
|
||||
g_pending_head = (g_pending_head + 1) % IRQ_PENDING_MAX;
|
||||
if (cpu & CPU_BASED_INTR_WINDOW_EXITING) {
|
||||
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL,
|
||||
cpu & ~CPU_BASED_INTR_WINDOW_EXITING);
|
||||
}
|
||||
} else {
|
||||
/* 暂不可注入:arm interrupt-window exiting */
|
||||
if (!(cpu & CPU_BASED_INTR_WINDOW_EXITING)) {
|
||||
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL,
|
||||
cpu | CPU_BASED_INTR_WINDOW_EXITING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== guest COM1 8250 UART 模拟 =====
|
||||
*
|
||||
* console=ttyS0 的 I/O 端口 0x3F8-0x3FF。
|
||||
* TX:THR 写 → host 串口输出(与 UTSM log 共用 QEMU stdio)。
|
||||
* RX:host 串口输入 → g_guest_rx 环形缓冲 → guest 读 RBR;
|
||||
* 有数据时注入 IRQ4(vector 0x34)。
|
||||
* 寄存器:DLL/DLM/IER/FCR/LCR/MCR/SCR 存储,LSR/IIR/MSR 按状态合成。
|
||||
*/
|
||||
|
||||
#define GUEST_RX_SIZE 256
|
||||
static u8 g_guest_rx[GUEST_RX_SIZE];
|
||||
static int g_rx_head, g_rx_tail;
|
||||
|
||||
static u8 g_com1_dll, g_com1_dlm, g_com1_ier, g_com1_fcr;
|
||||
static u8 g_com1_lcr, g_com1_mcr, g_com1_scr;
|
||||
|
||||
static u64 com1_io(u32 port, int is_in, u64 value) {
|
||||
u32 reg = port - 0x3F8;
|
||||
int dlab = (g_com1_lcr & 0x80) != 0;
|
||||
if (is_in) {
|
||||
switch (reg) {
|
||||
case 0: /* RBR / DLL */
|
||||
if (dlab) return g_com1_dll;
|
||||
if (g_rx_head != g_rx_tail) {
|
||||
u8 b = g_guest_rx[g_rx_head];
|
||||
g_rx_head = (g_rx_head + 1) % GUEST_RX_SIZE;
|
||||
return b;
|
||||
}
|
||||
return 0;
|
||||
case 1: return dlab ? g_com1_dlm : g_com1_ier;
|
||||
case 2: /* IIR:bit0=0 表示有中断;bits7:6=1 表示 FIFO 启用 */
|
||||
if (g_rx_head != g_rx_tail) return 0xC4; /* RX data available */
|
||||
return 0xC1; /* no interrupt pending */
|
||||
case 3: return g_com1_lcr;
|
||||
case 4: return g_com1_mcr;
|
||||
case 5: /* LSR:bit0 DR,bit5 THRE,bit6 TEMT */
|
||||
return ((g_rx_head != g_rx_tail) ? 0x01 : 0x00) | 0x20 | 0x40;
|
||||
case 6: return 0xB0; /* MSR:CTS|DSR|DCD */
|
||||
case 7: return g_com1_scr;
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
switch (reg) {
|
||||
case 0:
|
||||
if (dlab) g_com1_dll = (u8)value;
|
||||
else serial_putc((char)(value & 0xFF)); /* guest → host 串口 */
|
||||
break;
|
||||
case 1: if (dlab) g_com1_dlm = (u8)value; else g_com1_ier = (u8)value; break;
|
||||
case 2: g_com1_fcr = (u8)value; break;
|
||||
case 3: g_com1_lcr = (u8)value; break;
|
||||
case 4: g_com1_mcr = (u8)value; break;
|
||||
case 7: g_com1_scr = (u8)value; break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== guest 8254 PIT ch0 模拟(仅跟踪 divisor,tick 由 preemption exit 驱动) ===== */
|
||||
|
||||
static u16 g_pit_divisor = 0; /* 0 = 65536(默认 18.2Hz) */
|
||||
static int g_pit_load_state = 0; /* 0=等 lo 字节, 1=等 hi 字节 */
|
||||
static u8 g_pit_lo = 0;
|
||||
static u64 g_pit_period_tsc = 0; /* 每 tick 的 TSC 周期 */
|
||||
static u64 g_pit_accum = 0;
|
||||
static u64 g_last_tick_tsc = 0;
|
||||
|
||||
static u64 rdtsc_local(void) {
|
||||
u32 lo, hi;
|
||||
__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
|
||||
return ((u64)hi << 32) | lo;
|
||||
}
|
||||
|
||||
static void pit_recompute_period(void) {
|
||||
u64 tpm = serial_tsc_per_ms();
|
||||
if (tpm == 0) tpm = 2000000; /* 未校准时按 ~2GHz 兜底 */
|
||||
u64 div = g_pit_divisor ? g_pit_divisor : 65536;
|
||||
/* period_tsc = div / 1193182 Hz,用 ms 刻度换算避免溢出 */
|
||||
g_pit_period_tsc = (div * tpm) / 1193; /* ≈ div * tpm * 1000 / 1193182 */
|
||||
if (g_pit_period_tsc == 0) g_pit_period_tsc = 1;
|
||||
}
|
||||
|
||||
static u64 pit_io(u32 port, int is_in, u64 value) {
|
||||
if (is_in) return 0; /* 计数器读回:恒 0(Linux 用 TSC clocksource,不依赖) */
|
||||
if (port == 0x43) {
|
||||
/* mode/command:仅关心 ch0 + lo/hi 访问方式 */
|
||||
if ((value & 0xC0) == 0x00 && (value & 0x30) == 0x30) {
|
||||
g_pit_load_state = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (port == 0x40) {
|
||||
if (g_pit_load_state == 0) {
|
||||
g_pit_lo = (u8)value;
|
||||
g_pit_load_state = 1;
|
||||
} else {
|
||||
g_pit_divisor = (u16)(g_pit_lo | ((u16)(u8)value << 8));
|
||||
g_pit_load_state = 0;
|
||||
pit_recompute_period();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 0; /* 0x41/0x42 吞掉 */
|
||||
}
|
||||
|
||||
/* ===== 其余 legacy 端口(吞掉/返回安全值) ===== */
|
||||
|
||||
static u64 misc_io(u32 port, int is_in, u64 value, int *out_terminate) {
|
||||
/* 8259 PIC master */
|
||||
if (port == 0x20) {
|
||||
if (!is_in) {
|
||||
if (value & 0x10) { /* ICW1 */
|
||||
g_pic_icw_sngl = (value & 0x02) != 0;
|
||||
g_pic_icw_need4 = (value & 0x01) != 0;
|
||||
g_pic_icw_state = 1;
|
||||
g_pic_imr = 0xFF;
|
||||
}
|
||||
/* 其余 OCW2/OCW3(含 EOI)吞掉 */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (port == 0x21) {
|
||||
if (is_in) return g_pic_imr;
|
||||
switch (g_pic_icw_state) {
|
||||
case 1: /* ICW2(向量基址,忽略,固定 0x30) */
|
||||
g_pic_icw_state = g_pic_icw_sngl ? (g_pic_icw_need4 ? 3 : 0) : 2;
|
||||
break;
|
||||
case 2: /* ICW3 */
|
||||
g_pic_icw_state = g_pic_icw_need4 ? 3 : 0;
|
||||
break;
|
||||
case 3: /* ICW4 */
|
||||
g_pic_icw_state = 0;
|
||||
break;
|
||||
default: /* OCW1 = IMR */
|
||||
g_pic_imr = (u8)value;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/* 8259 PIC slave:吞掉(不用) */
|
||||
if (port == 0xA0 || port == 0xA1) return is_in ? 0xFF : 0;
|
||||
|
||||
/* 8254 PIT */
|
||||
if (port >= 0x40 && port <= 0x43) return pit_io(port, is_in, value);
|
||||
|
||||
/* PS/2 键盘/鼠标(i8042):status 读 0(空),data 读 0,写吞掉。
|
||||
* i8042 自检将失败 → guest 无 PS/2 键鼠(console 输入走串口)。 */
|
||||
if (port == 0x60 || port == 0x64) return is_in ? 0 : 0;
|
||||
|
||||
/* CMOS/RTC */
|
||||
if (port == 0x70 || port == 0x71) return is_in ? 0 : 0;
|
||||
|
||||
/* PCI config mechanism #1:对 guest 隐藏整个 PCI 总线。
|
||||
* guest reconfig BAR 会破坏 UTSM 的 AHCI/e1000,故全部返回"无设备"。 */
|
||||
if (port >= 0xCF8 && port <= 0xCFF) return is_in ? 0xFFFFFFFFULL : 0;
|
||||
|
||||
/* 0xCF9 reset control:guest 请求重启 → 终止 guest 回 host */
|
||||
if (port == 0xCF9) {
|
||||
if (!is_in) {
|
||||
log_warn("[VMEXIT] guest requested reboot via 0xCF9");
|
||||
*out_terminate = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* COM2-4:无设备 */
|
||||
if ((port >= 0x2F8 && port <= 0x2FF) ||
|
||||
(port >= 0x3E8 && port <= 0x3EF) ||
|
||||
(port >= 0x2E8 && port <= 0x2EF)) {
|
||||
return is_in ? 0xFF : 0;
|
||||
}
|
||||
|
||||
/* 其余端口(0x80 delay、0x61 NMI、0x92 A20、0xB2 APM、0xF0-0xFF FPU
|
||||
* error、0xED delay、ACPI 等):读 0,写吞掉 */
|
||||
return is_in ? 0 : 0;
|
||||
}
|
||||
|
||||
/* ===== EXIT_IO_INSTRUCTION 处理 =====
|
||||
*
|
||||
* exit qualification(Intel SDM 27.2.4.2? 实为 Vol 3D 表 27-6):
|
||||
* bits 2:0 = 访问大小-1(0=1B, 1=2B, 3=4B)
|
||||
* bit 3 = 方向(0=OUT, 1=IN)
|
||||
* bit 4 = string 指令
|
||||
* bit 5 = REP 前缀
|
||||
* bits31:16 = 端口号
|
||||
* OUT 数据在 guest RAX;IN 结果写回 guest RAX。
|
||||
*/
|
||||
static int handle_io(u64 qualification, u64 rip, u64 instr_len, int *out_resume) {
|
||||
u32 port = (u32)(qualification >> 16);
|
||||
u32 size = (u32)(qualification & 7) + 1;
|
||||
int is_in = (qualification >> 3) & 1;
|
||||
u64 value = 0;
|
||||
int terminate = 0;
|
||||
|
||||
if (!is_in) {
|
||||
value = g_guest_regs.rax;
|
||||
if (size == 1) value &= 0xFF;
|
||||
else if (size == 2) value &= 0xFFFF;
|
||||
else value &= 0xFFFFFFFFULL;
|
||||
}
|
||||
|
||||
u64 result;
|
||||
if (port >= 0x3F8 && port <= 0x3FF) {
|
||||
result = com1_io(port, is_in, value);
|
||||
} else {
|
||||
result = misc_io(port, is_in, value, &terminate);
|
||||
}
|
||||
|
||||
if (is_in) {
|
||||
if (size == 1) {
|
||||
g_guest_regs.rax = (g_guest_regs.rax & ~0xFFULL) | (result & 0xFF);
|
||||
} else if (size == 2) {
|
||||
g_guest_regs.rax = (g_guest_regs.rax & ~0xFFFFULL) | (result & 0xFFFF);
|
||||
} else {
|
||||
g_guest_regs.rax = result & 0xFFFFFFFFULL; /* 32 位写清零高 32 位 */
|
||||
}
|
||||
}
|
||||
|
||||
if (terminate) {
|
||||
*out_resume = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
*out_resume = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== EXIT_CPUID 处理:host passthrough + 屏蔽 VMX/hypervisor =====
|
||||
*
|
||||
* CPUID 在 VMX non-root 无条件 exit。必须真实执行并把结果写回 guest
|
||||
* rax/rbx/rcx/rdx,否则 guest 特性探测全错(原"skip"实现会崩 Linux)。
|
||||
* leaf 1 ecx:清 bit5(VMX) 与 bit31(hypervisor),让 guest 认为自己在裸机。
|
||||
*/
|
||||
static void cpuid_host(u32 a, u32 c, u32 *oa, u32 *ob, u32 *oc, u32 *od) {
|
||||
__asm__ volatile("cpuid"
|
||||
: "=a"(*oa), "=b"(*ob), "=c"(*oc), "=d"(*od)
|
||||
: "a"(a), "c"(c));
|
||||
}
|
||||
|
||||
static int handle_cpuid(u64 rip, u64 instr_len, int *out_resume) {
|
||||
u32 a = (u32)g_guest_regs.rax;
|
||||
u32 c = (u32)g_guest_regs.rcx;
|
||||
u32 oa, ob, oc, od;
|
||||
cpuid_host(a, c, &oa, &ob, &oc, &od);
|
||||
|
||||
if (a == 1) {
|
||||
oc &= ~(1u << 5); /* VMX */
|
||||
oc &= ~(1u << 31); /* hypervisor present */
|
||||
}
|
||||
/* leaf 0x15/0x16 passthrough:QEMU 提供 TSC/总线频率,
|
||||
* guest 据此校准 TSC(绕过 PIT ch2 校准路径)。 */
|
||||
|
||||
g_guest_regs.rax = oa;
|
||||
g_guest_regs.rbx = ob;
|
||||
g_guest_regs.rcx = oc;
|
||||
g_guest_regs.rdx = od;
|
||||
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
*out_resume = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== resume 前例行工作:RX 轮询 + PIT tick + 注入 pending IRQ ===== */
|
||||
|
||||
static void poll_host_serial_to_guest(void) {
|
||||
for (int i = 0; i < 32; i++) {
|
||||
int b = serial_try_read();
|
||||
if (b < 0) break;
|
||||
int next = (g_rx_tail + 1) % GUEST_RX_SIZE;
|
||||
if (next == g_rx_head) break; /* ring 满 */
|
||||
g_guest_rx[g_rx_tail] = (u8)b;
|
||||
g_rx_tail = next;
|
||||
}
|
||||
if (g_rx_head != g_rx_tail) {
|
||||
vmx_guest_queue_irq(0x34); /* COM1 = IRQ4 → vector 0x34 */
|
||||
}
|
||||
}
|
||||
|
||||
static void pit_tick_update(void) {
|
||||
u64 now = rdtsc_local();
|
||||
if (g_last_tick_tsc == 0) {
|
||||
g_last_tick_tsc = now;
|
||||
if (g_pit_period_tsc == 0) pit_recompute_period();
|
||||
return;
|
||||
}
|
||||
u64 delta = now - g_last_tick_tsc;
|
||||
g_last_tick_tsc = now;
|
||||
if (g_pit_period_tsc == 0) return;
|
||||
g_pit_accum += delta;
|
||||
int n = 0;
|
||||
while (g_pit_accum >= g_pit_period_tsc && n < 8) {
|
||||
g_pit_accum -= g_pit_period_tsc;
|
||||
vmx_guest_queue_irq(0x30); /* PIT = IRQ0 → vector 0x30(自动合并) */
|
||||
n++;
|
||||
}
|
||||
if (g_pit_accum >= g_pit_period_tsc) g_pit_accum = 0; /* 防积压 */
|
||||
}
|
||||
|
||||
static u64 g_preemption_quantum = 0;
|
||||
|
||||
u64 vmx_preemption_quantum_1ms(void) {
|
||||
if (g_preemption_quantum) return g_preemption_quantum;
|
||||
u64 basic = vmx_read_msr(IA32_VMX_BASIC);
|
||||
u64 scale = (basic >> 48) & 0xFF; /* preemption timer scale */
|
||||
if (scale == 0) scale = 1;
|
||||
u64 tpm = serial_tsc_per_ms();
|
||||
if (tpm == 0) tpm = 2000000;
|
||||
g_preemption_quantum = tpm / scale;
|
||||
if (g_preemption_quantum == 0) g_preemption_quantum = 1;
|
||||
return g_preemption_quantum;
|
||||
}
|
||||
|
||||
int vmx_preemption_timer_supported(void) {
|
||||
u64 msr = vmx_read_msr(IA32_VMX_TRUE_PINBASED_CTLS);
|
||||
return (msr & (1ULL << (6 + 32))) != 0; /* bit6 允许置 1 */
|
||||
}
|
||||
|
||||
/* 每次 vmresume 前调用(vmexit_dispatch resume=1 路径末尾)。
|
||||
* 重新 arm preemption timer(1ms 周期 exit),轮询 host 串口 RX,
|
||||
* 推进 PIT tick,注入 pending IRQ。 */
|
||||
static void vmexit_before_resume(void) {
|
||||
if (!g_linux_guest_active) return;
|
||||
vmx_vmcs_write(VMCS_GUEST_PREEMPTION_TIMER, vmx_preemption_quantum_1ms());
|
||||
poll_host_serial_to_guest();
|
||||
pit_tick_update();
|
||||
maybe_inject_irq();
|
||||
}
|
||||
|
||||
/* ===== EPT violation: 指令解码(用于 virtio-mmio MMIO 数据) =====
|
||||
*
|
||||
* virtio-mmio 寄存器访问是普通 MOV 指令,如:
|
||||
* mov eax, [gpa] 读 (8A /r 或 A1)
|
||||
* mov [gpa], eax 写 (89 /r 或 A3)
|
||||
* movzx/movsx 等
|
||||
*
|
||||
* 我们不需要完整解码——只需确定:
|
||||
* 1. 访问方向(读 or 写)
|
||||
* 2. 访问宽度(1/2/4/8 字节)
|
||||
* 3. 写访问时的写入数据;读访问时数据写到哪个 guest GPR
|
||||
*
|
||||
* 这通过解码指令字节 + 读 guest RIP 处的机器码完成。
|
||||
* 用 GVA=EPT GPA 简化:guest 采用恒等页表,GUEST_LINEAR_ADDR 即 GPA。
|
||||
*/
|
||||
|
||||
/* 从 guest 内存读指令字节(GPA=GVA 恒等映射,经 EPT 翻译到 HPA)。 */
|
||||
static int guest_fetch(u64 gva, u8 *buf, u64 len) {
|
||||
u64 hhdm = g_hhdm_request.response ? g_hhdm_request.response->offset : 0;
|
||||
for (u64 i = 0; i < len; i++) {
|
||||
u64 hpa = ept_gpa_to_hpa(gva + i);
|
||||
if (hpa == 0) return -1;
|
||||
buf[i] = *(volatile u8 *)(hhdm + hpa);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 解码 MMIO 访问指令。
|
||||
* rip_gpa : guest RIP(GPA,恒等映射)
|
||||
* is_write_out : 输出 1=写 MMIO,0=读 MMIO
|
||||
* width_out : 输出访问宽度(字节)
|
||||
* reg_field_out: 输出目标/源 GPR 在 g_guest_regs 中的索引(0-15)
|
||||
* 返回指令总长度(用于回退时推进 RIP),失败返回 0。 */
|
||||
static u64 decode_mmio_insn(u64 rip_gpa, int *is_write_out, int *width_out,
|
||||
int *reg_field_out) {
|
||||
u8 insn[16];
|
||||
if (guest_fetch(rip_gpa, insn, sizeof(insn)) != 0) return 0;
|
||||
|
||||
u64 off = 0;
|
||||
int rex_w = 0, rex_r = 0;
|
||||
|
||||
/* 跳过前缀 + REX */
|
||||
for (;;) {
|
||||
u8 b = insn[off];
|
||||
if (b == 0x66 || b == 0x67 || b == 0xF0 || b == 0xF2 || b == 0xF3 ||
|
||||
(b >= 0x2E && b <= 0x3E && (b==0x2E||b==0x36||b==0x3E||b==0x26||b==0x64||b==0x65))) {
|
||||
off++;
|
||||
continue;
|
||||
}
|
||||
if (b >= 0x40 && b <= 0x4F) { /* REX */
|
||||
rex_w = (b >> 3) & 1;
|
||||
rex_r = (b >> 2) & 1;
|
||||
off++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
u8 op = insn[off];
|
||||
u8 modrm = insn[off + 1];
|
||||
int reg = ((modrm >> 3) & 7) | (rex_r << 3);
|
||||
|
||||
/* mov r, m(读 MMIO): 8A(8位) / 8B(全宽) */
|
||||
if (op == 0x8A || op == 0x8B) {
|
||||
*is_write_out = 0;
|
||||
*width_out = (op == 0x8A) ? 1 : (rex_w ? 8 : 4);
|
||||
*reg_field_out = reg;
|
||||
return off + 2; /* 简化:假设 ModRM mod=00,rm=101(无 SIB/disp) */
|
||||
}
|
||||
/* mov m, r(写 MMIO): 88(8位) / 89(全宽) */
|
||||
if (op == 0x88 || op == 0x89) {
|
||||
*is_write_out = 1;
|
||||
*width_out = (op == 0x88) ? 1 : (rex_w ? 8 : 4);
|
||||
*reg_field_out = reg;
|
||||
return off + 2;
|
||||
}
|
||||
/* movzx: 0F B6(8→宽) / 0F B7(16→宽) 读 */
|
||||
if (op == 0x0F && (insn[off+1] == 0xB6 || insn[off+1] == 0xB7)) {
|
||||
*is_write_out = 0;
|
||||
*width_out = (insn[off+1] == 0xB6) ? 1 : 2;
|
||||
u8 m2 = insn[off + 2];
|
||||
*reg_field_out = ((m2 >> 3) & 7) | (rex_r << 3);
|
||||
return off + 3;
|
||||
}
|
||||
|
||||
/* 无法解码(如带 SIB/displacement 的复杂寻址)。
|
||||
* virtio-mmio 驱动用简单寄存器访问,暂不支持则返回 0。 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 按索引读写 guest GPR(用于 MMIO 数据的源/目的)。 */
|
||||
static u64 *gpr_ptr(int idx) {
|
||||
switch (idx & 15) {
|
||||
case 0: return (u64 *)&g_guest_regs.rax;
|
||||
case 1: return (u64 *)&g_guest_regs.rcx;
|
||||
case 2: return (u64 *)&g_guest_regs.rdx;
|
||||
case 3: return (u64 *)&g_guest_regs.rbx;
|
||||
case 6: return (u64 *)&g_guest_regs.rsi;
|
||||
case 7: return (u64 *)&g_guest_regs.rdi;
|
||||
case 5: return (u64 *)&g_guest_regs.rbp;
|
||||
case 4: return (u64 *)&g_guest_regs.rax; /* rsp 不用作 MMIO 数据,占位 */
|
||||
case 8: return (u64 *)&g_guest_regs.r8;
|
||||
case 9: return (u64 *)&g_guest_regs.r9;
|
||||
case 10: return (u64 *)&g_guest_regs.r10;
|
||||
case 11: return (u64 *)&g_guest_regs.r11;
|
||||
case 12: return (u64 *)&g_guest_regs.r12;
|
||||
case 13: return (u64 *)&g_guest_regs.r13;
|
||||
case 14: return (u64 *)&g_guest_regs.r14;
|
||||
case 15: return (u64 *)&g_guest_regs.r15;
|
||||
}
|
||||
return (u64 *)&g_guest_regs.rax;
|
||||
}
|
||||
|
||||
/* 处理 virtio-mmio 设备 GPA 的访问:路由到设备模拟。返回 1=已处理(resume)。 */
|
||||
static int handle_virtio_mmio_access(u64 gpa, u64 rip, int *out_resume) {
|
||||
int is_write, width, reg;
|
||||
u64 insn_len = decode_mmio_insn(rip, &is_write, &width, ®);
|
||||
if (insn_len == 0) {
|
||||
log_error("[VMEXIT] virtio-mmio: cannot decode access insn");
|
||||
log_hex64("[VMEXIT] rip=", rip);
|
||||
*out_resume = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
u64 value = 0;
|
||||
if (is_write) {
|
||||
/* 从 guest GPR 取写入数据(按宽度截断) */
|
||||
u64 raw = *gpr_ptr(reg);
|
||||
if (width == 1) value = raw & 0xFF;
|
||||
else if (width == 2) value = raw & 0xFFFF;
|
||||
else if (width == 4) value = raw & 0xFFFFFFFFULL;
|
||||
else value = raw;
|
||||
virtio_mmio_write(gpa, value, width);
|
||||
} else {
|
||||
/* 从设备读,写入 guest GPR(按宽度) */
|
||||
value = virtio_mmio_read(gpa, width);
|
||||
u64 *dst = gpr_ptr(reg);
|
||||
if (width == 1) *dst = (*dst & ~0xFFULL) | (value & 0xFF);
|
||||
else if (width == 2) *dst = (*dst & ~0xFFFFULL) | (value & 0xFFFF);
|
||||
else if (width == 4) *dst = value & 0xFFFFFFFFULL; /* 32位写清零高32 */
|
||||
else *dst = value;
|
||||
}
|
||||
|
||||
/* 推进 RIP 越过 MMIO 指令,resume guest */
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + insn_len);
|
||||
*out_resume = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 处理 EPT violation:区分 guest RAM(按需分配)与 virtio-mmio(设备模拟)。
|
||||
*
|
||||
* exit qualification bits:
|
||||
* bit0 = read access, bit1 = write access, bit2 = instruction fetch
|
||||
* bit3 = EPT entry readable, bit4 = writable, bit5 = executable
|
||||
* virtio-mmio GPA 故意不映射 → bit3/4/5 全 0 → "访问不存在页"。 */
|
||||
static int handle_ept_violation(u64 qualification, u64 rip, int *out_resume) {
|
||||
(void)qualification; /* virtio-mmio 判定只看 GPA,不看访问类型 */
|
||||
u64 gpa = vmx_vmcs_read(VMCS_GUEST_PHYSICAL_ADDR);
|
||||
|
||||
/* virtio-mmio 设备区:路由到设备模拟,绝不映射真实内存 */
|
||||
if (IS_VIRTIO_MMIO_GPA(gpa)) {
|
||||
return handle_virtio_mmio_access(gpa, rip, out_resume);
|
||||
}
|
||||
|
||||
/* 普通 guest RAM:按需分配 HPA 页并映射 */
|
||||
u64 page = gpa & ~(EPT_PAGE_SIZE - 1);
|
||||
log_hex64("[VMEXIT] EPT violation gpa=", gpa);
|
||||
(void)qualification;
|
||||
/* 1:1 映射该页(假设 HPA=GPA) */
|
||||
if (ept_identity_map(page, EPT_PAGE_SIZE, EPT_RWX) != 0) {
|
||||
log_error("[VMEXIT] failed to map EPT page");
|
||||
if (ept_map_guest_ram_page(page) != 0) {
|
||||
log_error("[VMEXIT] EPT violation: guest RAM map failed");
|
||||
log_hex64("[VMEXIT] gpa=", gpa);
|
||||
*out_resume = 0;
|
||||
return -1;
|
||||
}
|
||||
@@ -84,7 +683,13 @@ int vmexit_dispatch(void) {
|
||||
handle_hlt(rip, instr_len, &resume);
|
||||
break;
|
||||
case EXIT_EPT_VIOLATION:
|
||||
handle_ept_violation(qualification, &resume);
|
||||
handle_ept_violation(qualification, rip, &resume);
|
||||
break;
|
||||
case EXIT_EPT_MISCONFIG:
|
||||
/* MMIO 区域权限配置错误或硬件 EPT 表损坏。诊断后终止。 */
|
||||
log_error("[VMEXIT] EPT misconfig");
|
||||
log_hex64("[VMEXIT] gpa=", vmx_vmcs_read(VMCS_GUEST_PHYSICAL_ADDR));
|
||||
resume = 0;
|
||||
break;
|
||||
case EXIT_EXCEPTION_NMI:
|
||||
handle_exception(qualification, rip, &resume);
|
||||
@@ -94,14 +699,26 @@ int vmexit_dispatch(void) {
|
||||
resume = 0;
|
||||
break;
|
||||
case EXIT_CPUID:
|
||||
/* 简化:让 guest 通过 CPUID,前进 RIP。Phase 1.2+ 完整模拟。 */
|
||||
log_info("[VMEXIT] CPUID - skip");
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
resume = 1;
|
||||
/* host passthrough + 屏蔽 VMX/hypervisor 位(handle_cpuid 内推进 RIP) */
|
||||
handle_cpuid(rip, instr_len, &resume);
|
||||
break;
|
||||
case EXIT_IO_INSTRUCTION:
|
||||
/* 简化:跳过 IO 指令。Phase 1.4 virtio-console 完整处理。 */
|
||||
vmx_vmcs_write(VMCS_GUEST_RIP, rip + instr_len);
|
||||
/* legacy 设备模拟:COM1 UART / PIC / PIT / 其余端口吞掉 */
|
||||
handle_io(qualification, rip, instr_len, &resume);
|
||||
break;
|
||||
case EXIT_INTERRUPT_WINDOW:
|
||||
/* guest 刚开中断(RFLAGS.IF=1):尝试注入 pending IRQ */
|
||||
{
|
||||
u64 cpu = vmx_vmcs_read(VMCS_CPU_BASED_VM_EXEC_CONTROL);
|
||||
vmx_vmcs_write(VMCS_CPU_BASED_VM_EXEC_CONTROL,
|
||||
cpu & ~CPU_BASED_INTR_WINDOW_EXITING);
|
||||
maybe_inject_irq();
|
||||
resume = 1;
|
||||
}
|
||||
break;
|
||||
case EXIT_VMX_PREEMPTION_TIMER:
|
||||
/* 1ms 周期 exit:resume 前由 vmexit_before_resume 统一
|
||||
* 轮询 host 串口 RX / 推进 PIT tick / 注入 pending IRQ */
|
||||
resume = 1;
|
||||
break;
|
||||
case EXIT_RDMSR:
|
||||
@@ -129,6 +746,12 @@ int vmexit_dispatch(void) {
|
||||
}
|
||||
|
||||
(void)qualification;
|
||||
|
||||
/* resume 前例行工作:arm preemption timer + host 串口 RX 轮询 +
|
||||
* PIT tick 推进 + pending IRQ 注入(仅 Linux guest active 时) */
|
||||
if (resume) {
|
||||
vmexit_before_resume();
|
||||
}
|
||||
return resume;
|
||||
}
|
||||
|
||||
|
||||
+438
-3
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "../UTSM/include/utsm/dsk.h"
|
||||
#include "../UTSM/include/utsm/pe.h"
|
||||
#include "../UTSM/pe/pe_loader.h" /* PE 头结构体(image_dos_header 等),供 peinfo 解析 */
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
@@ -196,6 +197,18 @@ static void term_putx(u32 v) {
|
||||
}
|
||||
}
|
||||
|
||||
/* 64 位十六进制输出(前导零抑制),用于 PE ImageBase 等宽字段 */
|
||||
static void term_putx64(u64 v) {
|
||||
static const char hex[] = "0123456789ABCDEF";
|
||||
term_putc('0'); term_putc('x');
|
||||
int started = 0;
|
||||
for (int i = 60; i >= 0; i -= 4) {
|
||||
u64 nib = (v >> i) & 0xF;
|
||||
if (nib) started = 1;
|
||||
if (started || i == 0) term_putc(hex[nib]);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 终端全屏渲染 ---- */
|
||||
static void term_redraw_all(void) {
|
||||
du_fill_rect(&g_ctx, 0, 0, (i64)fb_w, (i64)fb_h, CMD_BG);
|
||||
@@ -372,6 +385,8 @@ static void cmd_help(void) {
|
||||
term_puts_color("\nPE 程序执行:\r\n", CMD_WARN);
|
||||
term_puts(" <name>.exe 运行 PE32+/PE32 程序 (如 hello.exe)\r\n");
|
||||
term_puts(" <name> 自动查找 name.exe 并运行\r\n");
|
||||
term_puts(" pe <name> 显式运行 PE 程序 (如 pe hello64)\r\n");
|
||||
term_puts(" peinfo <nm> 显示 PE 头信息 (不执行)\r\n");
|
||||
}
|
||||
|
||||
static void cmd_ver(void) {
|
||||
@@ -645,6 +660,8 @@ static int has_exec_ext(const char *name) {
|
||||
* prog_name = 程序名(可能带或不带 .exe 后缀)
|
||||
* full_cmdline = 完整命令行(程序名 + 参数,即 GetCommandLineA 返回值)
|
||||
*/
|
||||
static int cmd_read_pe_from_bin(const char *name11, u8 **data, u32 *size); /* 前向声明 */
|
||||
|
||||
static void exec_pe(const char *prog_name, const char *full_cmdline) {
|
||||
if (!g_pe_svc) {
|
||||
term_puts_color("PE 兼容层不可用(UTSM PE 服务未初始化)\r\n", CMD_ERROR);
|
||||
@@ -677,12 +694,17 @@ static void exec_pe(const char *prog_name, const char *full_cmdline) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* 从 FAT32 根目录读取 */
|
||||
/* PATH 查找:先根目录,再 /bin/ */
|
||||
u8 *pe_data = 0; u32 pe_size = 0;
|
||||
if (f32_read_root_file(name11, &pe_data, &pe_size) != 0) {
|
||||
int rc = f32_read_root_file(name11, &pe_data, &pe_size);
|
||||
if (rc != 0) {
|
||||
rc = cmd_read_pe_from_bin(name11, &pe_data, &pe_size);
|
||||
}
|
||||
if (rc != 0) {
|
||||
term_puts_color("'", CMD_ERROR);
|
||||
term_puts_color(prog_name, CMD_ERROR);
|
||||
term_puts_color("' 不是内部或外部命令,也不是可运行的程序。\r\n", CMD_ERROR);
|
||||
term_puts_color(" (已搜索 C:\\ 及 C:\\bin\\)\r\n", CMD_DIM);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -702,7 +724,7 @@ static void exec_pe(const char *prog_name, const char *full_cmdline) {
|
||||
swrite("[cmd] cmdline: "); swrite(full_cmdline); swrite("\n");
|
||||
|
||||
u64 exit_code = 0;
|
||||
int rc = g_pe_svc->run(pe_data, (u64)pe_size, full_cmdline, &exit_code);
|
||||
rc = g_pe_svc->run(pe_data, (u64)pe_size, full_cmdline, &exit_code);
|
||||
|
||||
if (rc != 0) {
|
||||
term_puts_color("PE 执行失败,错误码: ", CMD_ERROR);
|
||||
@@ -717,6 +739,417 @@ static void exec_pe(const char *prog_name, const char *full_cmdline) {
|
||||
logl("[cmd] PE run end");
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* /bin 子目录支持 — 用于查找 PE 可执行程序
|
||||
* ============================================================ */
|
||||
|
||||
/* 在根目录簇链中查找名为 name11 的子目录(attr & 0x10),返回首簇号。
|
||||
* 使用 f32_disk 缓存(来自 fat32_io.h)。返回 0 成功,-1 未找到。 */
|
||||
static int cmd_find_subdir(u32 dir_clus, const char *name11, u32 *out_clus) {
|
||||
if (!f32_disk_loaded && f32_disk_load() != 0) return -2;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
u32 clus = dir_clus;
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8) {
|
||||
u32 lba = data_lba + (clus - 2) * spc;
|
||||
const u8 *cb;
|
||||
if ((u64)lba * 512 + (u64)spc * 512 <= sizeof(f32_disk))
|
||||
cb = f32_disk + (u64)lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4;
|
||||
cb = f32_cluster;
|
||||
}
|
||||
const f32_dirent *dir = (const f32_dirent *)cb;
|
||||
for (u32 e = 0; e * 32 < spc * 512; e++) {
|
||||
if (dir[e].name[0] == 0) goto done_dir;
|
||||
if ((u8)dir[e].name[0] == 0xE5) continue;
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
if (!(dir[e].attr & 0x10)) continue; /* 只匹配目录 */
|
||||
if (f32_neq11(dir[e].name, name11)) {
|
||||
*out_clus = f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
u32 fo = fat_byte_off + clus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
done_dir:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 从指定目录首簇读取文件到 f32_data。
|
||||
* dir_clus: 目录起始簇号。name11: 8.3 名。
|
||||
* 返回 0 成功,*out_data 指向 f32_data,*out_size 为字节数;非 0 失败。 */
|
||||
static int cmd_read_file_in_dir(u32 dir_clus, const char *name11, u8 **out_data, u32 *out_size) {
|
||||
if (!f32_disk_loaded && f32_disk_load() != 0) return -1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
|
||||
/* 先在目录中查找文件 */
|
||||
u32 clus = dir_clus;
|
||||
u32 found_clus = 0, found_size = 0;
|
||||
int found = 0;
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8 && !found) {
|
||||
u32 lba = data_lba + (clus - 2) * spc;
|
||||
const u8 *cb;
|
||||
if ((u64)lba * 512 + (u64)spc * 512 <= sizeof(f32_disk))
|
||||
cb = f32_disk + (u64)lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4;
|
||||
cb = f32_cluster;
|
||||
}
|
||||
const f32_dirent *dir = (const f32_dirent *)cb;
|
||||
for (u32 e = 0; e * 32 < spc * 512; e++) {
|
||||
if (dir[e].name[0] == 0) goto done_find;
|
||||
if ((u8)dir[e].name[0] == 0xE5) continue;
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
if (dir[e].attr & 0x10) continue; /* 跳过目录 */
|
||||
if (f32_neq11(dir[e].name, name11)) {
|
||||
found_clus = f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16);
|
||||
found_size = dir[e].fsize;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
u32 fo = fat_byte_off + clus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
done_find:
|
||||
if (!found) return -5;
|
||||
if (found_size > sizeof(f32_data)) return -6;
|
||||
|
||||
/* 读取文件数据 */
|
||||
u8 *dst = f32_data; u32 remaining = found_size; u32 fc = found_clus;
|
||||
while (fc >= 2 && fc < 0x0FFFFFF8 && remaining > 0) {
|
||||
u32 fc_lba = data_lba + (fc - 2) * spc;
|
||||
u32 fc_bytes = spc * 512;
|
||||
if (fc_bytes > remaining) fc_bytes = remaining;
|
||||
const u8 *fb;
|
||||
if ((u64)fc_lba * 512 + (u64)spc * 512 <= sizeof(f32_disk))
|
||||
fb = f32_disk + (u64)fc_lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(fc_lba, 8, f32_cluster) != 0) return -7;
|
||||
fb = f32_cluster;
|
||||
}
|
||||
for (u32 b = 0; b < fc_bytes; b++) dst[b] = fb[b];
|
||||
dst += fc_bytes; remaining -= fc_bytes;
|
||||
u32 fo = fat_byte_off + fc * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
fc = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
*out_data = f32_data; *out_size = found_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 g_cmd_bin_clus = 0;
|
||||
static int g_cmd_bin_checked = 0;
|
||||
|
||||
/* 获取 /bin 目录首簇号。返回 0 成功,-1 未找到。 */
|
||||
static int cmd_get_bin_cluster(u32 *out_clus) {
|
||||
if (g_cmd_bin_checked) {
|
||||
if (g_cmd_bin_clus == 0) return -1;
|
||||
*out_clus = g_cmd_bin_clus;
|
||||
return 0;
|
||||
}
|
||||
g_cmd_bin_checked = 1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
char bin83[11] = {'B','I','N',' ',' ',' ',' ',' ',' ',' ',' '};
|
||||
if (cmd_find_subdir(bpb->root_clus, bin83, &g_cmd_bin_clus) != 0) {
|
||||
g_cmd_bin_clus = 0;
|
||||
return -1;
|
||||
}
|
||||
*out_clus = g_cmd_bin_clus;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 从 /bin 子目录读取 PE 文件。返回 0 成功。 */
|
||||
static int cmd_read_pe_from_bin(const char *name11, u8 **data, u32 *size) {
|
||||
u32 bin_clus;
|
||||
if (cmd_get_bin_cluster(&bin_clus) != 0) return -1;
|
||||
return cmd_read_file_in_dir(bin_clus, name11, data, size);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* peinfo — 解析并显示 PE 头信息(不执行)
|
||||
* ============================================================ */
|
||||
|
||||
/* 把程序名转为 8.3 名(自动补 .exe),返回 0 成功,-1 失败。
|
||||
* 内联自 exec_pe 的同名逻辑,供 cmd_peinfo 复用。 */
|
||||
static int make_pe_name83(const char *prog, char name11[11]) {
|
||||
char base[64];
|
||||
str_copy(base, prog, sizeof(base));
|
||||
int has_dot = 0;
|
||||
for (int i = 0; base[i]; i++) {
|
||||
if (base[i] == '.') { has_dot = 1; break; }
|
||||
}
|
||||
if (!has_dot) {
|
||||
int bl = str_len(base);
|
||||
if (bl > 59) bl = 59;
|
||||
base[bl] = '.'; base[bl + 1] = 'e'; base[bl + 2] = 'x';
|
||||
base[bl + 3] = 'e'; base[bl + 4] = 0;
|
||||
}
|
||||
return f32_name_to_83(base, name11);
|
||||
}
|
||||
|
||||
static void cmd_peinfo(const char *args) {
|
||||
if (!*args) {
|
||||
term_puts_color("用法: peinfo <程序名> (如 peinfo hello64)\r\n", CMD_INFO);
|
||||
return;
|
||||
}
|
||||
if (!f32_blk_read) {
|
||||
term_puts_color("peinfo: 无块设备\r\n", CMD_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
char name11[11];
|
||||
if (make_pe_name83(args, name11) != 0) {
|
||||
term_puts_color("peinfo: 无效文件名\r\n", CMD_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
/* PATH 查找:先根目录,再 /bin/ */
|
||||
u8 *pe = 0; u32 pe_size = 0;
|
||||
int rc = f32_read_root_file(name11, &pe, &pe_size);
|
||||
if (rc != 0) {
|
||||
rc = cmd_read_pe_from_bin(name11, &pe, &pe_size);
|
||||
}
|
||||
if (rc != 0) {
|
||||
term_puts_color("peinfo: 文件不存在: ", CMD_ERROR);
|
||||
term_puts_color(args, CMD_ERROR);
|
||||
term_puts_color(" (已搜索 C:\\ 及 C:\\bin\\)\r\n", CMD_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
/* ---- DOS 头 ---- */
|
||||
if (pe_size < sizeof(image_dos_header)) {
|
||||
term_puts_color("peinfo: 文件过小,无 DOS 头\r\n", CMD_ERROR);
|
||||
return;
|
||||
}
|
||||
const image_dos_header *dos = (const image_dos_header *)pe;
|
||||
if (dos->e_magic != IMAGE_DOS_SIGNATURE) {
|
||||
term_puts_color("peinfo: 缺少 MZ 签名\r\n", CMD_ERROR);
|
||||
return;
|
||||
}
|
||||
u32 lfanew = dos->e_lfanew;
|
||||
if ((u64)lfanew + 4 + sizeof(image_file_header) > pe_size) {
|
||||
term_puts_color("peinfo: e_lfanew 越界\r\n", CMD_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
term_puts_color("=== PE 头信息 ===\r\n", CMD_INFO);
|
||||
term_puts(" DOS: MZ OK, e_lfanew=");
|
||||
term_putx(lfanew);
|
||||
term_puts("\r\n");
|
||||
|
||||
/* ---- NT 签名 + File Header ---- */
|
||||
const u8 *nt = pe + lfanew;
|
||||
if (*(const u32 *)nt != IMAGE_NT_SIGNATURE) {
|
||||
term_puts_color("peinfo: 缺少 PE\\0\\0 签名\r\n", CMD_ERROR);
|
||||
return;
|
||||
}
|
||||
const image_file_header *fh = (const image_file_header *)(nt + 4);
|
||||
u16 machine = fh->machine;
|
||||
u16 nsec = fh->number_of_sections;
|
||||
u16 opt_size = fh->size_of_optional_header;
|
||||
|
||||
term_puts(" NT: PE\\0\\0 OK\r\n");
|
||||
term_puts(" Machine: ");
|
||||
term_putx(machine);
|
||||
if (machine == 0x8664) term_puts_color(" (AMD64)\r\n", CMD_OK);
|
||||
else if (machine == 0x014c) term_puts_color(" (i386)\r\n", CMD_OK);
|
||||
else term_puts(" (未知)\r\n");
|
||||
term_puts(" 节区数: ");
|
||||
term_putu(nsec);
|
||||
term_puts(" OptHdr 大小: ");
|
||||
term_putu(opt_size);
|
||||
term_puts("\r\n");
|
||||
|
||||
/* ---- Optional Header ---- */
|
||||
if (opt_size < 2) {
|
||||
term_puts_color("peinfo: Optional Header 过小\r\n", CMD_WARN);
|
||||
return;
|
||||
}
|
||||
if ((u64)lfanew + 4 + sizeof(image_file_header) + opt_size > pe_size) {
|
||||
term_puts_color("peinfo: Optional Header 越界\r\n", CMD_ERROR);
|
||||
return;
|
||||
}
|
||||
const void *opt = (const void *)(nt + 4 + sizeof(image_file_header));
|
||||
u16 magic = *(const u16 *)opt;
|
||||
int is_pe32_plus = (magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC);
|
||||
|
||||
u64 img_base = 0;
|
||||
u32 entry_rva = 0;
|
||||
u32 img_size = 0;
|
||||
u32 subsys = 0;
|
||||
const image_data_directory *import_dir = 0;
|
||||
|
||||
if (is_pe32_plus) {
|
||||
if (opt_size >= sizeof(image_optional_header64)) {
|
||||
const image_optional_header64 *oh = (const image_optional_header64 *)opt;
|
||||
img_base = oh->image_base;
|
||||
entry_rva = oh->address_of_entry_point;
|
||||
img_size = oh->size_of_image;
|
||||
subsys = oh->subsystem;
|
||||
import_dir = &oh->data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
||||
}
|
||||
term_puts_color(" 格式: PE32+ (64位 原生)\r\n", CMD_OK);
|
||||
} else if (magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
||||
if (opt_size >= sizeof(image_optional_header32)) {
|
||||
const image_optional_header32 *oh = (const image_optional_header32 *)opt;
|
||||
img_base = oh->image_base;
|
||||
entry_rva = oh->address_of_entry_point;
|
||||
img_size = oh->size_of_image;
|
||||
subsys = oh->subsystem;
|
||||
import_dir = &oh->data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
||||
}
|
||||
term_puts_color(" 格式: PE32 (32位 解释器)\r\n", CMD_OK);
|
||||
} else {
|
||||
term_puts_color("peinfo: 未知 Optional Header magic=", CMD_ERROR);
|
||||
term_putx(magic);
|
||||
term_puts("\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
term_puts(" ImageBase: ");
|
||||
term_putx64(img_base);
|
||||
term_puts("\r\n");
|
||||
term_puts(" EntryPoint: ");
|
||||
term_putx(entry_rva);
|
||||
term_puts(" (RVA)\r\n");
|
||||
term_puts(" SizeOfImage: ");
|
||||
term_putx(img_size);
|
||||
term_puts(" (");
|
||||
term_putu(img_size);
|
||||
term_puts(" bytes)\r\n");
|
||||
term_puts(" Subsystem: ");
|
||||
term_putu(subsys);
|
||||
term_puts(subsys == 3 ? " (CUI 控制台)\r\n" : "\r\n");
|
||||
|
||||
/* ---- 节区表 ---- */
|
||||
const image_section_header *secs = (const image_section_header *)
|
||||
((const u8 *)opt + opt_size);
|
||||
if ((u64)((const u8 *)secs - pe) + (u64)nsec * sizeof(image_section_header) > pe_size) {
|
||||
term_puts_color("peinfo: 节区表越界,跳过\r\n", CMD_WARN);
|
||||
nsec = 0;
|
||||
}
|
||||
if (nsec > 0) {
|
||||
term_puts_color(" 节区表:\r\n", CMD_INFO);
|
||||
term_puts(" Name VAddr VSize RawSize Flg\r\n");
|
||||
for (int i = 0; i < nsec && i < 96; i++) {
|
||||
char nm[9];
|
||||
for (int j = 0; j < 8; j++) nm[j] = (char)secs[i].name[j];
|
||||
nm[8] = 0;
|
||||
term_puts(" ");
|
||||
term_puts(nm);
|
||||
int pad = 8 - str_len(nm);
|
||||
for (int k = 0; k < pad; k++) term_putc(' ');
|
||||
term_puts(" ");
|
||||
term_putx(secs[i].virtual_address);
|
||||
term_puts(" ");
|
||||
term_putx(secs[i].virtual_size);
|
||||
term_puts(" ");
|
||||
term_putx(secs[i].size_of_raw_data);
|
||||
term_puts(" ");
|
||||
char flags[4];
|
||||
flags[0] = (secs[i].characteristics & IMAGE_SCN_MEM_EXECUTE) ? 'X' : '-';
|
||||
flags[1] = (secs[i].characteristics & IMAGE_SCN_MEM_READ) ? 'R' : '-';
|
||||
flags[2] = (secs[i].characteristics & IMAGE_SCN_MEM_WRITE) ? 'W' : '-';
|
||||
flags[3] = 0;
|
||||
term_puts_color(flags, CMD_WARN);
|
||||
term_puts("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 导入表 ---- */
|
||||
if (import_dir && import_dir->virtual_address != 0 && import_dir->size != 0) {
|
||||
u32 imp_rva = import_dir->virtual_address;
|
||||
if (imp_rva >= pe_size) {
|
||||
term_puts_color("peinfo: 导入表 RVA 越界\r\n", CMD_WARN);
|
||||
} else {
|
||||
term_puts_color(" 导入表:\r\n", CMD_INFO);
|
||||
int dll_count = 0;
|
||||
int func_total = 0;
|
||||
int truncated = 0;
|
||||
for (int di = 0; !truncated; di++) {
|
||||
u32 desc_off = imp_rva + (u32)di * sizeof(image_import_descriptor);
|
||||
if ((u64)desc_off + sizeof(image_import_descriptor) > pe_size) break;
|
||||
const image_import_descriptor *desc =
|
||||
(const image_import_descriptor *)(pe + desc_off);
|
||||
if (desc->name == 0 && desc->first_thunk == 0) break;
|
||||
|
||||
const char *dll_name = 0;
|
||||
if (desc->name < pe_size) dll_name = (const char *)(pe + desc->name);
|
||||
term_puts(" DLL: ");
|
||||
term_puts_color(dll_name ? dll_name : "(?)", CMD_WARN);
|
||||
term_puts("\r\n");
|
||||
|
||||
u32 int_rva = desc->original_first_thunk;
|
||||
u32 iat_rva = desc->first_thunk;
|
||||
if (int_rva == 0) int_rva = iat_rva;
|
||||
if (int_rva == 0) { dll_count++; continue; }
|
||||
|
||||
for (int fi = 0; ; fi++) {
|
||||
u32 thunk_off = int_rva + (u32)fi * 4;
|
||||
if ((u64)thunk_off + 4 > pe_size) break;
|
||||
u32 thunk = *(const u32 *)(pe + thunk_off);
|
||||
if (thunk == 0) break;
|
||||
|
||||
term_puts(" ");
|
||||
if (thunk & 0x80000000u) {
|
||||
term_puts("#ordinal ");
|
||||
term_putu(thunk & 0xFFFF);
|
||||
} else if (thunk + 2 < pe_size) {
|
||||
const char *fn = (const char *)(pe + thunk + 2);
|
||||
term_puts(fn);
|
||||
} else {
|
||||
term_puts("(?)");
|
||||
}
|
||||
term_puts("\r\n");
|
||||
func_total++;
|
||||
if (func_total > 64) {
|
||||
term_puts_color(" ... (导入过多,截断)\r\n", CMD_DIM);
|
||||
truncated = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dll_count++;
|
||||
if (dll_count > 32) {
|
||||
term_puts_color(" ... (DLL 过多,截断)\r\n", CMD_DIM);
|
||||
break;
|
||||
}
|
||||
}
|
||||
term_puts(" 共 ");
|
||||
term_putu(dll_count);
|
||||
term_puts(" 个 DLL, ");
|
||||
term_putu(func_total);
|
||||
term_puts(" 个导入函数\r\n");
|
||||
}
|
||||
} else {
|
||||
term_puts_color(" 导入表: 无\r\n", CMD_DIM);
|
||||
}
|
||||
|
||||
term_puts_color("=== PE 头信息结束 ===\r\n", CMD_INFO);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* pe — 显式运行 PE 程序(等价于直接输入名字,但可在 help 中列出)
|
||||
* ============================================================ */
|
||||
static void cmd_pe(const char *args) {
|
||||
if (!*args) {
|
||||
term_puts_color("用法: pe <程序名> (如 pe hello64)\r\n", CMD_INFO);
|
||||
return;
|
||||
}
|
||||
exec_pe(args, args);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 命令解析与分发
|
||||
* ============================================================ */
|
||||
@@ -756,6 +1189,8 @@ static void execute_command(const char *cmd) {
|
||||
g_should_exit = 1;
|
||||
return;
|
||||
}
|
||||
if (str_ieq(name, "pe")) { cmd_pe(args); return; }
|
||||
if (str_ieq(name, "peinfo")) { cmd_peinfo(args); return; }
|
||||
if (str_ieq(name, "reboot")) {
|
||||
term_puts_color("重启中...\r\n", CMD_WARN);
|
||||
term_redraw_all();
|
||||
|
||||
+146
-13
@@ -268,6 +268,7 @@ static int g_ctx_win = -1;
|
||||
/* 状态栏滑块 */
|
||||
static int g_vol = 65;
|
||||
static int g_bright = 100;
|
||||
static int g_bright_applied = 100; /* 上次应用的亮度值(避免重复处理) */
|
||||
static int g_drag_slider = 0; /* 1=vol 2=bright */
|
||||
static int g_vol_rect[4]; /* x,y,w,h 滑轨 */
|
||||
static int g_bri_rect[4];
|
||||
@@ -312,6 +313,68 @@ static int g_ide_tabx[4]; /* IDE 标签 × */
|
||||
static int g_ide_new_rect[4]; /* IDE "+ NEW IDE" 按钮 */
|
||||
static int g_cp_host[4]; /* 自定义页宿主 */
|
||||
|
||||
/* ============================================================
|
||||
* 脏矩形渲染(性能优化:只更新变化区域)
|
||||
* ============================================================ */
|
||||
|
||||
#define MAX_DIRTY_RECTS 16
|
||||
|
||||
typedef struct {
|
||||
int x, y, w, h;
|
||||
int active;
|
||||
} dirty_rect;
|
||||
|
||||
static dirty_rect g_dirty_rects[MAX_DIRTY_RECTS];
|
||||
static int g_dirty_count = 0;
|
||||
static int g_full_redraw = 1; /* 初始全屏重绘 */
|
||||
|
||||
/* 标记脏区域 */
|
||||
__attribute__((unused))
|
||||
static void mark_dirty(int x, int y, int w, int h) {
|
||||
if (g_dirty_count >= MAX_DIRTY_RECTS) {
|
||||
/* 溢出时合并为全屏重绘 */
|
||||
g_full_redraw = 1;
|
||||
return;
|
||||
}
|
||||
g_dirty_rects[g_dirty_count].x = x;
|
||||
g_dirty_rects[g_dirty_count].y = y;
|
||||
g_dirty_rects[g_dirty_count].w = w;
|
||||
g_dirty_rects[g_dirty_count].h = h;
|
||||
g_dirty_rects[g_dirty_count].active = 1;
|
||||
g_dirty_count++;
|
||||
}
|
||||
|
||||
/* 合并所有脏矩形为一个包围盒 */
|
||||
static void merge_dirty_rects(int *out_x, int *out_y, int *out_w, int *out_h) {
|
||||
if (g_dirty_count == 0) {
|
||||
*out_x = *out_y = *out_w = *out_h = 0;
|
||||
return;
|
||||
}
|
||||
int min_x = (int)g_fb_w, min_y = (int)g_fb_h;
|
||||
int max_x = 0, max_y = 0;
|
||||
for (int i = 0; i < g_dirty_count; i++) {
|
||||
if (!g_dirty_rects[i].active) continue;
|
||||
int x1 = g_dirty_rects[i].x;
|
||||
int y1 = g_dirty_rects[i].y;
|
||||
int x2 = x1 + g_dirty_rects[i].w;
|
||||
int y2 = y1 + g_dirty_rects[i].h;
|
||||
if (x1 < min_x) min_x = x1;
|
||||
if (y1 < min_y) min_y = y1;
|
||||
if (x2 > max_x) max_x = x2;
|
||||
if (y2 > max_y) max_y = y2;
|
||||
}
|
||||
*out_x = min_x;
|
||||
*out_y = min_y;
|
||||
*out_w = max_x - min_x;
|
||||
*out_h = max_y - min_y;
|
||||
}
|
||||
|
||||
/* 清除所有脏矩形 */
|
||||
static void clear_dirty_rects(void) {
|
||||
g_dirty_count = 0;
|
||||
g_full_redraw = 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 箭头光标形状(24×24,1=前景 0=透明)
|
||||
* ============================================================ */
|
||||
@@ -847,6 +910,9 @@ typedef struct {
|
||||
char input_buf[256];
|
||||
int input_len, input_cursor;
|
||||
int prompt_len;
|
||||
/* 性能优化:脏行标记(增量渲染) */
|
||||
u8 dirty_rows[TERM_MAX_ROWS]; /* 标记哪些行需要重绘 */
|
||||
int any_dirty; /* 是否有任何脏行 */
|
||||
} bash_state;
|
||||
|
||||
static const char *BASH_PROMPT = "deshab# ";
|
||||
@@ -864,11 +930,20 @@ static void bash_reset(bash_state *s, int w, int h) {
|
||||
s->cur_col = 0; s->cur_row = 0;
|
||||
s->input_len = 0; s->input_cursor = 0;
|
||||
s->prompt_len = kstrlen(BASH_PROMPT);
|
||||
/* 初始化脏行标记:所有行标记为脏,确保首次渲染 */
|
||||
for (int i = 0; i < TERM_MAX_ROWS; i++) s->dirty_rows[i] = 1;
|
||||
s->any_dirty = 1;
|
||||
}
|
||||
|
||||
static void bash_putc(bash_state *s, char c, u32 color) {
|
||||
if (c == '\n') { s->cur_col = 0; s->cur_row++; return; }
|
||||
if (c == '\n') {
|
||||
s->dirty_rows[s->cur_row] = 1; /* 标记当前行为脏 */
|
||||
s->any_dirty = 1;
|
||||
s->cur_col = 0; s->cur_row++;
|
||||
return;
|
||||
}
|
||||
if (s->cur_row >= s->rows) {
|
||||
/* 滚动:所有行都需要重绘 */
|
||||
for (int r = 1; r < s->rows; r++) {
|
||||
for (int c2 = 0; c2 < s->cols; c2++) {
|
||||
s->ch[(r-1)*TERM_MAX_COLS+c2] = s->ch[r*TERM_MAX_COLS+c2];
|
||||
@@ -879,11 +954,17 @@ static void bash_putc(bash_state *s, char c, u32 color) {
|
||||
s->ch[(s->rows-1)*TERM_MAX_COLS+c2] = ' ';
|
||||
}
|
||||
s->cur_row = s->rows - 1;
|
||||
/* 标记所有行为脏 */
|
||||
for (int r = 0; r < s->rows; r++) s->dirty_rows[r] = 1;
|
||||
s->any_dirty = 1;
|
||||
}
|
||||
if (s->cur_col >= s->cols) { s->cur_col = 0; s->cur_row++; }
|
||||
int idx = s->cur_row * TERM_MAX_COLS + s->cur_col;
|
||||
s->ch[idx] = (u8)c; s->fg[idx] = color;
|
||||
s->cur_col++;
|
||||
/* 标记当前行为脏 */
|
||||
s->dirty_rows[s->cur_row] = 1;
|
||||
s->any_dirty = 1;
|
||||
}
|
||||
|
||||
static void bash_puts(bash_state *s, const char *str, u32 color) {
|
||||
@@ -958,11 +1039,18 @@ static void bash_key(bash_state *s, u8 sc, int shift) {
|
||||
}
|
||||
}
|
||||
|
||||
/* 绘制终端到指定矩形区域(面板或窗口客户区) */
|
||||
/* 绘制终端到指定矩形区域(面板或窗口客户区)
|
||||
* 性能优化:增量渲染,只绘制脏行 */
|
||||
static void bash_draw_to(bash_state *s, int cx, int cy, int cw, int ch) {
|
||||
/* 快速退出:终端内容未修改,跳过绘制 */
|
||||
if (!s->any_dirty) return;
|
||||
|
||||
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_PRIMARY);
|
||||
|
||||
for (int r = 0; r < s->rows; r++) {
|
||||
/* 增量渲染:只绘制脏行 */
|
||||
if (!s->dirty_rows[r]) continue;
|
||||
|
||||
for (int c = 0; c < s->cols; c++) {
|
||||
int idx = r * TERM_MAX_COLS + c;
|
||||
if (s->ch[idx] == ' ') continue;
|
||||
@@ -972,6 +1060,9 @@ static void bash_draw_to(bash_state *s, int cx, int cy, int cw, int ch) {
|
||||
if (py + (int)DU_ASCII_CELL_H > cy + ch) continue;
|
||||
du_draw_char(&g_fb, s->ch[idx], px, py, s->fg[idx], KS_BG_PRIMARY);
|
||||
}
|
||||
|
||||
/* 清除脏行标记 */
|
||||
s->dirty_rows[r] = 0;
|
||||
}
|
||||
|
||||
int input_y = cy + (int)DU_SPACE_SM + s->cur_row * (int)DU_ASCII_LINE_H;
|
||||
@@ -987,6 +1078,9 @@ static void bash_draw_to(bash_state *s, int cx, int cy, int cw, int ch) {
|
||||
du_fill_rect(&g_fb, cursor_x, input_y + (int)DU_ASCII_CELL_H - 3,
|
||||
(int)DU_ASCII_CELL_W, 2, KS_ACCENT);
|
||||
}
|
||||
|
||||
/* 清除全局脏标记 */
|
||||
s->any_dirty = 0;
|
||||
}
|
||||
|
||||
/* ---- 窗口适配 ---- */
|
||||
@@ -1365,7 +1459,11 @@ static void register_apps(void) {
|
||||
}
|
||||
|
||||
/* Winux-Kate .desktop-grid:canvas padding 24px,列宽 104px,gap 18px,
|
||||
* 左→右排列,超出画布宽度则换行(align-content: start) */
|
||||
* 左→右排列,超出画布宽度则换行(align-content: start)
|
||||
*
|
||||
* 注意:Terminal 和 CMD 已在第一页 Dashboard(TERM-01 / TERM-02),
|
||||
* 桌面不再重复显示其图标。
|
||||
*/
|
||||
static void setup_desktop_icons(void) {
|
||||
int pad = KATE_ICON_PAD; /* 24 */
|
||||
int x = pad;
|
||||
@@ -1373,6 +1471,9 @@ static void setup_desktop_icons(void) {
|
||||
int max_x = (int)g_fb_w - pad; /* canvas 右缘 */
|
||||
for (int i = 0; i < g_app_count; i++) {
|
||||
if (!g_apps[i].on_create && !g_apps[i].elf_name) continue;
|
||||
/* 跳过 Terminal 和 CMD(已在 Dashboard 第一页) */
|
||||
if (g_apps[i].name[0] == 's' && g_apps[i].name[1] == 'h' && g_apps[i].name[2] == 'e') continue;
|
||||
if (g_apps[i].name[0] == 'c' && g_apps[i].name[1] == 'm' && g_apps[i].name[2] == 'd') continue;
|
||||
if (x > pad && x + KATE_ICON_W > max_x) {
|
||||
x = pad;
|
||||
y += KATE_ICON_H + KATE_ICON_GAP;
|
||||
@@ -1803,10 +1904,10 @@ static void draw_topbar(void) {
|
||||
du_divider_v(&g_fb, x - 4, 6, KATE_TOPBAR_H - 12, KS_BORDER);
|
||||
x += 4;
|
||||
|
||||
/* 页面切换 */
|
||||
static const char *pg_names[3] = { "1.DASHBOARD", "2.IDE", "3.DESKTOP" };
|
||||
/* 页面切换:精简按钮文字 */
|
||||
static const char *pg_names[3] = { "DASH", "IDE", "DESK" };
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int bw = kstrlen(pg_names[i]) * (int)DU_ASCII_STEP + 16;
|
||||
int bw = kstrlen(pg_names[i]) * (int)DU_ASCII_STEP + 24;
|
||||
int active = (g_page == i + 1);
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + bw &&
|
||||
g_mouse_y >= 5 && g_mouse_y < 29;
|
||||
@@ -1817,11 +1918,11 @@ static void draw_topbar(void) {
|
||||
du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT_DIM);
|
||||
}
|
||||
du_rect_outline(&g_fb, x, 5, bw, 24, active ? KS_ACCENT : KS_BORDER, 1);
|
||||
du_draw_string(&g_fb, pg_names[i], x + 8, ty,
|
||||
du_draw_string(&g_fb, pg_names[i], x + 12, ty,
|
||||
active ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
g_pg_rect[i][0] = x; g_pg_rect[i][1] = 5;
|
||||
g_pg_rect[i][2] = bw; g_pg_rect[i][3] = 24;
|
||||
x += bw + 6;
|
||||
x += bw + 8;
|
||||
}
|
||||
/* 自定义页 */
|
||||
for (int i = 0; i < g_cpage_count; i++) {
|
||||
@@ -2006,7 +2107,7 @@ static void dash_layout(void) {
|
||||
int pad = 8, gap = 8;
|
||||
int avail_w = (int)g_fb_w - 2 * pad;
|
||||
int avail_h = bot - top - 2 * pad;
|
||||
int left_w = avail_w * 42 / 100;
|
||||
int left_w = avail_w * 35 / 100; /* 终端面板:42%→35% */
|
||||
int right_x = pad + left_w + gap;
|
||||
int right_w = (int)g_fb_w - pad - right_x;
|
||||
int row_h = (avail_h - gap) / 2;
|
||||
@@ -2524,9 +2625,21 @@ static void draw_ctx_menu(void) {
|
||||
* 全屏重绘
|
||||
* ============================================================ */
|
||||
|
||||
/* 全局亮度(BRIGHT 滑块真实调暗) */
|
||||
/* 全局亮度(BRIGHT 滑块真实调暗)
|
||||
* 性能优化:仅在亮度变化时处理,避免每帧全屏遍历 */
|
||||
static void apply_brightness(void) {
|
||||
if (g_bright >= 100) return;
|
||||
/* 快速退出:亮度100%无需调整 */
|
||||
if (g_bright >= 100) {
|
||||
g_bright_applied = 100;
|
||||
return;
|
||||
}
|
||||
|
||||
/* 快速退出:亮度未变化,跳过处理 */
|
||||
if (g_bright == g_bright_applied) return;
|
||||
|
||||
/* 记录当前应用的亮度值 */
|
||||
g_bright_applied = g_bright;
|
||||
|
||||
u32 f = (u32)g_bright;
|
||||
for (u64 y = 0; y < g_fb_h; y++) {
|
||||
u32 *line = (u32 *)((u8 *)g_fb.fb + y * g_fb_pitch);
|
||||
@@ -3137,8 +3250,28 @@ void dsk_entry(const dsk_boot_context *ctx) {
|
||||
}
|
||||
|
||||
if (need_redraw) {
|
||||
redraw_all();
|
||||
flip_buffer(); /* 全屏 flip:无闪烁 */
|
||||
/* 脏矩形渲染:合并脏矩形,只 flip 变化区域 */
|
||||
if (g_full_redraw || g_dirty_count == 0) {
|
||||
/* 全屏重绘 */
|
||||
redraw_all();
|
||||
flip_buffer();
|
||||
} else {
|
||||
/* 合并脏矩形 */
|
||||
int dx, dy, dw, dh;
|
||||
merge_dirty_rects(&dx, &dy, &dw, &dh);
|
||||
|
||||
/* 重绘并 flip 脏区域 */
|
||||
if (dw > 0 && dh > 0) {
|
||||
/* 清除脏矩形标记 */
|
||||
clear_dirty_rects();
|
||||
|
||||
/* 重绘整个屏幕(简化:暂不支持局部重绘) */
|
||||
redraw_all();
|
||||
|
||||
/* Flip 合并后的脏区域 */
|
||||
flip_rect(dx, dy, dw, dh);
|
||||
}
|
||||
}
|
||||
} else if (moved) {
|
||||
/* 仅光标移动:局部 flip(双缓冲 → 无闪烁) */
|
||||
int old_x = g_cursor_old_x, old_y = g_cursor_old_y;
|
||||
|
||||
@@ -302,7 +302,7 @@ static void stroke_rounded_rect(u32 *fb, i64 x, i64 y, i64 w, i64 h, i64 radius,
|
||||
}
|
||||
|
||||
static void delay_frame(void) {
|
||||
delay_ms_fi(33); /* 实机: 基于 TSC 的 33ms 帧间隔 (~30fps) */
|
||||
delay_ms_fi(11); /* 实机: 基于 TSC 的 11ms 帧间隔 (原33ms/3,加快打字机效果) */
|
||||
}
|
||||
|
||||
#include "text_bitmaps.c"
|
||||
|
||||
Binary file not shown.
+73
-10
@@ -31,9 +31,15 @@ echo "[build] Linux source: $LINUX_SRC"
|
||||
# 1. Clone Linux 6.6 LTS if not present
|
||||
if [ ! -d "$LINUX_SRC" ]; then
|
||||
echo "[build] Cloning Linux 6.6 LTS..."
|
||||
# 优先 kernel.org,失败则 fallback 清华镜像
|
||||
git clone --depth 1 --branch v6.6 \
|
||||
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git \
|
||||
"$LINUX_SRC"
|
||||
"$LINUX_SRC" || {
|
||||
echo "[build] kernel.org failed, trying TUNA mirror..."
|
||||
git clone --depth 1 --branch v6.6 \
|
||||
https://mirrors.tuna.tsinghua.edu.cn/git/linux.git \
|
||||
"$LINUX_SRC"
|
||||
}
|
||||
fi
|
||||
|
||||
cd "$LINUX_SRC"
|
||||
@@ -101,19 +107,32 @@ echo "[build] bzImage: $OUTPUT_DIR/linux-bzImage ($(du -h $OUTPUT_DIR/linux-bzIm
|
||||
# 6. Build initramfs if initramfs/ directory exists
|
||||
if [ -d "$INITRAMFS_DIR" ]; then
|
||||
echo "[build] Building initramfs..."
|
||||
# Create a minimal initramfs with busybox or a simple init
|
||||
INITRAMFS_TMP="$SCRIPT_DIR/initramfs.tmp"
|
||||
rm -rf "$INITRAMFS_TMP"
|
||||
mkdir -p "$INITRAMFS_TMP"
|
||||
mkdir -p "$INITRAMFS_TMP/bin"
|
||||
|
||||
# Copy initramfs contents
|
||||
# Copy initramfs contents (init + bin/)
|
||||
cp -a "$INITRAMFS_DIR"/* "$INITRAMFS_TMP"/
|
||||
|
||||
# Build utsm_exec_daemon (static binary for initramfs)
|
||||
# 6a. Static busybox(缓存于 CODE/linux/.cache/,只下载一次)
|
||||
# busybox.net 官方预编译 x86_64 musl 静态二进制,含 sh/mount/uname 等 applet。
|
||||
BUSYBOX_CACHE="$SCRIPT_DIR/.cache/busybox"
|
||||
BUSYBOX_URL="https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox"
|
||||
if [ ! -x "$BUSYBOX_CACHE" ]; then
|
||||
echo "[build] Downloading static busybox..."
|
||||
mkdir -p "$(dirname "$BUSYBOX_CACHE")"
|
||||
curl -fL --retry 3 -o "$BUSYBOX_CACHE" "$BUSYBOX_URL" || \
|
||||
wget -O "$BUSYBOX_CACHE" "$BUSYBOX_URL"
|
||||
chmod +x "$BUSYBOX_CACHE"
|
||||
fi
|
||||
cp "$BUSYBOX_CACHE" "$INITRAMFS_TMP/bin/busybox"
|
||||
chmod +x "$INITRAMFS_TMP/bin/busybox"
|
||||
echo "[build] busybox: $(du -h "$INITRAMFS_TMP/bin/busybox" | cut -f1)"
|
||||
|
||||
# 6b. Build utsm_exec_daemon (static binary for initramfs)
|
||||
DAEMON_SRC="$INITRAMFS_DIR/bin/utsm_exec_daemon.c"
|
||||
if [ -f "$DAEMON_SRC" ]; then
|
||||
echo "[build] Compiling utsm_exec_daemon (static)..."
|
||||
mkdir -p "$INITRAMFS_TMP/bin"
|
||||
cc -static -O2 -Wall -I"$PROJECT_ROOT/CODE/utsm-ipc" \
|
||||
-o "$INITRAMFS_TMP/bin/utsm_exec_daemon" "$DAEMON_SRC"
|
||||
strip "$INITRAMFS_TMP/bin/utsm_exec_daemon" 2>/dev/null || true
|
||||
@@ -128,18 +147,62 @@ mount -t proc none /proc
|
||||
mount -t sysfs none /sys
|
||||
mount -t devtmpfs none /dev
|
||||
echo "[utsm-linux] Boot complete"
|
||||
echo "[utsm-linux] Console ready on /dev/hvc0 (virtio-console)"
|
||||
exec /bin/sh
|
||||
INITEOF
|
||||
chmod +x "$INITRAMFS_TMP/init"
|
||||
fi
|
||||
|
||||
# Pack initramfs
|
||||
cd "$INITRAMFS_TMP"
|
||||
find . | cpio -H newc -o | gzip -9 > "$OUTPUT_DIR/linux-initrd.img"
|
||||
# 6c. 用内核自带 gen_init_cpio 打包(无需 root、无需 cpio 二进制)。
|
||||
# spec 文件声明目录/设备节点/文件/符号链接;设备节点元数据直接写入
|
||||
# cpio 归档,guest 内核 unpack 时即有 /dev/console 与 /dev/ttyS0。
|
||||
GEN_INIT_CPIO="$LINUX_SRC/usr/gen_init_cpio"
|
||||
if [ ! -x "$GEN_INIT_CPIO" ]; then
|
||||
echo "[build] Building gen_init_cpio host tool..."
|
||||
make usr/ >/dev/null
|
||||
fi
|
||||
|
||||
SPEC="$SCRIPT_DIR/initramfs.tmp.spec"
|
||||
{
|
||||
echo "dir /bin 755 0 0"
|
||||
echo "dir /dev 755 0 0"
|
||||
echo "dir /proc 755 0 0"
|
||||
echo "dir /sys 755 0 0"
|
||||
# 控制台设备节点:kernel console=ttyS0 输出与 busybox sh 依赖
|
||||
echo "nod /dev/console 600 0 0 c 5 1"
|
||||
echo "nod /dev/null 666 0 0 c 1 3"
|
||||
echo "nod /dev/ttyS0 600 0 0 c 4 64"
|
||||
echo "file /init $INITRAMFS_TMP/init 755 0 0"
|
||||
echo "file /bin/busybox $INITRAMFS_TMP/bin/busybox 755 0 0"
|
||||
if [ -f "$INITRAMFS_TMP/bin/utsm_exec_daemon" ]; then
|
||||
echo "file /bin/utsm_exec_daemon $INITRAMFS_TMP/bin/utsm_exec_daemon 755 0 0"
|
||||
fi
|
||||
# busybox applet 符号链接(init 脚本与调试命令依赖)
|
||||
for app in sh mount umount echo sleep uname ls cat ps setsid cttyhack; do
|
||||
echo "slink /bin/$app /bin/busybox 777 0 0"
|
||||
done
|
||||
} > "$SPEC"
|
||||
|
||||
"$GEN_INIT_CPIO" "$SPEC" | gzip -9 > "$OUTPUT_DIR/linux-initrd.img"
|
||||
echo "[build] initrd: $OUTPUT_DIR/linux-initrd.img ($(du -h $OUTPUT_DIR/linux-initrd.img | cut -f1))"
|
||||
rm -rf "$INITRAMFS_TMP" "$SPEC"
|
||||
cd "$LINUX_SRC"
|
||||
fi
|
||||
|
||||
echo "[build] Done. Copy linux-bzImage (and linux-initrd.img) to your FAT32 image."
|
||||
echo "[build] Limine will load them as boot modules for UTSM."
|
||||
|
||||
# 7. Enable Linux modules in limine.conf (uncomment the 4 commented lines)
|
||||
LIMINE_CONF="$PROJECT_ROOT/SYSTEM/limine/limine.conf"
|
||||
if [ -f "$LIMINE_CONF" ]; then
|
||||
if grep -q "^ # module_path: boot():/boot/linux-bzImage" "$LIMINE_CONF"; then
|
||||
echo "[build] Enabling Linux modules in limine.conf..."
|
||||
sed -i 's|^ # module_path: boot():/boot/linux-bzImage| module_path: boot():/boot/linux-bzImage|' "$LIMINE_CONF"
|
||||
sed -i 's|^ # module_cmdline: linux:bzImage| module_cmdline: linux:bzImage|' "$LIMINE_CONF"
|
||||
sed -i 's|^ # module_path: boot():/boot/linux-initrd.img| module_path: boot():/boot/linux-initrd.img|' "$LIMINE_CONF"
|
||||
sed -i 's|^ # module_cmdline: linux:initrd| module_cmdline: linux:initrd|' "$LIMINE_CONF"
|
||||
echo "[build] Linux modules enabled in $LIMINE_CONF"
|
||||
echo "[build] Run build.bat to rebuild the disk image with Linux modules"
|
||||
else
|
||||
echo "[build] limine.conf already has Linux modules enabled (or pattern changed)"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# check_env.sh — Linux 内核构建环境依赖检查脚本
|
||||
#
|
||||
# 用途: 在 WSL/Linux 中运行,验证构建 Linux 6.6 LTS bzImage 所需依赖
|
||||
#
|
||||
# 使用:
|
||||
# ! wsl bash /mnt/d/Code/Deshab/CODE/linux/check_env.sh
|
||||
#
|
||||
# 退出码:
|
||||
# 0 = 所有依赖已满足
|
||||
# 1 = 缺少依赖(脚本会打印安装命令)
|
||||
|
||||
set -e
|
||||
|
||||
echo "[check] Linux 内核构建环境检查"
|
||||
echo "[check] ==========================="
|
||||
|
||||
# 1. 检查操作系统
|
||||
if [ -f /etc/os-release ]; then
|
||||
. /etc/os-release
|
||||
echo "[check] OS: $NAME $VERSION"
|
||||
if [ "$ID" != "ubuntu" ] && [ "$ID_LIKE" != "debian" ]; then
|
||||
echo "[check] WARNING: 非 Ubuntu/Debian,依赖名可能不同"
|
||||
fi
|
||||
else
|
||||
echo "[check] WARNING: 无法识别操作系统"
|
||||
fi
|
||||
|
||||
# 2. 检查必要包
|
||||
echo "[check]"
|
||||
echo "[check] 检查构建依赖..."
|
||||
MISSING=()
|
||||
for pkg in build-essential bc flex bison libelf-dev libssl-dev cpio gzip git; do
|
||||
if dpkg -s "$pkg" >/dev/null 2>&1; then
|
||||
echo "[check] ✓ $pkg"
|
||||
else
|
||||
echo "[check] ✗ $pkg (缺失)"
|
||||
MISSING+=("$pkg")
|
||||
fi
|
||||
done
|
||||
|
||||
# 3. 检查 gcc 版本
|
||||
echo "[check]"
|
||||
echo "[check] 工具链版本:"
|
||||
if command -v gcc >/dev/null 2>&1; then
|
||||
GCC_VER=$(gcc -dumpversion)
|
||||
echo "[check] gcc: $GCC_VER"
|
||||
if [ "$(printf '%s\n' "11" "$GCC_VER" | sort -V | head -n1)" != "11" ]; then
|
||||
echo "[check] WARNING: gcc 版本 < 11,Linux 6.6 可能需要 gcc 11+"
|
||||
fi
|
||||
else
|
||||
echo "[check] ✗ gcc 未安装"
|
||||
MISSING+=("gcc")
|
||||
fi
|
||||
|
||||
if command -v make >/dev/null 2>&1; then
|
||||
echo "[check] make: $(make --version | head -1 | awk '{print $3}')"
|
||||
fi
|
||||
|
||||
# 4. 检查磁盘空间(需要 ~3GB)
|
||||
echo "[check]"
|
||||
PROJECT_ROOT="/mnt/d/Code/Deshab"
|
||||
if [ -d "$PROJECT_ROOT" ]; then
|
||||
AVAILABLE_KB=$(df "$PROJECT_ROOT" | awk 'NR==2 {print $4}')
|
||||
AVAILABLE_GB=$((AVAILABLE_KB / 1024 / 1024))
|
||||
echo "[check] 磁盘空间: ${AVAILABLE_GB}GB 可用 ($PROJECT_ROOT)"
|
||||
if [ "$AVAILABLE_GB" -lt 3 ]; then
|
||||
echo "[check] ✗ 磁盘空间不足 3GB"
|
||||
MISSING+=("disk_space")
|
||||
else
|
||||
echo "[check] ✓ 磁盘空间充足"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 5. 检查访问 Windows D: 盘
|
||||
echo "[check]"
|
||||
if [ -d "/mnt/d/Code/Deshab" ]; then
|
||||
echo "[check] ✓ WSL 可访问 /mnt/d/Code/Deshab"
|
||||
else
|
||||
echo "[check] ✗ WSL 无法访问 /mnt/d/Code/Deshab"
|
||||
echo "[check] 请确认在 WSL 中执行,且 D: 盘已挂载"
|
||||
MISSING+=("wsl_mount")
|
||||
fi
|
||||
|
||||
# 6. 检查 git 网络(克隆 Linux 6.6 需要)
|
||||
echo "[check]"
|
||||
echo "[check] 测试 Git 网络连通..."
|
||||
if timeout 10 git ls-remote https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git v6.6 >/dev/null 2>&1; then
|
||||
echo "[check] ✓ kernel.org 可达"
|
||||
else
|
||||
echo "[check] ✗ kernel.org 不可达,尝试清华镜像..."
|
||||
if timeout 10 git ls-remote https://mirrors.tuna.tsinghua.edu.cn/git/linux.git v6.6 >/dev/null 2>&1; then
|
||||
echo "[check] ✓ 清华镜像可达(build.sh 会自动使用)"
|
||||
else
|
||||
echo "[check] ✗ 两个源都不可达,请检查网络/代理"
|
||||
MISSING+=("network")
|
||||
fi
|
||||
fi
|
||||
|
||||
# 7. 总结
|
||||
echo "[check]"
|
||||
echo "[check] ==========================="
|
||||
if [ ${#MISSING[@]} -gt 0 ]; then
|
||||
echo "[check] 缺失: ${MISSING[*]}"
|
||||
echo "[check]"
|
||||
echo "[check] 请运行以下命令安装依赖:"
|
||||
echo "[check] sudo apt update"
|
||||
echo "[check] sudo apt install -y build-essential bc flex bison libelf-dev libssl-dev cpio gzip git"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[check] ✓ 所有依赖已满足,可以构建 Linux 内核"
|
||||
echo "[check]"
|
||||
echo "[check] 下一步执行:"
|
||||
echo "[check] cd /mnt/d/Code/Deshab/CODE/linux && chmod +x build.sh && ./build.sh"
|
||||
exit 0
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# Fix UTSM driver entries in Linux source tree
|
||||
SRC="${1:-/tmp/linux-6.6}"
|
||||
|
||||
# Fix Kconfig: remove broken line, add correct one
|
||||
sed -i '/utsm/d' "$SRC/drivers/Kconfig"
|
||||
echo 'source "drivers/utsm/Kconfig"' >> "$SRC/drivers/Kconfig"
|
||||
|
||||
# Fix Makefile: remove broken line, add correct one
|
||||
sed -i '/utsm/d' "$SRC/drivers/Makefile"
|
||||
echo 'obj-$(CONFIG_UTSM_HCALL) += utsm/' >> "$SRC/drivers/Makefile"
|
||||
|
||||
echo "Fixed UTSM driver entries"
|
||||
tail -2 "$SRC/drivers/Kconfig"
|
||||
echo "---"
|
||||
tail -2 "$SRC/drivers/Makefile"
|
||||
@@ -33,10 +33,19 @@ fi
|
||||
# Start the exec daemon in the background.
|
||||
# The daemon sends EXEC_READY then HLTs (parks the guest).
|
||||
# UTSM's linux_launch() returns after this first HLT.
|
||||
echo "[utsm-linux] starting utsm_exec_daemon..."
|
||||
/bin/utsm_exec_daemon &
|
||||
DAEMON_PID=$!
|
||||
echo "[utsm-linux] daemon PID=$DAEMON_PID"
|
||||
if [ -x /bin/utsm_exec_daemon ]; then
|
||||
echo "[utsm-linux] starting utsm_exec_daemon..."
|
||||
/bin/utsm_exec_daemon &
|
||||
DAEMON_PID=$!
|
||||
echo "[utsm-linux] daemon PID=$DAEMON_PID"
|
||||
else
|
||||
# 无 daemon(第一阶段 bring-up 冒烟测试构建):
|
||||
# 直接在 ttyS0 上起交互 shell,验证 console 输入输出。
|
||||
# 注意:此模式下 guest 不会 park,UTSM linux_launch() 不返回,
|
||||
# 仅用于验证"Linux guest 进入 shell 提示符、可执行 uname -a"。
|
||||
echo "[utsm-linux] WARNING: no daemon, starting interactive debug shell on ttyS0"
|
||||
exec /bin/setsid /bin/cttyhack /bin/sh
|
||||
fi
|
||||
|
||||
# init must not exit — sleep forever to keep the kernel happy.
|
||||
# The daemon handles all UTSM exec requests; init just keeps the
|
||||
|
||||
@@ -1942,6 +1942,12 @@ void dsk_entry(const dsk_boot_context *ctx) {
|
||||
g_history_view = -1;
|
||||
term_putc('\n');
|
||||
execute_command(input_buf);
|
||||
/* 确保命令输出后在新行绘制提示符 */
|
||||
if (cur_col != 0) {
|
||||
term_putc('\n');
|
||||
}
|
||||
/* 刷新终端显示 */
|
||||
term_redraw_all();
|
||||
input_len = 0;
|
||||
input_cursor = 0;
|
||||
input_buf[0] = 0;
|
||||
|
||||
Binary file not shown.
@@ -63,6 +63,22 @@ static void load_current_dir(void) {
|
||||
scroll_off = 0;
|
||||
u32 dir_clus = (path_depth == 0) ? 0 : path_clus[path_depth - 1];
|
||||
f32_list_dir(dir_clus, fm_collect_cb, 0);
|
||||
|
||||
/* 如果不是根目录,在开头添加 ".." 返回上级条目 */
|
||||
if (path_depth > 0 && file_count < MAX_FILES) {
|
||||
/* 将现有文件向后移动 */
|
||||
for (int i = file_count; i > 0; i--) {
|
||||
files[i] = files[i - 1];
|
||||
}
|
||||
/* 在开头插入 ".." 条目 */
|
||||
files[0].name[0] = '.';
|
||||
files[0].name[1] = '.';
|
||||
files[0].name[2] = 0;
|
||||
files[0].clus = 0; /* 特殊标记 */
|
||||
files[0].size = 0;
|
||||
files[0].is_dir = 1;
|
||||
file_count++;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 路径显示 ---- */
|
||||
@@ -243,6 +259,12 @@ static void open_selected(void) {
|
||||
if (selected < 0 || selected >= file_count) return;
|
||||
fm_entry *f = &files[selected];
|
||||
|
||||
/* 特殊处理 ".." 条目 */
|
||||
if (path_depth > 0 && selected == 0 && f->name[0] == '.' && f->name[1] == '.' && f->name[2] == 0) {
|
||||
go_up();
|
||||
return;
|
||||
}
|
||||
|
||||
if (f->is_dir) {
|
||||
/* 进入子目录 */
|
||||
if (path_depth < MAX_DEPTH - 1) {
|
||||
@@ -279,6 +301,42 @@ static void go_up(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* 查找并进入 SYSTEM/user 目录 */
|
||||
static void find_user_dir(void) {
|
||||
/* 先查找 SYSTEM 目录 */
|
||||
file_count = 0;
|
||||
f32_list_dir(0, fm_collect_cb, 0);
|
||||
u32 system_clus = 0;
|
||||
for (int i = 0; i < file_count; i++) {
|
||||
if (files[i].is_dir && files[i].name[0] == 'S' && files[i].name[1] == 'Y' &&
|
||||
files[i].name[2] == 'S' && files[i].name[3] == 'T' &&
|
||||
files[i].name[4] == 'E' && files[i].name[5] == 'M') {
|
||||
system_clus = files[i].clus;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!system_clus) return; /* SYSTEM 目录不存在,保持根目录 */
|
||||
|
||||
/* 在 SYSTEM 中查找 user 目录 */
|
||||
file_count = 0;
|
||||
f32_list_dir(system_clus, fm_collect_cb, 0);
|
||||
for (int i = 0; i < file_count; i++) {
|
||||
if (files[i].is_dir && files[i].name[0] == 'U' && files[i].name[1] == 'S' &&
|
||||
files[i].name[2] == 'E' && files[i].name[3] == 'R') {
|
||||
/* 找到了 SYSTEM/user,设置路径栈 */
|
||||
path_clus[0] = system_clus;
|
||||
path_name[0][0] = 'S'; path_name[0][1] = 'Y'; path_name[0][2] = 'S';
|
||||
path_name[0][3] = 'T'; path_name[0][4] = 'E'; path_name[0][5] = 'M';
|
||||
path_name[0][6] = 0;
|
||||
path_clus[1] = files[i].clus;
|
||||
path_name[1][0] = 'U'; path_name[1][1] = 'S'; path_name[1][2] = 'E';
|
||||
path_name[1][3] = 'R'; path_name[1][4] = 0;
|
||||
path_depth = 2;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
void dsk_entry(const da_boot_context *ctx) {
|
||||
__asm__ volatile("cli");
|
||||
@@ -297,6 +355,8 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
f32_init((f32_block_read_fn)g_ac.block_read, (f32_block_write_fn)g_ac.block_write);
|
||||
|
||||
path_depth = 0;
|
||||
/* 尝试进入 SYSTEM/user 目录 */
|
||||
find_user_dir();
|
||||
load_current_dir();
|
||||
redraw_all();
|
||||
|
||||
@@ -307,15 +367,23 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
/* 鼠标轮询 */
|
||||
if (da_mouse_poll(&g_mouse, &g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h)) {
|
||||
need_redraw = 1;
|
||||
if (g_cursor.btn && !view_mode) {
|
||||
int top_y = DA_TITLEBAR_H + 4 + CHAR_H + 4;
|
||||
int row_h = CHAR_H + 6;
|
||||
int click_y = g_cursor.my - top_y - CHAR_H - 6;
|
||||
if (click_y >= 0) {
|
||||
int idx = click_y / row_h + scroll_off;
|
||||
if (idx >= 0 && idx < file_count) {
|
||||
selected = idx;
|
||||
need_redraw = 1;
|
||||
if (g_cursor.btn) {
|
||||
/* 检查关闭按钮点击(右上角:w-28, 4, 24, 20) */
|
||||
if (g_cursor.mx >= (i64)g_ac.fb_w - 28 && g_cursor.mx < (i64)g_ac.fb_w - 4 &&
|
||||
g_cursor.my >= 4 && g_cursor.my < 24) {
|
||||
da_slog("fileman", "close button clicked");
|
||||
return;
|
||||
}
|
||||
if (!view_mode) {
|
||||
int top_y = DA_TITLEBAR_H + 4 + CHAR_H + 4;
|
||||
int row_h = CHAR_H + 6;
|
||||
int click_y = g_cursor.my - top_y - CHAR_H - 6;
|
||||
if (click_y >= 0) {
|
||||
int idx = click_y / row_h + scroll_off;
|
||||
if (idx >= 0 && idx < file_count) {
|
||||
selected = idx;
|
||||
need_redraw = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,630 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
gen_pe.py — Deshab PE 兼容层测试 PE 程序生成器(无需 MinGW/MSVC)
|
||||
|
||||
用途: 当宿主机无 MinGW/MSVC 时,用纯 Python 构造最小 PE32+ 和 PE32 二进制,
|
||||
验证 pe_loader 解析与 pe_shim/x86emu32 执行路径。
|
||||
|
||||
构造的 PE 特征:
|
||||
- PE32+ (64位): machine=0x8664, entry=main, image_base=0x140000000
|
||||
- PE32 (32位): machine=0x014c, entry=main, image_base=0x00400000
|
||||
- 单 section (.text), 4KB 对齐
|
||||
- IAT 仅 2 项: msvcrt.dll!printf + kernel32.dll!ExitProcess
|
||||
- main 函数: 调用 printf("Hello from PE32+/PE32!\\r\\n") → 调用 ExitProcess(0)
|
||||
|
||||
输出:
|
||||
hello64.exe - PE32+ 二进制 (供 HELLO64 EXE 测试用)
|
||||
hello32.exe - PE32 二进制 (供 HELLO32 EXE 测试用)
|
||||
|
||||
用法:
|
||||
python gen_pe.py
|
||||
# 生成 hello64.exe / hello32.exe 到当前目录
|
||||
# 之后复制到 SYSTEM/system/deshab64/tools/ 并重新运行 build.bat
|
||||
"""
|
||||
|
||||
import struct
|
||||
import sys
|
||||
import os
|
||||
|
||||
# ============================================================================
|
||||
# PE32+ (64位) 构造
|
||||
# ============================================================================
|
||||
|
||||
IMAGE_DOS_SIGNATURE = 0x5A4D # 'MZ'
|
||||
IMAGE_NT_SIGNATURE = 0x00004550 # 'PE\0\0'
|
||||
IMAGE_FILE_MACHINE_I386 = 0x014c
|
||||
IMAGE_FILE_MACHINE_AMD64 = 0x8664
|
||||
IMAGE_FILE_MACHINE_I386_CHARACTERISTICS = 0x0102 # EXECUTABLE_IMAGE | 32BIT_MACHINE
|
||||
IMAGE_FILE_MACHINE_AMD64_CHARACTERISTICS = 0x0022 # EXECUTABLE_IMAGE | LARGE_ADDRESS_AWARE
|
||||
IMAGE_SUBSYSTEM_WINDOWS_CUI = 3
|
||||
IMAGE_SCN_CNT_CODE = 0x20
|
||||
IMAGE_SCN_MEM_EXECUTE = 0x20000000
|
||||
IMAGE_SCN_MEM_READ = 0x40000000
|
||||
IMAGE_DIRECTORY_ENTRY_IMPORT = 1
|
||||
|
||||
|
||||
def build_pe32_plus():
|
||||
"""构造 PE32+ (64位) 测试程序。"""
|
||||
image_base = 0x140000000
|
||||
section_align = 0x1000
|
||||
file_align = 0x200
|
||||
|
||||
# DOS header (64 bytes) + e_lfanew
|
||||
dos_header = bytearray(64)
|
||||
struct.pack_into('<H', dos_header, 0, IMAGE_DOS_SIGNATURE) # e_magic
|
||||
dos_header[2:60] = b'\x00' * 58
|
||||
struct.pack_into('<I', dos_header, 60, 64) # e_lfanew = 64 (PE header at offset 64)
|
||||
|
||||
# PE signature
|
||||
pe_sig = struct.pack('<I', IMAGE_NT_SIGNATURE)
|
||||
|
||||
# COFF file header (20 bytes)
|
||||
num_sections = 1
|
||||
time_stamp = 0
|
||||
sym_table_ptr = 0
|
||||
num_symbols = 0
|
||||
optional_header_size = 240 # PE32+ optional header size
|
||||
characteristics = IMAGE_FILE_MACHINE_AMD64_CHARACTERISTICS
|
||||
coff_header = struct.pack('<HHIIIHH',
|
||||
IMAGE_FILE_MACHINE_AMD64,
|
||||
num_sections,
|
||||
time_stamp,
|
||||
sym_table_ptr,
|
||||
num_symbols,
|
||||
optional_header_size,
|
||||
characteristics)
|
||||
|
||||
# PE32+ Optional header (240 bytes)
|
||||
# 布局:
|
||||
# Magic (2) = 0x20b (PE32+)
|
||||
# MajorLinkerVersion (1) = 0
|
||||
# MinorLinkerVersion (1) = 0
|
||||
# SizeOfCode (4) = .text 大小
|
||||
# SizeOfInitializedData (4) = 0
|
||||
# SizeOfUninitializedData (4) = 0
|
||||
# AddressOfEntryPoint (4) = main RVA
|
||||
# BaseOfCode (4) = 0x1000
|
||||
# ImageBase (8) = 0x140000000
|
||||
# SectionAlignment (4) = 0x1000
|
||||
# FileAlignment (4) = 0x200
|
||||
# MajorOperatingSystemVersion (2) = 6
|
||||
# MinorOperatingSystemVersion (2) = 0
|
||||
# MajorImageVersion (2) = 0
|
||||
# MinorImageVersion (2) = 0
|
||||
# MajorSubsystemVersion (2) = 6
|
||||
# MinorSubsystemVersion (2) = 0
|
||||
# Win32VersionValue (4) = 0
|
||||
# SizeOfImage (4) = 0x3000
|
||||
# SizeOfHeaders (4) = 0x200
|
||||
# CheckSum (4) = 0
|
||||
# Subsystem (2) = IMAGE_SUBSYSTEM_WINDOWS_CUI = 3
|
||||
# DllCharacteristics (2) = 0
|
||||
# SizeOfStackReserve (8) = 0x100000
|
||||
# SizeOfStackCommit (8) = 0x1000
|
||||
# SizeOfHeapReserve (8) = 0x100000
|
||||
# SizeOfHeapCommit (8) = 0x1000
|
||||
# LoaderFlags (4) = 0
|
||||
# NumberOfRvaAndSizes (4) = 16
|
||||
# DataDirectory[16] (8*16 = 128 bytes)
|
||||
|
||||
# .text 段机器码(x86_64):
|
||||
# main:
|
||||
# lea rcx, [rip + msg] ; 48 8D 0D XX XX XX XX (printf arg1 = msg)
|
||||
# call [rip + printf_iat] ; FF 15 XX XX XX XX (call IAT[printf])
|
||||
# xor ecx, ecx ; 31 C9 (ExitProcess arg1 = 0)
|
||||
# call [rip + exitproc_iat] ; FF 15 XX XX XX XX (call IAT[ExitProcess])
|
||||
# ret ; C3
|
||||
# msg: "Hello from PE32+!\r\n\0"
|
||||
msg = b"Hello from PE32+!\r\n\0"
|
||||
# 占位用,后面填正确偏移
|
||||
code = bytearray()
|
||||
code += b'\x48\x8D\x0D' + b'\x00\x00\x00\x00' # lea rcx, [rip+0] (placeholder)
|
||||
code += b'\xFF\x15' + b'\x00\x00\x00\x00' # call [rip+0] (placeholder)
|
||||
code += b'\x31\xC9' # xor ecx, ecx
|
||||
code += b'\xFF\x15' + b'\x00\x00\x00\x00' # call [rip+0] (placeholder)
|
||||
code += b'\xC3' # ret
|
||||
code += msg
|
||||
|
||||
code_rva = 0x1000 # .text RVA
|
||||
main_rva = code_rva
|
||||
printf_iat_rva = code_rva + 0x200 # IAT 在 .text 后(同一 section 内,简化)
|
||||
exitproc_iat_rva = printf_iat_rva + 8
|
||||
|
||||
# 修正 lea rcx, [rip + msg]: msg 在 code[19] 之后(lea 7字节 + call 6 + xor 2 + call 6 + ret 1 = 22)
|
||||
msg_offset_in_code = 22
|
||||
lea_rip_offset = msg_offset_in_code - len(code) + len(code) # 偏移=22-0=22(lea 后下一指令的 RIP)
|
||||
# lea rcx, [rip + disp32]: disp32 = msg - (next_instr_rip)
|
||||
# next_instr_rip = main_rva + 7
|
||||
# msg = main_rva + 22
|
||||
# disp32 = 22 - 7 = 15
|
||||
struct.pack_into('<i', code, 3, 15)
|
||||
# 修正 call [rip + printf_iat]: printf IAT 在 printf_iat_rva, call 后下一指令 RIP = main_rva + 13
|
||||
# disp32 = printf_iat_rva - (main_rva + 13) = 0x200 - 13 = 499
|
||||
struct.pack_into('<i', code, 10, 0x200 - 13)
|
||||
# 修正 call [rip + exitproc]: ExitProcess IAT 在 exitproc_iat_rva, call 后下一指令 RIP = main_rva + 21
|
||||
# disp32 = exitproc_iat_rva - (main_rva + 21) = 0x208 - 21 = 491
|
||||
struct.pack_into('<i', code, 17, 0x208 - 21)
|
||||
|
||||
# IAT 8字节对齐填充
|
||||
while len(code) < 0x200:
|
||||
code += b'\x00'
|
||||
# IAT 内容(指向 import 函数名 RVA)
|
||||
iat_data = bytearray()
|
||||
iat_data += struct.pack('<Q', 0x300) # printf function name RVA (hint+name 表)
|
||||
iat_data += struct.pack('<Q', 0x320) # ExitProcess function name RVA
|
||||
code += iat_data
|
||||
|
||||
# Import descriptor 数据
|
||||
import_desc_rva = 0x400 # 在 IAT 后
|
||||
while len(code) < 0x300:
|
||||
code += b'\x00'
|
||||
|
||||
# Hint/Name 表
|
||||
# "msvcrt.dll\0" + printf name "printf\0"
|
||||
# "kernel32.dll\0" + ExitProcess name "ExitProcess\0"
|
||||
printf_name_rva = 0x300
|
||||
exitproc_name_rva = 0x320
|
||||
printf_hint_name = struct.pack('<H', 0) + b'printf\0' # hint=0
|
||||
exitproc_hint_name = struct.pack('<H', 0) + b'ExitProcess\0'
|
||||
while len(code) < printf_name_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += printf_hint_name
|
||||
while len(code) < exitproc_name_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += exitproc_hint_name
|
||||
|
||||
# DLL 名(放在 import descriptor 之后)
|
||||
msvcrt_dll_rva = 0x350
|
||||
kernel32_dll_rva = 0x360
|
||||
while len(code) < msvcrt_dll_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += b'msvcrt.dll\0'
|
||||
while len(code) < kernel32_dll_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += b'kernel32.dll\0'
|
||||
|
||||
# Import descriptor (5 * 4 + 4 = 24 bytes for original thunk 这里简化)
|
||||
while len(code) < import_desc_rva - code_rva:
|
||||
code += b'\x00'
|
||||
import_desc = struct.pack('<IIIII',
|
||||
printf_iat_rva, # OriginalFirstThunk
|
||||
0, # TimeDateStamp
|
||||
0, # ForwarderChain
|
||||
msvcrt_dll_rva, # Name
|
||||
printf_iat_rva) # FirstThunk
|
||||
code += import_desc
|
||||
import_desc2 = struct.pack('<IIIII',
|
||||
exitproc_iat_rva,
|
||||
0, 0,
|
||||
kernel32_dll_rva,
|
||||
exitproc_iat_rva)
|
||||
code += import_desc2
|
||||
# NULL 终止 import desc 数组
|
||||
code += b'\x00' * 20
|
||||
|
||||
# 对齐到 file_align
|
||||
while len(code) % file_align != 0:
|
||||
code += b'\x00'
|
||||
|
||||
size_of_code = len(code)
|
||||
size_of_image = code_rva + size_of_code
|
||||
# 修正 size_of_image 到 section_align
|
||||
while size_of_image % section_align != 0:
|
||||
size_of_image += 1
|
||||
|
||||
opt_header = bytearray(240)
|
||||
struct.pack_into('<H', opt_header, 0, 0x20b) # Magic = PE32+
|
||||
opt_header[2] = 0 # MajorLinkerVersion
|
||||
opt_header[3] = 0 # MinorLinkerVersion
|
||||
struct.pack_into('<I', opt_header, 4, size_of_code) # SizeOfCode
|
||||
struct.pack_into('<I', opt_header, 8, 0) # SizeOfInitializedData
|
||||
struct.pack_into('<I', opt_header, 12, 0) # SizeOfUninitializedData
|
||||
struct.pack_into('<I', opt_header, 16, main_rva) # AddressOfEntryPoint
|
||||
struct.pack_into('<I', opt_header, 20, code_rva) # BaseOfCode
|
||||
struct.pack_into('<Q', opt_header, 24, image_base) # ImageBase
|
||||
struct.pack_into('<I', opt_header, 32, section_align)
|
||||
struct.pack_into('<I', opt_header, 36, file_align)
|
||||
struct.pack_into('<H', opt_header, 40, 6) # MajorOperatingSystemVersion
|
||||
struct.pack_into('<H', opt_header, 42, 0)
|
||||
struct.pack_into('<H', opt_header, 44, 0) # MajorImageVersion
|
||||
struct.pack_into('<H', opt_header, 46, 0)
|
||||
struct.pack_into('<H', opt_header, 48, 6) # MajorSubsystemVersion
|
||||
struct.pack_into('<H', opt_header, 50, 0)
|
||||
struct.pack_into('<I', opt_header, 52, 0) # Win32VersionValue
|
||||
struct.pack_into('<I', opt_header, 56, size_of_image)
|
||||
struct.pack_into('<I', opt_header, 60, 0x200) # SizeOfHeaders
|
||||
struct.pack_into('<I', opt_header, 64, 0) # CheckSum
|
||||
struct.pack_into('<H', opt_header, 68, IMAGE_SUBSYSTEM_WINDOWS_CUI) # Subsystem
|
||||
struct.pack_into('<H', opt_header, 70, 0) # DllCharacteristics
|
||||
struct.pack_into('<Q', opt_header, 72, 0x100000) # SizeOfStackReserve
|
||||
struct.pack_into('<Q', opt_header, 80, 0x1000) # SizeOfStackCommit
|
||||
struct.pack_into('<Q', opt_header, 88, 0x100000) # SizeOfHeapReserve
|
||||
struct.pack_into('<Q', opt_header, 96, 0x1000) # SizeOfHeapCommit
|
||||
struct.pack_into('<I', opt_header, 104, 0) # LoaderFlags
|
||||
struct.pack_into('<I', opt_header, 108, 16) # NumberOfRvaAndSizes
|
||||
|
||||
# DataDirectory[16] = 16 entries * 8 bytes = 128 bytes
|
||||
data_dir_offset = 112
|
||||
# DataDirectory[1] = Import (RVA, Size)
|
||||
struct.pack_into('<II', opt_header, data_dir_offset + 8, import_desc_rva, 48)
|
||||
|
||||
# Section header (.text)
|
||||
section_header = bytearray(40)
|
||||
section_header[0:8] = b'.text\0\0\0' # Name
|
||||
struct.pack_into('<I', section_header, 8, size_of_code) # VirtualSize
|
||||
struct.pack_into('<I', section_header, 12, code_rva) # VirtualAddress
|
||||
struct.pack_into('<I', section_header, 16, size_of_code) # SizeOfRawData
|
||||
struct.pack_into('<I', section_header, 20, file_align) # PointerToRawData
|
||||
struct.pack_into('<I', section_header, 24, 0) # PointerToRelocations
|
||||
struct.pack_into('<I', section_header, 28, 0) # PointerToLinenumbers
|
||||
struct.pack_into('<H', section_header, 32, 0) # NumberOfRelocations
|
||||
struct.pack_into('<H', section_header, 34, 0) # NumberOfLinenumbers
|
||||
struct.pack_into('<I', section_header, 36,
|
||||
IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ)
|
||||
|
||||
# 组装文件
|
||||
headers = bytearray()
|
||||
headers += dos_header
|
||||
headers += pe_sig
|
||||
headers += coff_header
|
||||
headers += opt_header
|
||||
headers += section_header
|
||||
|
||||
# 头部对齐到 file_align
|
||||
while len(headers) % file_align != 0:
|
||||
headers += b'\x00'
|
||||
|
||||
return bytes(headers) + bytes(code)
|
||||
|
||||
|
||||
def build_pe32():
|
||||
"""构造 PE32 (32位) 测试程序(x86emu32 解释器执行)。
|
||||
|
||||
与 build_pe32_plus 的区别:
|
||||
- Machine = 0x014c (i386), Characteristics = 0x0102
|
||||
- Optional Header magic = 0x10b, 大小 224 字节
|
||||
- 有 BaseOfData 字段;ImageBase 为 4 字节
|
||||
- Stack/Heap Reserve/Commit 为 4 字节
|
||||
- IAT thunk 为 4 字节(struct '<I')
|
||||
- 机器码为 x86-32,使用 call [abs] 绝对寻址(FF 25 disp32)
|
||||
"""
|
||||
image_base = 0x00400000
|
||||
section_align = 0x1000
|
||||
file_align = 0x200
|
||||
|
||||
# DOS header (64 bytes) + e_lfanew
|
||||
dos_header = bytearray(64)
|
||||
struct.pack_into('<H', dos_header, 0, IMAGE_DOS_SIGNATURE)
|
||||
dos_header[2:60] = b'\x00' * 58
|
||||
struct.pack_into('<I', dos_header, 60, 64) # e_lfanew
|
||||
|
||||
pe_sig = struct.pack('<I', IMAGE_NT_SIGNATURE)
|
||||
|
||||
# COFF file header (20 bytes) — PE32 optional header = 224 字节
|
||||
num_sections = 1
|
||||
optional_header_size = 224
|
||||
characteristics = IMAGE_FILE_MACHINE_I386_CHARACTERISTICS # 0x0102
|
||||
coff_header = struct.pack('<HHIIIHH',
|
||||
IMAGE_FILE_MACHINE_I386,
|
||||
num_sections,
|
||||
0, 0, 0,
|
||||
optional_header_size,
|
||||
characteristics)
|
||||
|
||||
# .text 段机器码(x86-32):
|
||||
# main:
|
||||
# push msg_addr ; 68 XX XX XX XX (5) printf 参数
|
||||
# call [printf_iat_abs] ; FF 25 XX XX XX XX (6) 通过 IAT 调用
|
||||
# add esp, 4 ; 83 C4 04 (3) cdecl 清栈
|
||||
# push 0 ; 6A 00 (2) ExitProcess 参数
|
||||
# call [exit_iat_abs] ; FF 25 XX XX XX XX (6) 通过 IAT 调用
|
||||
# ret ; C3 (1)
|
||||
# msg: "Hello from PE32!\r\n\0"
|
||||
msg = b"Hello from PE32!\r\n\0"
|
||||
code_rva = 0x1000
|
||||
main_rva = code_rva
|
||||
printf_iat_rva = code_rva + 0x200 # 0x1200
|
||||
exitproc_iat_rva = printf_iat_rva + 4 # 0x1204 (4-byte thunk)
|
||||
printf_name_rva = 0x1300
|
||||
exitproc_name_rva = 0x1320
|
||||
msvcrt_dll_rva = 0x1350
|
||||
kernel32_dll_rva = 0x1360
|
||||
import_desc_rva = 0x1400
|
||||
|
||||
msg_offset = 5 + 6 + 3 + 2 + 6 + 1 # = 23
|
||||
msg_addr = image_base + code_rva + msg_offset # 0x401017
|
||||
printf_iat_abs = image_base + printf_iat_rva # 0x401200
|
||||
exitproc_iat_abs = image_base + exitproc_iat_rva # 0x401204
|
||||
|
||||
code = bytearray()
|
||||
code += b'\x68' + struct.pack('<I', msg_addr) # push msg_addr
|
||||
code += b'\xFF\x25' + struct.pack('<I', printf_iat_abs) # call [printf_iat]
|
||||
code += b'\x83\xC4\x04' # add esp, 4
|
||||
code += b'\x6A\x00' # push 0
|
||||
code += b'\xFF\x25' + struct.pack('<I', exitproc_iat_abs) # call [exitproc_iat]
|
||||
code += b'\xC3' # ret
|
||||
code += msg
|
||||
|
||||
# 对齐到 IAT (0x200)
|
||||
while len(code) < 0x200:
|
||||
code += b'\x00'
|
||||
# IAT (4-byte thunks,初始值 = hint/name RVA)
|
||||
code += struct.pack('<I', printf_name_rva)
|
||||
code += struct.pack('<I', exitproc_name_rva)
|
||||
|
||||
# 对齐到 hint/name 表 (0x300)
|
||||
while len(code) < printf_name_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += struct.pack('<H', 0) + b'printf\0' # hint=0
|
||||
while len(code) < exitproc_name_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += struct.pack('<H', 0) + b'ExitProcess\0'
|
||||
|
||||
# DLL 名
|
||||
while len(code) < msvcrt_dll_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += b'msvcrt.dll\0'
|
||||
while len(code) < kernel32_dll_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += b'kernel32.dll\0'
|
||||
|
||||
# Import descriptors
|
||||
while len(code) < import_desc_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += struct.pack('<IIIII',
|
||||
printf_iat_rva, # OriginalFirstThunk (INT)
|
||||
0, 0,
|
||||
msvcrt_dll_rva, # Name
|
||||
printf_iat_rva) # FirstThunk (IAT)
|
||||
code += struct.pack('<IIIII',
|
||||
exitproc_iat_rva, # OriginalFirstThunk
|
||||
0, 0,
|
||||
kernel32_dll_rva, # Name
|
||||
exitproc_iat_rva) # FirstThunk
|
||||
code += b'\x00' * 20 # NULL 终止
|
||||
|
||||
# 对齐到 file_align
|
||||
while len(code) % file_align != 0:
|
||||
code += b'\x00'
|
||||
|
||||
size_of_code = len(code)
|
||||
size_of_image = code_rva + size_of_code
|
||||
while size_of_image % section_align != 0:
|
||||
size_of_image += 1
|
||||
|
||||
# PE32 Optional Header (224 字节)
|
||||
opt_header = bytearray(224)
|
||||
struct.pack_into('<H', opt_header, 0, 0x10b) # Magic = PE32
|
||||
opt_header[2] = 0 # MajorLinkerVersion
|
||||
opt_header[3] = 0 # MinorLinkerVersion
|
||||
struct.pack_into('<I', opt_header, 4, size_of_code) # SizeOfCode
|
||||
struct.pack_into('<I', opt_header, 8, 0) # SizeOfInitializedData
|
||||
struct.pack_into('<I', opt_header, 12, 0) # SizeOfUninitializedData
|
||||
struct.pack_into('<I', opt_header, 16, main_rva) # AddressOfEntryPoint
|
||||
struct.pack_into('<I', opt_header, 20, code_rva) # BaseOfCode
|
||||
struct.pack_into('<I', opt_header, 24, 0) # BaseOfData (PE32 only)
|
||||
struct.pack_into('<I', opt_header, 28, image_base) # ImageBase (32-bit)
|
||||
struct.pack_into('<I', opt_header, 32, section_align)
|
||||
struct.pack_into('<I', opt_header, 36, file_align)
|
||||
struct.pack_into('<H', opt_header, 40, 6) # MajorOperatingSystemVersion
|
||||
struct.pack_into('<H', opt_header, 42, 0)
|
||||
struct.pack_into('<H', opt_header, 44, 0) # MajorImageVersion
|
||||
struct.pack_into('<H', opt_header, 46, 0)
|
||||
struct.pack_into('<H', opt_header, 48, 6) # MajorSubsystemVersion
|
||||
struct.pack_into('<H', opt_header, 50, 0)
|
||||
struct.pack_into('<I', opt_header, 52, 0) # Win32VersionValue
|
||||
struct.pack_into('<I', opt_header, 56, size_of_image)
|
||||
struct.pack_into('<I', opt_header, 60, 0x200) # SizeOfHeaders
|
||||
struct.pack_into('<I', opt_header, 64, 0) # CheckSum
|
||||
struct.pack_into('<H', opt_header, 68, IMAGE_SUBSYSTEM_WINDOWS_CUI) # Subsystem
|
||||
struct.pack_into('<H', opt_header, 70, 0) # DllCharacteristics
|
||||
struct.pack_into('<I', opt_header, 72, 0x100000) # SizeOfStackReserve (4字节)
|
||||
struct.pack_into('<I', opt_header, 76, 0x1000) # SizeOfStackCommit
|
||||
struct.pack_into('<I', opt_header, 80, 0x100000) # SizeOfHeapReserve
|
||||
struct.pack_into('<I', opt_header, 84, 0x1000) # SizeOfHeapCommit
|
||||
struct.pack_into('<I', opt_header, 88, 0) # LoaderFlags
|
||||
struct.pack_into('<I', opt_header, 92, 16) # NumberOfRvaAndSizes
|
||||
|
||||
# DataDirectory[1] = Import (RVA, Size)
|
||||
data_dir_offset = 96
|
||||
struct.pack_into('<II', opt_header, data_dir_offset + 8, import_desc_rva, 60)
|
||||
|
||||
# Section header (.text)
|
||||
section_header = bytearray(40)
|
||||
section_header[0:8] = b'.text\0\0\0'
|
||||
struct.pack_into('<I', section_header, 8, size_of_code) # VirtualSize
|
||||
struct.pack_into('<I', section_header, 12, code_rva) # VirtualAddress
|
||||
struct.pack_into('<I', section_header, 16, size_of_code) # SizeOfRawData
|
||||
struct.pack_into('<I', section_header, 20, file_align) # PointerToRawData
|
||||
struct.pack_into('<I', section_header, 24, 0)
|
||||
struct.pack_into('<I', section_header, 28, 0)
|
||||
struct.pack_into('<H', section_header, 32, 0)
|
||||
struct.pack_into('<H', section_header, 34, 0)
|
||||
struct.pack_into('<I', section_header, 36,
|
||||
IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ)
|
||||
|
||||
# 组装文件
|
||||
headers = bytearray()
|
||||
headers += dos_header
|
||||
headers += pe_sig
|
||||
headers += coff_header
|
||||
headers += opt_header
|
||||
headers += section_header
|
||||
while len(headers) % file_align != 0:
|
||||
headers += b'\x00'
|
||||
|
||||
return bytes(headers) + bytes(code)
|
||||
|
||||
|
||||
def build_cmd_exe():
|
||||
"""构造 cmd.exe — PE32+ (64位) 命令行测试程序。
|
||||
|
||||
与 build_pe32_plus() 类似,仅修改消息文本为 "Hello from cmd.exe!"
|
||||
"""
|
||||
image_base = 0x140000000
|
||||
section_align = 0x1000
|
||||
file_align = 0x200
|
||||
|
||||
dos_header = bytearray(64)
|
||||
struct.pack_into('<H', dos_header, 0, IMAGE_DOS_SIGNATURE)
|
||||
dos_header[2:60] = b'\x00' * 58
|
||||
struct.pack_into('<I', dos_header, 60, 64)
|
||||
|
||||
pe_sig = struct.pack('<I', IMAGE_NT_SIGNATURE)
|
||||
|
||||
num_sections = 1
|
||||
optional_header_size = 240
|
||||
characteristics = IMAGE_FILE_MACHINE_AMD64_CHARACTERISTICS
|
||||
coff_header = struct.pack('<HHIIIHH',
|
||||
IMAGE_FILE_MACHINE_AMD64,
|
||||
num_sections,
|
||||
0, 0, 0,
|
||||
optional_header_size,
|
||||
characteristics)
|
||||
|
||||
msg = b"Hello from cmd.exe!\r\n\0"
|
||||
code = bytearray()
|
||||
code += b'\x48\x8D\x0D' + b'\x00\x00\x00\x00' # lea rcx, [rip+0]
|
||||
code += b'\xFF\x15' + b'\x00\x00\x00\x00' # call [rip+0]
|
||||
code += b'\x31\xC9' # xor ecx, ecx
|
||||
code += b'\xFF\x15' + b'\x00\x00\x00\x00' # call [rip+0]
|
||||
code += b'\xC3' # ret
|
||||
code += msg
|
||||
|
||||
code_rva = 0x1000
|
||||
main_rva = code_rva
|
||||
printf_iat_rva = code_rva + 0x200
|
||||
exitproc_iat_rva = printf_iat_rva + 8
|
||||
|
||||
msg_offset_in_code = 22
|
||||
struct.pack_into('<i', code, 3, 15)
|
||||
struct.pack_into('<i', code, 10, 0x200 - 13)
|
||||
struct.pack_into('<i', code, 17, 0x208 - 21)
|
||||
|
||||
while len(code) < 0x200:
|
||||
code += b'\x00'
|
||||
code += struct.pack('<Q', 0x300)
|
||||
code += struct.pack('<Q', 0x320)
|
||||
|
||||
import_desc_rva = 0x400
|
||||
while len(code) < 0x300:
|
||||
code += b'\x00'
|
||||
printf_name_rva = 0x300
|
||||
exitproc_name_rva = 0x320
|
||||
printf_hint_name = struct.pack('<H', 0) + b'printf\0'
|
||||
exitproc_hint_name = struct.pack('<H', 0) + b'ExitProcess\0'
|
||||
while len(code) < printf_name_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += printf_hint_name
|
||||
while len(code) < exitproc_name_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += exitproc_hint_name
|
||||
|
||||
msvcrt_dll_rva = 0x350
|
||||
kernel32_dll_rva = 0x360
|
||||
while len(code) < msvcrt_dll_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += b'msvcrt.dll\0'
|
||||
while len(code) < kernel32_dll_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += b'kernel32.dll\0'
|
||||
|
||||
while len(code) < import_desc_rva - code_rva:
|
||||
code += b'\x00'
|
||||
code += struct.pack('<IIIII', printf_iat_rva, 0, 0, msvcrt_dll_rva, printf_iat_rva)
|
||||
code += struct.pack('<IIIII', exitproc_iat_rva, 0, 0, kernel32_dll_rva, exitproc_iat_rva)
|
||||
code += b'\x00' * 20
|
||||
|
||||
while len(code) % file_align != 0:
|
||||
code += b'\x00'
|
||||
|
||||
size_of_code = len(code)
|
||||
size_of_image = code_rva + size_of_code
|
||||
while size_of_image % section_align != 0:
|
||||
size_of_image += 1
|
||||
|
||||
opt_header = bytearray(240)
|
||||
struct.pack_into('<H', opt_header, 0, 0x20b)
|
||||
opt_header[2] = 0
|
||||
opt_header[3] = 0
|
||||
struct.pack_into('<I', opt_header, 4, size_of_code)
|
||||
struct.pack_into('<I', opt_header, 8, 0)
|
||||
struct.pack_into('<I', opt_header, 12, 0)
|
||||
struct.pack_into('<I', opt_header, 16, main_rva)
|
||||
struct.pack_into('<I', opt_header, 20, code_rva)
|
||||
struct.pack_into('<Q', opt_header, 24, image_base)
|
||||
struct.pack_into('<I', opt_header, 32, section_align)
|
||||
struct.pack_into('<I', opt_header, 36, file_align)
|
||||
struct.pack_into('<H', opt_header, 40, 6)
|
||||
struct.pack_into('<H', opt_header, 42, 0)
|
||||
struct.pack_into('<H', opt_header, 44, 0)
|
||||
struct.pack_into('<H', opt_header, 46, 0)
|
||||
struct.pack_into('<H', opt_header, 48, 6)
|
||||
struct.pack_into('<H', opt_header, 50, 0)
|
||||
struct.pack_into('<I', opt_header, 52, 0)
|
||||
struct.pack_into('<I', opt_header, 56, size_of_image)
|
||||
struct.pack_into('<I', opt_header, 60, 0x200)
|
||||
struct.pack_into('<I', opt_header, 64, 0)
|
||||
struct.pack_into('<H', opt_header, 68, IMAGE_SUBSYSTEM_WINDOWS_CUI)
|
||||
struct.pack_into('<H', opt_header, 70, 0)
|
||||
struct.pack_into('<Q', opt_header, 72, 0x100000)
|
||||
struct.pack_into('<Q', opt_header, 80, 0x1000)
|
||||
struct.pack_into('<Q', opt_header, 88, 0x100000)
|
||||
struct.pack_into('<Q', opt_header, 96, 0x1000)
|
||||
struct.pack_into('<I', opt_header, 104, 0)
|
||||
struct.pack_into('<I', opt_header, 108, 16)
|
||||
|
||||
data_dir_offset = 112
|
||||
struct.pack_into('<II', opt_header, data_dir_offset + 8, import_desc_rva, 48)
|
||||
|
||||
section_header = bytearray(40)
|
||||
section_header[0:8] = b'.text\0\0\0'
|
||||
struct.pack_into('<I', section_header, 8, size_of_code)
|
||||
struct.pack_into('<I', section_header, 12, code_rva)
|
||||
struct.pack_into('<I', section_header, 16, size_of_code)
|
||||
struct.pack_into('<I', section_header, 20, file_align)
|
||||
struct.pack_into('<I', section_header, 24, 0)
|
||||
struct.pack_into('<I', section_header, 28, 0)
|
||||
struct.pack_into('<H', section_header, 32, 0)
|
||||
struct.pack_into('<H', section_header, 34, 0)
|
||||
struct.pack_into('<I', section_header, 36,
|
||||
IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ)
|
||||
|
||||
headers = bytearray()
|
||||
headers += dos_header
|
||||
headers += pe_sig
|
||||
headers += coff_header
|
||||
headers += opt_header
|
||||
headers += section_header
|
||||
while len(headers) % file_align != 0:
|
||||
headers += b'\x00'
|
||||
|
||||
return bytes(headers) + bytes(code)
|
||||
|
||||
|
||||
def main():
|
||||
out_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
pe32_plus = build_pe32_plus()
|
||||
out_path = os.path.join(out_dir, 'hello64.exe')
|
||||
with open(out_path, 'wb') as f:
|
||||
f.write(pe32_plus)
|
||||
print(f"[gen] Generated PE32+: {out_path} ({len(pe32_plus)} bytes)")
|
||||
|
||||
pe32 = build_pe32()
|
||||
out_path = os.path.join(out_dir, 'hello32.exe')
|
||||
with open(out_path, 'wb') as f:
|
||||
f.write(pe32)
|
||||
print(f"[gen] Generated PE32: {out_path} ({len(pe32)} bytes)")
|
||||
|
||||
cmd_exe = build_cmd_exe()
|
||||
out_path = os.path.join(out_dir, 'cmd.exe')
|
||||
with open(out_path, 'wb') as f:
|
||||
f.write(cmd_exe)
|
||||
print(f"[gen] Generated cmd.exe: {out_path} ({len(cmd_exe)} bytes)")
|
||||
|
||||
print("[gen] 完成。复制到 SYSTEM/system/deshab64/tools/ 并重新运行 build.bat")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,27 @@
|
||||
/* hello32.c — PE32 (32-bit) 测试程序
|
||||
*
|
||||
* 用途: 验证 Deshab PE 兼容层 x86emu32 解释器对 PE32 的加载与执行
|
||||
*
|
||||
* 调用的 shim API (pe_shim.c 已实现):
|
||||
* - msvcrt.dll!printf
|
||||
* - kernel32.dll!ExitProcess
|
||||
*
|
||||
* 编译 (MinGW-w64 32位):
|
||||
* i686-w64-mingw32-gcc -o hello32.exe hello32.c -nostartfiles \
|
||||
* -Wl,--entry,_main -Wl,--image-base,0x00400000
|
||||
*
|
||||
* 编译 (MSVC 32位):
|
||||
* cl /Fe:hello32.exe hello32.c /link /ENTRY:_main /BASE:0x00400000
|
||||
*
|
||||
* 或直接使用 gen_pe.py 生成等价 PE32 二进制
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int __cdecl main(void) {
|
||||
printf("Hello from PE32!\r\n");
|
||||
printf("Deshab x86emu32 interpreter works.\r\n");
|
||||
ExitProcess(0);
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,27 @@
|
||||
/* hello64.c — PE32+ (64-bit) 测试程序
|
||||
*
|
||||
* 用途: 验证 Deshab PE 兼容层 pe_service 对 PE32+ 的加载与原生 Ring0 执行
|
||||
*
|
||||
* 调用的 shim API (pe_shim.c 已实现):
|
||||
* - msvcrt.dll!printf
|
||||
* - kernel32.dll!ExitProcess
|
||||
*
|
||||
* 编译 (MinGW-w64):
|
||||
* x86_64-w64-mingw32-gcc -o hello64.exe hello64.c -nostartfiles \
|
||||
* -Wl,--entry,main -Wl,--image-base,0x140000000
|
||||
*
|
||||
* 编译 (MSVC):
|
||||
* cl /Fe:hello64.exe hello64.c /link /ENTRY:main /BASE:0x140000000
|
||||
*
|
||||
* 或直接使用 gen_pe.py 生成等价 PE32+ 二进制
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
printf("Hello from PE32+!\r\n");
|
||||
printf("Deshab PE compat layer works.\r\n");
|
||||
ExitProcess(0);
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
+25
-4
@@ -1,5 +1,5 @@
|
||||
@echo off
|
||||
setlocal
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
set "SCRIPT_DIR=%~dp0"
|
||||
set "IMG=%SCRIPT_DIR%deshab.img"
|
||||
@@ -41,15 +41,35 @@ if not defined OVMF (
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM ===== Accelerator selection =====
|
||||
REM QEMU_ACCEL env var can override: "whpx" / "tcg"
|
||||
REM Default: try WHPX first (for VMX/Linux compat), fallback to TCG if unavailable
|
||||
if not defined QEMU_ACCEL set "QEMU_ACCEL=whpx"
|
||||
|
||||
REM Detect whether Linux compat layer is enabled (bzImage present in IMG)
|
||||
set "LINUX_ENABLED=0"
|
||||
findstr /C:"module_path: boot():/boot/linux-bzImage" "%SCRIPT_DIR%..\SYSTEM\limine\limine.conf" >nul 2>&1
|
||||
if not errorlevel 1 (
|
||||
set "LINUX_ENABLED=1"
|
||||
)
|
||||
|
||||
echo [qemu] Using: %QEMU%
|
||||
echo [qemu] UEFI: %OVMF%
|
||||
echo [qemu] Image: %IMG%
|
||||
echo [qemu] Accel: %QEMU_ACCEL%
|
||||
if "%LINUX_ENABLED%"=="1" (
|
||||
echo [qemu] Linux compat: ENABLED (requires WHPX or real hardware for VMX)
|
||||
echo [qemu] If VMX unavailable, UTSM will auto-skip Linux and continue to DSK
|
||||
) else (
|
||||
echo [qemu] Linux compat: disabled (build via CODE/linux/build.sh to enable)
|
||||
)
|
||||
if exist "%SATA_IMG%" (
|
||||
echo [qemu] SATA: %SATA_IMG%
|
||||
"%QEMU%" ^
|
||||
-accel %QEMU_ACCEL% ^
|
||||
-machine q35 ^
|
||||
-m 512M ^
|
||||
-cpu max ^
|
||||
-cpu qemu64 ^
|
||||
-serial stdio ^
|
||||
-drive if=pflash,format=raw,readonly=on,file="%OVMF%" ^
|
||||
-drive format=raw,file="%IMG%",if=virtio ^
|
||||
@@ -61,9 +81,10 @@ if exist "%SATA_IMG%" (
|
||||
) else (
|
||||
echo [qemu] SATA image not found, booting without block device
|
||||
"%QEMU%" ^
|
||||
-accel %QEMU_ACCEL% ^
|
||||
-machine q35 ^
|
||||
-m 512M ^
|
||||
-cpu max ^
|
||||
-cpu qemu64 ^
|
||||
-serial stdio ^
|
||||
-drive if=pflash,format=raw,readonly=on,file="%OVMF%" ^
|
||||
-drive format=raw,file="%IMG%",if=virtio ^
|
||||
|
||||
+39
-6
@@ -1,6 +1,39 @@
|
||||
开发者模式中启动的Shell的提示符没有在第一行也没有显示
|
||||
shell光标没有闪烁
|
||||
Login界面没有显示鼠标指针
|
||||
在桌面上时每按下按键屏幕就会闪烁
|
||||
窗口打开和关闭时的动画太卡,而且屏幕会闪,直接去掉动画
|
||||
启动一次之后,第二次开机启动会直接退出
|
||||
# Deshab Bug 记录
|
||||
|
||||
> 所有测试与开发过程中发现的 bug 统一登记于此。每个 bug 按 BUG-YYYYMMDD-NNN 编号。
|
||||
> 测试用例文档位于 RE/test-cases/。
|
||||
|
||||
## 严重程度定义
|
||||
|
||||
- P0 阻断: 影响主链路启动或核心功能完全不可用(必须立即修复)
|
||||
- P1 严重: 功能可降级但关键路径受损(本轮内修复)
|
||||
- P2 一般: 非关键路径异常或体验问题(下一里程碑前修复)
|
||||
- P3 提示: 日志/小问题,不影响功能(记录备查)
|
||||
|
||||
## Bug 条目格式
|
||||
|
||||
```
|
||||
### BUG-YYYYMMDD-NNN [P级] [模块] 简述
|
||||
- 现象:
|
||||
- 复现步骤:
|
||||
- 串口/日志证据:
|
||||
- 根因分析:
|
||||
- 修复方案:
|
||||
- 状态: [OPEN/FIXING/FIXED/CLOSED]
|
||||
- 修复提交: <commit hash 或 "待提交">
|
||||
- 验证: <验证方法与结果>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bug 列表
|
||||
|
||||
<!-- 在此追加 bug 条目,最新放在顶部 -->
|
||||
P2: 软件Files的窗口点击叉无法关闭
|
||||
P3: 在第一页已经有了终端,所以在第四页的桌面上不用显示终端和CMD
|
||||
P2: 在开发者模式中的shell输入命令并回车时文字消失没有反应
|
||||
P3: 启动时的打字机效果再快3倍
|
||||
P2: 桌面中第一页的shell中输入命令回车后,上一条命令输入的文字会跑到下一行
|
||||
P2: 桌面中第一页的Files显示的不是SYSTEM/user中的文件夹内容
|
||||
P2: Files中没有显示文件夹,直接显示了文件夹下的文件
|
||||
P2: Files没有提供“..”来供用户返回上一级的文件夹
|
||||
@@ -0,0 +1,38 @@
|
||||
# Deshab 测试用例矩阵
|
||||
|
||||
本目录组织 Deshab-OS 的系统化测试用例文档。
|
||||
|
||||
## 测试矩阵总览
|
||||
|
||||
| 文档 | 范围 | 优先级 |
|
||||
|---|---|---|
|
||||
| [boot-regression.md](boot-regression.md) | 启动链路回归(首启/正常/dev_mode/无盘/无NIC) | P0 |
|
||||
| [linux-compat.md](linux-compat.md) | UTSM+Linux 双内核兼容层端到端 | P0 |
|
||||
| [pe-compat.md](pe-compat.md) | PE/EXE 兼容层(PE32+ native + PE32 x86emu32) | P1 |
|
||||
| [driver-matrix.md](driver-matrix.md) | 14 DKM 驱动功能矩阵 | P1 |
|
||||
| [userapps-matrix.md](userapps-matrix.md) | 用户态程序矩阵(shell/cmd/desktop/tools) | P1 |
|
||||
| [network-e2e.md](network-e2e.md) | 网络端到端(DHCP/ARP/DNS/ping/curl) | P1 |
|
||||
| [performance-baseline.md](performance-baseline.md) | 性能基线(TSC 测量) | P2 |
|
||||
| [x86emu32-coverage.md](x86emu32-coverage.md) | x86emu32 指令覆盖率 | P2 |
|
||||
|
||||
## 测试场景状态标记
|
||||
|
||||
- ✅ PASS: 全部测试项通过
|
||||
- 🔶 PARTIAL: 部分测试项通过,存在已知限制
|
||||
- ❌ FAIL: 关键测试项失败,需修复
|
||||
- ⬜ PENDING: 未执行
|
||||
- ⏭ SKIP: 因环境/依赖不可用而跳过
|
||||
|
||||
## 自动化执行
|
||||
|
||||
```powershell
|
||||
# 运行全部自动化测试场景
|
||||
.\ISO\run_tests.bat
|
||||
|
||||
# 输出
|
||||
# ISO/logs/test-report-<timestamp>.md
|
||||
```
|
||||
|
||||
## Bug 跟踪
|
||||
|
||||
所有测试发现的 bug 登记到 [../Bug记录.txt](../Bug记录.txt),并在对应测试文档中引用 bug 编号。
|
||||
@@ -0,0 +1,96 @@
|
||||
# 启动链路回归测试矩阵
|
||||
|
||||
> 对应计划 Phase C2:验证不同启动场景下系统行为正确
|
||||
|
||||
## 状态: ⬜ PENDING
|
||||
|
||||
## 测试场景
|
||||
|
||||
| # | 场景 | firstInit.txt | 配置 | 期望串口锚点 | 状态 | Bug# |
|
||||
|---|---|---|---|---|---|---|
|
||||
| B1 | 首次启动 | `0\n0` | 空 user.conf | `[DSK] firstInit=0` → `mouseInit` → `FirstInit` → 完成向导 → 写盘 | ⬜ | |
|
||||
| B2 | 正常启动 | `1\n0` | 已有 user.conf | `[DSK] firstInit=1` → `login` → desktop | ⬜ | |
|
||||
| B3 | dev_mode | `1\n1` | dev_mode=1 | `[DSK] dev_mode` → shell run_dev_tests 全绿 → Esc → 正常流程 | ⬜ | |
|
||||
| B4 | 无 SATA 盘 | 任意 | 移除 sata_fat32_dsk.img | DSK fallback 到 Limine module | ⬜ | |
|
||||
| B5 | 无 NIC | 任意 | 移除 `-netdev` | e1000 not found → netman 警告 → 继续启动 | ⬜ | |
|
||||
| B6 | Linux 兼容层启用 | 任意 | +linux-bzImage +linux-initrd | UTSM VMM + Linux park → DSK 正常接管 | ⬜ | |
|
||||
|
||||
## 串口日志锚点(标准启动)
|
||||
|
||||
### 首次启动(B1)
|
||||
|
||||
```
|
||||
[UTSM] boot
|
||||
[UTSM] SELFTEST PASS
|
||||
[UTSM] loading DSK
|
||||
[DSK] boot
|
||||
[DSK] context ok
|
||||
[DSK] firstInit=0
|
||||
[DSK] loading mouseInit.elf
|
||||
[DSK] loading FirstInit.elf
|
||||
[FirstInit] ...
|
||||
[DSK] writing firstInit=1
|
||||
[DSK] loading shell.elf
|
||||
```
|
||||
|
||||
### 正常启动(B2)
|
||||
|
||||
```
|
||||
[UTSM] boot
|
||||
[UTSM] SELFTEST PASS
|
||||
[UTSM] loading DSK
|
||||
[DSK] boot
|
||||
[DSK] firstInit=1
|
||||
[DSK] loading login.elf
|
||||
[DSK] loading desktop.elf
|
||||
```
|
||||
|
||||
### dev_mode(B3)
|
||||
|
||||
```
|
||||
[UTSM] boot
|
||||
[UTSM] SELFTEST PASS
|
||||
[DSK] dev_mode=1
|
||||
[DSK] loading shell.elf
|
||||
[shell] run_dev_tests:
|
||||
[shell] ls: OK
|
||||
[shell] echo>: OK
|
||||
[shell] cat: OK
|
||||
[shell] cp: OK
|
||||
[shell] mv: OK
|
||||
[shell] rm: OK
|
||||
[shell] dev_tests done
|
||||
... (Esc 返回后正常流程)
|
||||
```
|
||||
|
||||
## 自动化脚本
|
||||
|
||||
[ISO/run_qemu_scenario.bat](../../ISO/run_qemu_scenario.bat) 接收场景名参数:
|
||||
|
||||
```powershell
|
||||
.\ISO\run_qemu_scenario.bat firstboot
|
||||
.\ISO\run_qemu_scenario.bat normal
|
||||
.\ISO\run_qemu_scenario.bat devmode
|
||||
.\ISO\run_qemu_scenario.bat nosata
|
||||
.\ISO\run_qemu_scenario.bat nonic
|
||||
.\ISO\run_qemu_scenario.bat linux
|
||||
```
|
||||
|
||||
每个场景输出独立日志到 `ISO/logs/<scenario>-<timestamp>.log`。
|
||||
|
||||
## 配置文件准备
|
||||
|
||||
### B1 首次启动
|
||||
|
||||
- `SYSTEM/system/user/use/firstInit.txt`: `0\n0`
|
||||
- 备份 `user.conf` 后清空内容(仅保留 `DESHAB_USERCONF_V1` 头)
|
||||
|
||||
### B3 dev_mode
|
||||
|
||||
- `firstInit.txt`: `1\n1`
|
||||
- 保持现有 user.conf
|
||||
|
||||
### B4 无 SATA
|
||||
|
||||
- 临时重命名 `.build_tmp/sata_fat32_dsk.img` → `.build_tmp/sata_fat32_dsk.img.bak`
|
||||
- run_qemu.bat 自动检测,缺失时跳过 `-device ide-hd`
|
||||
@@ -0,0 +1,70 @@
|
||||
# 14 DKM 驱动功能矩阵
|
||||
|
||||
> 对应计划 Phase C3:每个驱动的加载、探测、功能验证点
|
||||
|
||||
## 状态: ⬜ PENDING
|
||||
|
||||
## 驱动清单
|
||||
|
||||
| # | 驱动 | stage | class | required | QEMU 参数 | 期望串口日志 | 功能验证点 | 状态 | Bug# |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 1 | timer | 0 | timer | ✅ | 默认 | `[DKM:timer]` | 100ms busy-wait 精度 | ⬜ | |
|
||||
| 2 | apic | 0 | interrupt | ✅ | 默认 | `[DKM:apic] MADT found` | LAPIC MMIO 只读 | ⬜ | |
|
||||
| 3 | acpi | 0 | platform | ❌ | 默认 | `[DKM:acpi]` | RSDP/XSDT 枚举 | ⬜ | |
|
||||
| 4 | pci | 0 | bus | ✅ | 默认 | `[DKM:pci]` | PCI config space 扫描 | ⬜ | |
|
||||
| 5 | console_fb | 1 | console | ❌ | 默认 | `[DKM:console_fb]` | 淡蓝色背景清屏 | ⬜ | |
|
||||
| 6 | ahci | 1 | storage | ❌ | +SATA 测试盘 | `[DKM:ahci] ahci0 registered` | LBA0 读取 | ⬜ | |
|
||||
| 7 | nvme | 1 | storage | ❌ | `-device nvme` | `[DKM:nvme] 1b36:0010 found` | BAR0 读取(高位 MMIO 未解决则 SKIP) | ⏭ | |
|
||||
| 8 | bootfs | 1 | fs | ✅ | 默认 | `[DKM:bootfs]` | Limine boot module FS | ⬜ | |
|
||||
| 9 | vfs | 2 | fs-core | ✅ | 默认 | `[DKM:vfs]` | 挂载 bootfs | ⬜ | |
|
||||
| 10 | fat32 | 2 | fs | ❌ | +SATA 测试盘 | `[DKM:fat32]` | BPB/FAT/目录项解析 | ⬜ | |
|
||||
| 11 | devfs | 2 | fs | ❌ | 默认 | `[DKM:devfs]` | /dev/version, /dev/fb0 等 | ⬜ | |
|
||||
| 12 | e1000 | 3 | net | ❌ | `-device e1000` | `tx=ready rx=ready` | DHCP + ping | ⬜ | |
|
||||
| 13 | virtio_net | 3 | net | ❌ | `-device virtio-net-pci` | `[DKM:virtio_net] 1af4:1001 found` | capability 枚举 | ⬜ | |
|
||||
| 14 | ath9k | 3 | net | ❌ | (QEMU 不支持 WiFi) | `no Atheros WiFi device found` | 跳过(QEMU 限制) | ⏭ | |
|
||||
| 15 | ps2kbd | 3 | input | ❌ | 默认 | scan code 读取 | 输入回显 | ⬜ | |
|
||||
|
||||
## stage 加载顺序
|
||||
|
||||
```
|
||||
stage 0 (platform): timer → apic → acpi → pci
|
||||
stage 1 (boot): console_fb → ahci → nvme → bootfs
|
||||
stage 2 (fs): vfs → fat32 → devfs
|
||||
stage 3 (optional): e1000 → virtio_net → ath9k → ps2kbd
|
||||
```
|
||||
|
||||
## 测试设备 QEMU 参数
|
||||
|
||||
### AHCI + FAT32 测试盘
|
||||
|
||||
```
|
||||
-drive id=sata0,format=raw,file=.build_tmp/sata_fat32_dsk.img,if=none ^
|
||||
-device ide-hd,drive=sata0,bus=ide.0 ^
|
||||
```
|
||||
|
||||
### NVMe
|
||||
|
||||
```
|
||||
-device nvme,drive=nvme0,serial=UTSMTEST ^
|
||||
-drive id=nvme0,format=raw,file=.build_tmp/nvme_test.img,if=none ^
|
||||
```
|
||||
|
||||
### e1000
|
||||
|
||||
```
|
||||
-netdev user,id=net0 ^
|
||||
-device e1000,netdev=net0,mac=52:54:00:12:34:56 ^
|
||||
```
|
||||
|
||||
### virtio-net
|
||||
|
||||
```
|
||||
-netdev user,id=net0 ^
|
||||
-device virtio-net-pci,netdev=net0 ^
|
||||
```
|
||||
|
||||
## 已知限制
|
||||
|
||||
- NVMe BAR0 在 QEMU 中常位于 4G 以上,当前 HHDM 不能安全访问,需 Phase 0 高位 MMIO 映射解决
|
||||
- ath9k 在 QEMU 中无对应设备模拟,永远走 "not found" 路径
|
||||
- 多个 NIC 同时挂载会改变 PCI 拓扑,可能影响 e1000 探测位置
|
||||
@@ -0,0 +1,77 @@
|
||||
# Linux 兼容层测试矩阵
|
||||
|
||||
> 对应计划 Phase A:UTSM+Linux 双内核 park-and-resume 端到端验证
|
||||
> 前置条件:Linux 6.6 LTS bzImage 已构建(SYSTEM/boot/linux-bzImage + linux-initrd.img)
|
||||
|
||||
## 状态: ⬜ PENDING
|
||||
|
||||
## 测试环境
|
||||
|
||||
- QEMU 加速: WHPX (`-accel whpx -cpu max`),TCG 不支持 VMX
|
||||
- Linux 模块: limine.conf 中 bzImage/initrd 模块已启用
|
||||
- 串口: `-serial stdio`,过滤 `Linux version|utsm-linux|linux_compat|UTSM.*Linux|DSK|shell`
|
||||
|
||||
## 测试矩阵
|
||||
|
||||
| # | 测试项 | 串口/Shell 预期 | 状态 | 失败处理 | Bug# |
|
||||
|---|---|---|---|---|---|
|
||||
| A5.1 | UTSM VMM self-test 通过 | `[UTSM] VMM self-test PASS` | ⬜ | 检查 VMCS/EPT 初始化;BIOS 启用 VT-x | |
|
||||
| A5.2 | bzImage 加载与解析 | `[UTSM] Linux bzImage loaded` | ⬜ | 检查 linux_loader_init header magic 校验 | |
|
||||
| A5.3 | EPT 映射 + vmlaunch | `[UTSM] Linux guest launched` | ⬜ | 检查 EPT 表、VMCS guest 状态 | |
|
||||
| A5.4 | Linux 内核启动 | `Linux version 6.6.x` 串口日志 | ⬜ | 检查 cmdline、boot_params、E820 表 | |
|
||||
| A5.5 | init 脚本运行 | `[utsm-linux] init started` | ⬜ | 检查 initramfs 结构 | |
|
||||
| A5.6 | exec daemon 启动 + park | `[utsm-linux] starting utsm_exec_daemon...` → `linux_compat_init() ready` | ⬜ | 检查 daemon 编译、IPC 共享内存初始化 | |
|
||||
| A5.7 | DSK 正常接管 | `[DSK] boot` → `[DSK] SELFTEST PASS` | ⬜ | 验证 Linux park 不阻塞 DSK | |
|
||||
| A5.8 | shell `linux`(无参)状态查询 | `Linux 兼容层已就绪 (双内核 park-and-resume)` | ⬜ | 检查 reserved[5] 传递链 | |
|
||||
| A5.9 | shell `linux ls` | 输出 initramfs 根目录内容 | ⬜ | 检查 IPC exec 协议、vmresume 路径 | |
|
||||
| A5.10 | shell `linux uname -a` | 输出 `Linux ... 6.6.x ...` | ⬜ | 同上 | |
|
||||
| A5.11 | shell `linux cat /etc/hostname` | 输出 hostname | ⬜ | 同上 | |
|
||||
| A5.12 | 错误路径:`linux nosuchprog` | 输出 exec 失败,shell 不崩溃 | ⬜ | 检查 EXEC_EXIT 消息处理 | |
|
||||
|
||||
## 关键约束验证
|
||||
|
||||
| 约束 | 验证方法 | 状态 |
|
||||
|---|---|---|
|
||||
| Linux guest cmdline 含 `nohlt idle=poll` | 检查 [linux_loader.c](../../CODE/UTSM/vmm/linux_loader.c) cmdline 构造 | ⬜ |
|
||||
| IPC 共享内存 GPA=0x04000000, 1MB | e820 中标记 RESERVED,Linux 不分配 | ⬜ |
|
||||
| Linux guest RAM 起始 0x05000000 | 检查 EPT 映射范围 | ⬜ |
|
||||
| exec daemon 先发 EXEC_READY 再 HLT | daemon 源码 [utsm_exec_daemon.c](../../CODE/linux/initramfs/bin/utsm_exec_daemon.c) | ⬜ |
|
||||
| UTSM hypercall ABI: RAX=magic\|op | 检查 [hypercall.c](../../CODE/UTSM/vmm/hypercall.c) 处理 | ⬜ |
|
||||
|
||||
## 已知限制
|
||||
|
||||
- QEMU TCG 不支持 VMX,必须 WHPX 或真机
|
||||
- WHPX 可能不暴露 nested VMX 给 guest(需实测)
|
||||
- 若 CPU 不支持 VT-x,整个 Linux 兼容层降级为"不可用",不影响其他系统功能
|
||||
|
||||
## 手动测试命令
|
||||
|
||||
进入 shell 后执行:
|
||||
|
||||
```
|
||||
deshab:/ $ linux
|
||||
deshab:/ $ linux ls
|
||||
deshab:/ $ linux uname -a
|
||||
deshab:/ $ linux cat /etc/hostname
|
||||
deshab:/ $ linux nosuchprog
|
||||
```
|
||||
|
||||
## 串口日志锚点
|
||||
|
||||
启动后期望按顺序出现:
|
||||
|
||||
```
|
||||
[UTSM] VMM self-test PASS
|
||||
[UTSM] Linux bzImage loaded
|
||||
[UTSM] Linux guest launched
|
||||
Linux version 6.6.x ...
|
||||
[utsm-linux] init started
|
||||
[utsm-linux] kernel: 6.6.x
|
||||
[utsm-linux] starting utsm_exec_daemon...
|
||||
[utsm-linux] daemon PID=...
|
||||
[utsm-linux] init: daemon running, entering idle hold
|
||||
[UTSM] linux_compat service ready
|
||||
[DSK] boot
|
||||
[DSK] SELFTEST PASS
|
||||
... (进入 shell 提示符)
|
||||
```
|
||||
@@ -0,0 +1,62 @@
|
||||
# 网络端到端测试矩阵
|
||||
|
||||
> 对应计划 Phase C4 网络部分:e1000 + net_stack + ping/curl/browser
|
||||
|
||||
## 状态: ✅ 已验证([deshab_system_roadmap_plan.md](../../.trae/documents/deshab_system_roadmap_plan.md) Phase 1)
|
||||
|
||||
> 本矩阵用于回归验证,确保不退化
|
||||
|
||||
## 测试环境
|
||||
|
||||
- QEMU 网络: `-netdev user,id=net0 -device e1000,netdev=net0`
|
||||
- slirp 默认: guest 10.0.2.15, 网关 10.0.2.2, DNS 10.0.2.3
|
||||
- 主机 http 服务: `python -m http.server 8000`
|
||||
|
||||
## 测试矩阵
|
||||
|
||||
| # | 测试项 | 命令 | 预期 | 状态 | Bug# |
|
||||
|---|---|---|---|---|---|
|
||||
| N1 | e1000 驱动加载 | (自动) | `tx=ready rx=ready` | ✅ | |
|
||||
| N2 | netman 注册 | (自动) | netman 打印设备枚举 | ✅ | |
|
||||
| N3 | DHCP 获取 IP | (自动) | netman DHCP final rc=0 | ✅ | |
|
||||
| N4 | ARP 解析网关 | (自动) | netman ARP final rc=0 | ✅ | |
|
||||
| N5 | DNS 查询 | (自动) | netman DNS final rc=0 | ✅ | |
|
||||
| N6 | shell ping 网关 | `ping 10.0.2.2` | 4/4 reply, RTT<10ms | ✅ | |
|
||||
| N7 | shell ping 外网 | `ping 8.8.8.8` | (依赖主机联网) | 🔶 | |
|
||||
| N8 | shell curl 本地 | `curl http://10.0.2.2:8000/` | HTTP 200 + 目录 HTML | ✅ | |
|
||||
| N9 | shell curl 外网 | `curl http://example.com/` | HTTP 200 (依赖主机联网) | 🔶 | |
|
||||
| N10 | curl.elf 工具 | `run CURL.ELF` | 独立工具执行 curl | ✅ | |
|
||||
| N11 | ping.elf 工具 | `run PING.ELF` | 独立工具执行 ping | ✅ | |
|
||||
| N12 | browser.elf 工具 | `run BROWSER.ELF` | URL 输入 + HTTP GET | ✅ | |
|
||||
|
||||
## 回归验证
|
||||
|
||||
每次启动 dev_mode 时,run_dev_tests 应包含:
|
||||
|
||||
```
|
||||
[shell] dev_tests:
|
||||
...
|
||||
ping 10.0.2.2: OK (4/4 reply, RTT=Xms)
|
||||
...
|
||||
```
|
||||
|
||||
若 ping 失败:
|
||||
|
||||
1. 检查 e1000 是否加载(`-device e1000` 参数)
|
||||
2. 检查 netman 是否成功 DHCP
|
||||
3. 检查 ARP 是否解析网关
|
||||
4. 串口日志 grep `e1000|netman|DHCP|ARP|DNS|network`
|
||||
|
||||
## 性能基线
|
||||
|
||||
参见 [performance-baseline.md](performance-baseline.md):
|
||||
|
||||
- ping RTT < 10ms
|
||||
- curl 64KB 页面接收无明显停顿
|
||||
- DHCP 总耗时 < 5s
|
||||
|
||||
## 已知限制
|
||||
|
||||
- HTTPS/TLS 不支持(curl 仅 http)
|
||||
- TCP 仅最小 client 实现(无拥塞控制/乱序队列)
|
||||
- QEMU slirp 不支持真实 Wi-Fi 模式,ath9k 驱动在 QEMU 中无设备
|
||||
@@ -0,0 +1,72 @@
|
||||
# PE/EXE 兼容层测试矩阵
|
||||
|
||||
> 对应计划 Phase B:PE32+ (64位 native) 与 PE32 (32位 x86emu32) 兼容层验证
|
||||
> 入口:cmd.elf 通过 `ctx->reserved[4]` 取 pe_service
|
||||
|
||||
## 状态: ⬜ PENDING
|
||||
|
||||
## 测试程序
|
||||
|
||||
| 文件 | 类型 | 来源 | FAT32 8.3 名 | 路径 |
|
||||
|---|---|---|---|---|
|
||||
| hello64.exe | PE32+ (64位) | MinGW/MSVC 编译或 Python 生成 | `HELLO64 EXE` | SYSTEM/system/deshab64/tools/ |
|
||||
| hello32.exe | PE32 (32位) | MinGW 交叉编译或 Python 生成 | `HELLO32 EXE` | SYSTEM/system/deshab64/tools/ |
|
||||
|
||||
## 测试矩阵
|
||||
|
||||
| # | 测试项 | 命令 | 预期输出 | 状态 | Bug# |
|
||||
|---|---|---|---|---|---|
|
||||
| B2.1 | PE32+ 加载执行 | `pe HELLO64.EXE` | `Hello from PE32+!` | ⬜ | |
|
||||
| B2.2 | PE32 加载执行(解释器) | `pe HELLO32.EXE` | `Hello from PE32!` | ⬜ | |
|
||||
| B2.3 | PE 头解析 | `peinfo HELLO64.EXE` | `machine=0x8664 entry=0x... image_base=0x...` | ⬜ | |
|
||||
| B2.4 | 文件不存在错误路径 | `pe NONEXIST.EXE` | `Error: file not found` | ⬜ | |
|
||||
| B2.5 | 无参用法提示 | `pe` | 显示用法 | ⬜ | |
|
||||
| B2.6 | ExitProcess 返回 | (执行后) | cmd 接管,longjmp 跳回 pe_service_run | ⬜ | |
|
||||
| B2.7 | PE32 头解析 | `peinfo HELLO32.EXE` | `machine=0x14c entry=0x... image_base=0x00400000` | ⬜ | |
|
||||
|
||||
## 关键 ABI 验证
|
||||
|
||||
| ABI | 验证方法 | 状态 |
|
||||
|---|---|---|
|
||||
| `ctx->reserved[4]` 持 pe_service* | [cmd/main.c:825](../../CODE/cmd/main.c#L825) | ⬜ |
|
||||
| `PE_SERVICE_MAGIC` 校验 | [cmd/main.c:830](../../CODE/cmd/main.c#L830) | ⬜ |
|
||||
| `g_pe_svc` 缓存 | [cmd/main.c:108](../../CODE/cmd/main.c#L108) | ⬜ |
|
||||
| PE32+ IAT 填 ms_abi shim | [pe_loader.c](../../CODE/UTSM/pe/pe_loader.c) | ⬜ |
|
||||
| PE32 IAT 填合成地址 0x00010000\|idx | [pe_loader.c](../../CODE/UTSM/pe/pe_loader.c) | ⬜ |
|
||||
| x86emu32 拦截 CALL 合成地址 | [x86emu32.c](../../CODE/UTSM/pe/x86emu32.c) | ⬜ |
|
||||
|
||||
## 测试程序源码
|
||||
|
||||
### hello64.c (PE32+)
|
||||
|
||||
```c
|
||||
#include <windows.h>
|
||||
int main(void) {
|
||||
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "Hello from PE32+!\r\n", 19, NULL, NULL);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
编译:`x86_64-w64-mingw32-gcc -o hello64.exe hello64.c` 或 `cl /Fe:hello64.exe hello64.c`
|
||||
|
||||
### hello32.c (PE32)
|
||||
|
||||
```c
|
||||
#include <windows.h>
|
||||
int main(void) {
|
||||
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "Hello from PE32!\r\n", 18, NULL, NULL);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
编译:`i686-w64-mingw32-gcc -o hello32.exe hello32.c -Wl,--image-base=0x00400000`
|
||||
|
||||
### Fallback: Python 生成器
|
||||
|
||||
若无 MinGW/MSVC,使用 [CODE/tools/pe-samples/gen_pe.py](../../CODE/tools/pe-samples/gen_pe.py) 生成最小 PE 程序。
|
||||
|
||||
## 已知限制
|
||||
|
||||
- pe_shim 实现的 Win32 API 子集有限,测试程序仅使用 WriteFile + ExitProcess
|
||||
- x86emu32 指令覆盖率参见 [x86emu32-coverage.md](x86emu32-coverage.md)
|
||||
- TLS / IFUNC / lazy binding 不支持(DKM ABI 约束)
|
||||
@@ -0,0 +1,88 @@
|
||||
# 性能基线测量
|
||||
|
||||
> 对应计划 Phase C5:基于 TSC 的关键性能指标测量
|
||||
|
||||
## 状态: ⬜ PENDING
|
||||
|
||||
## 测量方法
|
||||
|
||||
所有指标基于 TSC(Time Stamp Counter):
|
||||
|
||||
```c
|
||||
static inline u64 rdtsc(void) {
|
||||
u32 lo, hi;
|
||||
__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
|
||||
return ((u64)hi << 32) | lo;
|
||||
}
|
||||
```
|
||||
|
||||
TSC 频率需先校准(PIT 100ms busy-wait 测量):
|
||||
|
||||
```c
|
||||
u64 tsc_freq; // Hz
|
||||
u64 calibrate_tsc(void) {
|
||||
u64 start = rdtsc();
|
||||
pit_busy_wait(100); // 100ms
|
||||
u64 end = rdtsc();
|
||||
return (end - start) * 10; // ×10 得到 Hz
|
||||
}
|
||||
```
|
||||
|
||||
时间换算:`ms = (tsc_diff * 1000) / tsc_freq`
|
||||
|
||||
## 基线指标
|
||||
|
||||
| # | 指标 | 测量位置 | 基线目标 | 实测 | 状态 |
|
||||
|---|---|---|---|---|---|
|
||||
| P1 | UTSM 启动到 DSK SELFTEST | UTSM entry → DSK selftest pass | < 500ms (QEMU) | | ⬜ |
|
||||
| P2 | DSK 加载 shell.elf 时间 | FAT32 read + PIE load 包围 | < 200ms | | ⬜ |
|
||||
| P3 | DSK 加载 desktop.elf 时间 | 同上 | < 200ms | | ⬜ |
|
||||
| P4 | shell `ping 10.0.2.2` RTT | echo request → reply | < 10ms (user-net) | | ⬜ |
|
||||
| P5 | shell `linux ls` 完整 RTT | IPC exec round-trip | < 100ms (park-and-resume) | | ⬜ |
|
||||
| P6 | shell `run PING.ELF` 加载时间 | FAT32 read + PIE load | < 150ms | | ⬜ |
|
||||
| P7 | desktop 窗口拖动帧率 | 帧计数/TSC | 30+ FPS | | ⬜ |
|
||||
| P8 | `pe HELLO64.EXE` 加载+执行 | pe_service_run 包围 | < 50ms | | ⬜ |
|
||||
| P9 | `pe HELLO32.EXE` 加载+执行 | x86emu32 解释 | < 200ms | | ⬜ |
|
||||
| P10 | UTSM VMM self-test | vmm_init → vmm_self_test | < 50ms | | ⬜ |
|
||||
| P11 | Linux guest 启动到 park | vmlaunch → daemon HLT | < 3s (QEMU WHPX) | | ⬜ |
|
||||
| P12 | e1000 DHCP 完整耗时 | netman DHCP start → final rc | < 5s | | ⬜ |
|
||||
|
||||
## 自动化测量
|
||||
|
||||
在 dev_mode run_dev_tests 中追加性能测量代码:
|
||||
|
||||
```c
|
||||
/* 在 run_dev_tests 开头校准 TSC */
|
||||
u64 tsc_freq = calibrate_tsc();
|
||||
logl("[shell] TSC freq: <Hz>");
|
||||
|
||||
/* 各项测试包围 TSC */
|
||||
u64 t0 = rdtsc();
|
||||
/* ... 操作 ... */
|
||||
u64 t1 = rdtsc();
|
||||
u64 ms = (t1 - t0) * 1000 / tsc_freq;
|
||||
logl("[shell] perf: <name> = <ms>ms");
|
||||
```
|
||||
|
||||
输出到串口日志,由 [ISO/run_tests.bat](../../ISO/run_tests.bat) 收集汇总。
|
||||
|
||||
## 性能回归检测
|
||||
|
||||
```powershell
|
||||
# 比较两次测试结果
|
||||
$prev = Get-Content ISO/logs/perf-baseline-prev.json | ConvertFrom-Json
|
||||
$curr = Get-Content ISO/logs/perf-baseline-curr.json | ConvertFrom-Json
|
||||
|
||||
foreach ($m in $curr.metrics) {
|
||||
$p = $prev.metrics | Where-Object name -eq $m.name
|
||||
if ($p -and $m.ms -gt $p.ms * 1.5) {
|
||||
Write-Host "[REGRESSION] $($m.name): $($p.ms)ms -> $($m.ms)ms (>50%)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 已知限制
|
||||
|
||||
- QEMU 性能数据不等于真机,仅作回归参考
|
||||
- WHPX vs TCG 性能差异巨大,必须固定加速方案
|
||||
- 多次测量取中位数,避免单次抖动
|
||||
@@ -0,0 +1,76 @@
|
||||
# 用户态程序功能矩阵
|
||||
|
||||
> 对应计划 Phase C4:FirstInit/login/desktop/shell/cmd + 5 个工具应用
|
||||
|
||||
## 状态: ⬜ PENDING
|
||||
|
||||
## 程序矩阵
|
||||
|
||||
| # | 程序 | ELF 路径 | 入口 | 关键命令/功能 | 验证点 | 状态 | Bug# |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 1 | FirstInit | `system/user/use/FirstInit.elf` | 首启触发 | 计算机名/用户名/密码输入、空字段红字、网络配置 | SHA256 密码、user.conf 写入缓冲 | ⬜ | |
|
||||
| 2 | login | `system/user/use/login.elf` | 次启触发 | 用户选择、密码输入、错误重试 | USER.CONF 解密、密码匹配 | ⬜ | |
|
||||
| 3 | desktop | `system/deshab64/desktop.elf` | login 后 | 双击图标、窗口拖动、任务栏、内建 bash | 6 个工具启动、鼠标光标 | ⬜ | |
|
||||
| 4 | shell | `system/deshab64/shell.elf` | desktop Terminal | ls/cat/cp/mv/rm/echo>/ping/curl/run/linux | dev_mode 自动测试全绿 | ⬜ | |
|
||||
| 5 | cmd | `system/deshab64/cmd.elf` | desktop CMD | dir/type/copy/del + `pe <NAME.EXE>` | PE 兼容层调用 | ⬜ | |
|
||||
| 6 | netman | `system/deshab64/network/netman.elf` | DSK 调度 | 读 conf.conf、应用网络配置 | DHCP/静态 IP | ⬜ | |
|
||||
| 7 | mouseInit | `system/deshab64/mouse/mouseInit.elf` | DSK 调度 | 安全 stub | 不阻塞启动 | ⬜ | |
|
||||
| 8 | editor | `system/deshab64/tools/editor.elf` | desktop 双击 | 文件打开/编辑/保存 | FAT32 写盘 | ⬜ | |
|
||||
| 9 | fileman | `system/deshab64/tools/fileman.elf` | desktop 双击 | 浏览/打开/复制 | 目录遍历 | ⬜ | |
|
||||
| 10 | browser | `system/deshab64/tools/browser.elf` | desktop 双击 | URL 输入/HTTP GET | net_stack 协议栈 | ⬜ | |
|
||||
| 11 | curl | `system/deshab64/tools/curl.elf` | desktop 双击 | `curl http://...` | HTTP GET | ⬜ | |
|
||||
| 12 | ping | `system/deshab64/tools/ping.elf` | desktop 双击 | `ping <ip>` | ICMP echo | ⬜ | |
|
||||
|
||||
## 启动链路
|
||||
|
||||
```
|
||||
首启: utsm → DSK → mouseInit → netman → FirstInit → desktop
|
||||
次启: utsm → DSK → login → desktop
|
||||
dev_mode: utsm → DSK → shell(dev_tests) → Esc → login → desktop
|
||||
desktop → 双击 Terminal → shell.elf
|
||||
desktop → 双击 CMD → cmd.elf
|
||||
shell → run FILEMAN.ELF → fileman.elf → Esc 返回 shell
|
||||
shell → run BROWSER.ELF → browser.elf
|
||||
shell → run EDITOR.ELF → editor.elf
|
||||
shell → run PING.ELF → ping.elf
|
||||
shell → run CURL.ELF → curl.elf
|
||||
```
|
||||
|
||||
## 详细测试用例
|
||||
|
||||
### shell 命令测试
|
||||
|
||||
| 命令 | 参数 | 预期 | 状态 |
|
||||
|---|---|---|---|
|
||||
| `ls` | (无) | 列出根目录文件(DESHAB.ELF, SHELL.ELF 等) | ⬜ |
|
||||
| `pwd` | (无) | `/` | ⬜ |
|
||||
| `cat` | `README.TXT` | 显示文件内容 | ⬜ |
|
||||
| `cp` | `a b` | 复制成功 | ⬜ |
|
||||
| `mv` | `a b` | 移动成功 | ⬜ |
|
||||
| `rm` | `a` | 删除成功 | ⬜ |
|
||||
| `echo>` | `text > file` | 写入文件 | ⬜ |
|
||||
| `ping` | `10.0.2.2` | 4/4 reply | ⬜ |
|
||||
| `curl` | `http://10.0.2.2:8000/` | HTTP 200 + body | ⬜ |
|
||||
| `run` | `PING.ELF` | 加载并执行 ping | ⬜ |
|
||||
| `linux` | (无) | 显示兼容层状态 | ⬜ |
|
||||
| `linux` | `ls` | 输出 initramfs 内容 | ⬜ |
|
||||
| `help` | (无) | 显示命令列表 | ⬜ |
|
||||
| `ver`/`version` | (无) | 显示版本 | ⬜ |
|
||||
|
||||
### desktop UI 测试
|
||||
|
||||
| 操作 | 预期 | 状态 |
|
||||
|---|---|---|
|
||||
| 双击 Terminal 图标 | 打开 shell 窗口 | ⬜ |
|
||||
| 双击 CMD 图标 | 打开 cmd 窗口 | ⬜ |
|
||||
| 双击 Editor 图标 | 打开 editor | ⬜ |
|
||||
| 双击 File Manager 图标 | 打开 fileman | ⬜ |
|
||||
| 双击 Browser 图标 | 打开 browser | ⬜ |
|
||||
| 鼠标移动 | 光标跟随,无闪烁 | ⬜ |
|
||||
| 窗口拖动 | 流畅,无撕裂 | ⬜ |
|
||||
| 任务栏点击 | 窗口切换 | ⬜ |
|
||||
|
||||
## 已知问题
|
||||
|
||||
- fileman.elf 之前触发过 QEMU 退出(访问未支持 boot context 字段),需观察是否复现
|
||||
- 真实 TTF 字体未运行时光栅化,使用预渲染位图(已知设计选择,非 bug)
|
||||
@@ -0,0 +1,86 @@
|
||||
# x86emu32 指令覆盖率测试
|
||||
|
||||
> 对应计划 Phase B3:枚举 x86emu32.c 已实现指令,验证覆盖率
|
||||
|
||||
## 状态: ⬜ PENDING
|
||||
|
||||
## 已实现指令清单
|
||||
|
||||
> 待根据 [x86emu32.c](../../CODE/UTSM/pe/x86emu32.c) 实际代码枚举填充
|
||||
|
||||
### 数据传送
|
||||
|
||||
| 指令 | 操作码 | 测试程序 | 预期 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| MOV r32, imm32 | B8+rd | `mov eax, 0x12345678` | eax=0x12345678 | ⬜ |
|
||||
| MOV r32, r/m32 | 8B /r | `mov eax, ebx` | eax=ebx | ⬜ |
|
||||
| MOV r/m32, r32 | 89 /r | `mov [esi], eax` | [esi]=eax | ⬜ |
|
||||
| PUSH r32 | 50+rd | `push eax` | [esp]=eax, esp-=4 | ⬜ |
|
||||
| POP r32 | 58+rd | `pop eax` | eax=[esp], esp+=4 | ⬜ |
|
||||
|
||||
### 算术运算
|
||||
|
||||
| 指令 | 操作码 | 测试程序 | 预期 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| ADD r32, r/m32 | 03 /r | `add eax, ebx` | eax=eax+ebx | ⬜ |
|
||||
| SUB r32, r/m32 | 2B /r | `sub eax, ebx` | eax=eax-ebx | ⬜ |
|
||||
| INC r32 | FF /0 | `inc eax` | eax=eax+1 | ⬜ |
|
||||
| DEC r32 | FF /1 | `dec eax` | eax=eax-1 | ⬜ |
|
||||
| MUL r/m32 | F7 /4 | `mul ebx` | edx:eax=eax*ebx | ⬜ |
|
||||
| DIV r/m32 | F7 /6 | `div ebx` | eax=edx:eax/ebx | ⬜ |
|
||||
|
||||
### 逻辑运算
|
||||
|
||||
| 指令 | 操作码 | 测试程序 | 预期 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| AND r32, r/m32 | 23 /r | `and eax, ebx` | eax=eax&ebx | ⬜ |
|
||||
| OR r32, r/m32 | 0B /r | `or eax, ebx` | eax=eax\|ebx | ⬜ |
|
||||
| XOR r32, r/m32 | 33 /r | `xor eax, ebx` | eax=eax^ebx | ⬜ |
|
||||
| NOT r/m32 | F7 /2 | `not eax` | eax=~eax | ⬜ |
|
||||
|
||||
### 控制流
|
||||
|
||||
| 指令 | 操作码 | 测试程序 | 预期 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| JMP rel32 | E9 cw/cd | `jmp label` | rip=label | ⬜ |
|
||||
| JMP r/m32 | FF /4 | `jmp eax` | rip=eax | ⬜ |
|
||||
| CALL rel32 | E8 cd | `call func` | push rip, rip=func | ⬜ |
|
||||
| RET | C3 | `ret` | pop rip | ⬜ |
|
||||
| Jcc rel32 | 0F 8x cd | `jz label` | 条件跳转 | ⬜ |
|
||||
|
||||
### 字符串操作
|
||||
|
||||
| 指令 | 操作码 | 测试程序 | 预期 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| MOVSB | A4 | `movsb` | [edi]=[esi], inc/dec | ⬜ |
|
||||
| STOSB | AA | `stosb` | [edi]=al, inc/dec | ⬜ |
|
||||
| REP MOVSB | F3 A4 | `rep movsb` | 重复 ecx 次 | ⬜ |
|
||||
| REP STOSB | F3 AA | `rep stosb` | 重复 ecx 次 | ⬜ |
|
||||
|
||||
### 堆栈与控制
|
||||
|
||||
| 指令 | 操作码 | 测试程序 | 预期 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| ENTER imm16, imm8 | C8 iw ib | `enter 8, 0` | 建立栈帧 | ⬜ |
|
||||
| LEAVE | C9 | `leave` | 释放栈帧 | ⬜ |
|
||||
| NOP | 90 | `nop` | 无操作 | ⬜ |
|
||||
| INT3 | CC | `int3` | 触发断点 | ⬜ |
|
||||
|
||||
## 测试 PE 程序
|
||||
|
||||
每个指令类别构造一个最小 PE32 测试程序,在 [CODE/tools/pe-samples/](../../CODE/tools/pe-samples/) 下:
|
||||
|
||||
- `test_arith.exe`:算术运算
|
||||
- `test_logic.exe`:逻辑运算
|
||||
- `test_control.exe`:控制流
|
||||
- `test_string.exe`:字符串操作
|
||||
|
||||
每个测试程序通过 WriteFile 输出结果到 cmd.elf,与预期对比。
|
||||
|
||||
## 覆盖率统计
|
||||
|
||||
```
|
||||
已测试指令数 / 已实现指令数 = 覆盖率
|
||||
```
|
||||
|
||||
目标:核心指令(MOV/ADD/SUB/CMP/JMP/CALL/RET/PUSH/POP)覆盖率 100%。
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -57,6 +57,8 @@ verbose: yes
|
||||
# 8. UTSM loads DSK (deshab.elf) → shell.elf → `linux <prog>` command
|
||||
# invokes linux_compat_service->exec() → vmresume → daemon runs prog
|
||||
#
|
||||
# Linux 兼容层模块(需先通过 WSL 运行 CODE/linux/build.sh 构建产物)
|
||||
# build.sh 成功后会自动取消下面 4 行注释;若未构建 Linux 内核请保持注释状态
|
||||
# module_path: boot():/boot/linux-bzImage
|
||||
# module_cmdline: linux:bzImage
|
||||
# module_path: boot():/boot/linux-initrd.img
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user