From 2774c10d0c50bd7be8ab868de29a5dcc105334e4 Mon Sep 17 00:00:00 2001 From: xyou Date: Tue, 28 Jul 2026 21:11:10 +0800 Subject: [PATCH] FUCKYOU --- CODE/UTSM/arch/x86_64/serial.c | 15 + CODE/UTSM/arch/x86_64/serial.o | Bin 2400 -> 2616 bytes CODE/UTSM/include/utsm/deshab_ui.h | 32 +- CODE/UTSM/include/utsm/linux_loader.h | 30 +- CODE/UTSM/include/utsm/virtio_mmio.h | 156 ++++++ CODE/UTSM/include/utsm/vmx.h | 20 + CODE/UTSM/vmm/linux_boot.c | 18 +- CODE/UTSM/vmm/linux_loader.c | 71 ++- CODE/UTSM/vmm/virtio_blk.c | 254 ++++++++++ CODE/UTSM/vmm/virtio_mmio.c | 294 ++++++++++++ CODE/UTSM/vmm/virtio_net.c | 218 +++++++++ CODE/UTSM/vmm/vmexit.c | 653 +++++++++++++++++++++++++- CODE/cmd/main.c | 441 ++++++++++++++++- CODE/desktop/main.c | 159 ++++++- CODE/firstInit/main.c | 2 +- CODE/firstInit/main.o | Bin 167024 -> 167024 bytes CODE/linux/build.sh | 83 +++- CODE/linux/check_env.sh | 117 +++++ CODE/linux/fix_driver.sh | 16 + CODE/linux/initramfs/init | 17 +- CODE/shell/main.c | 6 + CODE/shell/main.o | Bin 108536 -> 109168 bytes CODE/tools/fileman/main.c | 86 +++- CODE/tools/pe-samples/cmd.exe | Bin 0 -> 1536 bytes CODE/tools/pe-samples/gen_pe.py | 630 +++++++++++++++++++++++++ CODE/tools/pe-samples/hello32.c | 27 ++ CODE/tools/pe-samples/hello32.exe | Bin 0 -> 2048 bytes CODE/tools/pe-samples/hello64.c | 27 ++ CODE/tools/pe-samples/hello64.exe | Bin 0 -> 1536 bytes ISO/run_qemu.bat | 29 +- RE/Bug记录.txt | 45 +- RE/test-cases/README.md | 38 ++ RE/test-cases/boot-regression.md | 96 ++++ RE/test-cases/driver-matrix.md | 70 +++ RE/test-cases/linux-compat.md | 77 +++ RE/test-cases/network-e2e.md | 62 +++ RE/test-cases/pe-compat.md | 72 +++ RE/test-cases/performance-baseline.md | 88 ++++ RE/test-cases/userapps-matrix.md | 76 +++ RE/test-cases/x86emu32-coverage.md | 86 ++++ SYSTEM/bin/cmd.exe | Bin 0 -> 1536 bytes SYSTEM/bin/hello32.exe | Bin 0 -> 2048 bytes SYSTEM/bin/hello64.exe | Bin 0 -> 1536 bytes SYSTEM/boot/utsm.elf | Bin 128216 -> 143136 bytes SYSTEM/limine/limine.conf | 2 + SYSTEM/system/deshab64/cmd.elf | Bin 490864 -> 511584 bytes SYSTEM/system/deshab64/desktop.elf | Bin 140128 -> 140184 bytes SYSTEM/system/deshab64/shell.elf | Bin 791904 -> 791904 bytes SYSTEM/system/user/use/FirstInit.elf | Bin 160456 -> 160456 bytes 49 files changed, 4019 insertions(+), 94 deletions(-) create mode 100644 CODE/UTSM/include/utsm/virtio_mmio.h create mode 100644 CODE/UTSM/vmm/virtio_blk.c create mode 100644 CODE/UTSM/vmm/virtio_mmio.c create mode 100644 CODE/UTSM/vmm/virtio_net.c create mode 100644 CODE/linux/check_env.sh create mode 100644 CODE/linux/fix_driver.sh create mode 100644 CODE/tools/pe-samples/cmd.exe create mode 100644 CODE/tools/pe-samples/gen_pe.py create mode 100644 CODE/tools/pe-samples/hello32.c create mode 100644 CODE/tools/pe-samples/hello32.exe create mode 100644 CODE/tools/pe-samples/hello64.c create mode 100644 CODE/tools/pe-samples/hello64.exe create mode 100644 RE/test-cases/README.md create mode 100644 RE/test-cases/boot-regression.md create mode 100644 RE/test-cases/driver-matrix.md create mode 100644 RE/test-cases/linux-compat.md create mode 100644 RE/test-cases/network-e2e.md create mode 100644 RE/test-cases/pe-compat.md create mode 100644 RE/test-cases/performance-baseline.md create mode 100644 RE/test-cases/userapps-matrix.md create mode 100644 RE/test-cases/x86emu32-coverage.md create mode 100644 SYSTEM/bin/cmd.exe create mode 100644 SYSTEM/bin/hello32.exe create mode 100644 SYSTEM/bin/hello64.exe diff --git a/CODE/UTSM/arch/x86_64/serial.c b/CODE/UTSM/arch/x86_64/serial.c index f89b8f7..6517ec2 100644 --- a/CODE/UTSM/arch/x86_64/serial.c +++ b/CODE/UTSM/arch/x86_64/serial.c @@ -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; +} diff --git a/CODE/UTSM/arch/x86_64/serial.o b/CODE/UTSM/arch/x86_64/serial.o index afeb06bb9edcc50ec13842e85094e58c29d2b63c..3ed8c8cf94d8f24bdebbb32e7e4ac222d85cc15e 100644 GIT binary patch delta 417 zcmaDLv_oWq2IG&7nj4s0_~jcIfFRVP^XdM-%nS@KK)lX_JN^T~w}Xr&y!(H^MESQJ zh&_y?-lLlpq&W8Q7ju!K+y1wa}kz5pt&2^Cj^ieG?=^KO=8DQB`>1?92;rP+Y^56GE7Z~%w+b*MOq zVxHW}W++gcT9lcX6JJuC9AA)H6rVeJ3!5WHNl|5dQEFn!U2BX48%?(T}3_vhhmbsZRYw|*7aYnVtJDJ@XizhR(h%;7z$+*deEaHNJ zKxIMg~P&Oko1&;NWN k)d0kB05caRz%e@~ diff --git a/CODE/UTSM/include/utsm/deshab_ui.h b/CODE/UTSM/include/utsm/deshab_ui.h index 665ba10..7cfd256 100644 --- a/CODE/UTSM/include/utsm/deshab_ui.h +++ b/CODE/UTSM/include/utsm/deshab_ui.h @@ -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 效果) diff --git a/CODE/UTSM/include/utsm/linux_loader.h b/CODE/UTSM/include/utsm/linux_loader.h index 6496f00..ef41995 100644 --- a/CODE/UTSM/include/utsm/linux_loader.h +++ b/CODE/UTSM/include/utsm/linux_loader.h @@ -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 diff --git a/CODE/UTSM/include/utsm/virtio_mmio.h b/CODE/UTSM/include/utsm/virtio_mmio.h new file mode 100644 index 0000000..163506a --- /dev/null +++ b/CODE/UTSM/include/utsm/virtio_mmio.h @@ -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 + +/* 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 */ diff --git a/CODE/UTSM/include/utsm/vmx.h b/CODE/UTSM/include/utsm/vmx.h index 4d737f3..ed225db 100644 --- a/CODE/UTSM/include/utsm/vmx.h +++ b/CODE/UTSM/include/utsm/vmx.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 diff --git a/CODE/UTSM/vmm/linux_boot.c b/CODE/UTSM/vmm/linux_boot.c index e8f8cd3..fc4b24c 100644 --- a/CODE/UTSM/vmm/linux_boot.c +++ b/CODE/UTSM/vmm/linux_boot.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #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; diff --git a/CODE/UTSM/vmm/linux_loader.c b/CODE/UTSM/vmm/linux_loader.c index bb45b01..2662835 100644 --- a/CODE/UTSM/vmm/linux_loader.c +++ b/CODE/UTSM/vmm/linux_loader.c @@ -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, diff --git a/CODE/UTSM/vmm/virtio_blk.c b/CODE/UTSM/vmm/virtio_blk.c new file mode 100644 index 0000000..2d151ba --- /dev/null +++ b/CODE/UTSM/vmm/virtio_blk.c @@ -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 +#include +#include +#include +#include + +/* 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); +} diff --git a/CODE/UTSM/vmm/virtio_mmio.c b/CODE/UTSM/vmm/virtio_mmio.c new file mode 100644 index 0000000..2694606 --- /dev/null +++ b/CODE/UTSM/vmm/virtio_mmio.c @@ -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 +#include +#include +#include +#include +#include +#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"); +} diff --git a/CODE/UTSM/vmm/virtio_net.c b/CODE/UTSM/vmm/virtio_net.c new file mode 100644 index 0000000..a2c2a9e --- /dev/null +++ b/CODE/UTSM/vmm/virtio_net.c @@ -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 +#include +#include +#include +#include + +/* 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); +} diff --git a/CODE/UTSM/vmm/vmexit.c b/CODE/UTSM/vmm/vmexit.c index 08eee02..9e8042d 100644 --- a/CODE/UTSM/vmm/vmexit.c +++ b/CODE/UTSM/vmm/vmexit.c @@ -5,15 +5,35 @@ #include #include #include +#include +#include +#include +#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; } diff --git a/CODE/cmd/main.c b/CODE/cmd/main.c index edef93f..70b504f 100644 --- a/CODE/cmd/main.c +++ b/CODE/cmd/main.c @@ -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(" .exe 运行 PE32+/PE32 程序 (如 hello.exe)\r\n"); term_puts(" 自动查找 name.exe 并运行\r\n"); + term_puts(" pe 显式运行 PE 程序 (如 pe hello64)\r\n"); + term_puts(" peinfo 显示 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(); diff --git a/CODE/desktop/main.c b/CODE/desktop/main.c index 70d2e53..b06775c 100644 --- a/CODE/desktop/main.c +++ b/CODE/desktop/main.c @@ -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; diff --git a/CODE/firstInit/main.c b/CODE/firstInit/main.c index 05c069c..b485272 100644 --- a/CODE/firstInit/main.c +++ b/CODE/firstInit/main.c @@ -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" diff --git a/CODE/firstInit/main.o b/CODE/firstInit/main.o index efa29d2064d4b242356450217129f5d5ffdb21be..e6739297cb4263dd005fcab174e07267c8b8eaf7 100644 GIT binary patch delta 644 zcmew`gX_Z#t_{Zo*n4>zJbFz$CZ88jV$=Qe|9@KAeW%2GMNeF?ph(67xQw&MktrP@Sd!aq8SGB(#DJ*9W=xo@ z5UrsAaz}Yz8Wy*M98d&tKz%e;%~H5EZ#IhA#)oDhkx}1Vl-*vG&A7cNo9Ukd0H20A A_W%F@ delta 644 zcmew`gX_Z#t_{ZoI6IGd9DK&=!FX))K?x-m?LYtjPyQ-kqc}gxh=ITT>F@vl`Q=?0 z7(P$<-x~1u|Nm|0GkJ@kEmUXef1Elu3kj`Y zL$#NE`%jSahRJUvOkoCqMX+1#BuTE-Oj5U*QLI)F^61`sZuM4EsNMLxlL`{gIv&o3TF16~hdL zXblCBJIedgu(%!MfFg(k>Z7r0mcp%hvr)`8J`4+qiu&fF?DnE;#_dJfO#c)By30Ba diff --git a/CODE/linux/build.sh b/CODE/linux/build.sh index dcf19b4..ca9f38e 100644 --- a/CODE/linux/build.sh +++ b/CODE/linux/build.sh @@ -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 diff --git a/CODE/linux/check_env.sh b/CODE/linux/check_env.sh new file mode 100644 index 0000000..3c3d38a --- /dev/null +++ b/CODE/linux/check_env.sh @@ -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 diff --git a/CODE/linux/fix_driver.sh b/CODE/linux/fix_driver.sh new file mode 100644 index 0000000..5499f5c --- /dev/null +++ b/CODE/linux/fix_driver.sh @@ -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" diff --git a/CODE/linux/initramfs/init b/CODE/linux/initramfs/init index 5850368..887510e 100644 --- a/CODE/linux/initramfs/init +++ b/CODE/linux/initramfs/init @@ -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 diff --git a/CODE/shell/main.c b/CODE/shell/main.c index c970248..3823bdb 100644 --- a/CODE/shell/main.c +++ b/CODE/shell/main.c @@ -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; diff --git a/CODE/shell/main.o b/CODE/shell/main.o index d24563a683b4c7feb38389126491443fe877d3d1..5f4bf384d9d8e48526e8cd20b87c4850dc235257 100644 GIT binary patch delta 27619 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 diff --git a/CODE/tools/fileman/main.c b/CODE/tools/fileman/main.c index ca8ff4e..b79fb24 100644 --- a/CODE/tools/fileman/main.c +++ b/CODE/tools/fileman/main.c @@ -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; + } } } } diff --git a/CODE/tools/pe-samples/cmd.exe b/CODE/tools/pe-samples/cmd.exe new file mode 100644 index 0000000000000000000000000000000000000000..b3ac238458f6987f72b81d86c223911ab13edff7 GIT binary patch literal 1536 zcmeZ`Vjvqh0CfhqGBBjHF`}69fkBCZn+Yhyf+Q>eWP>mRg9BJKhz(T31{Oll3d!D z_ literal 0 HcmV?d00001 diff --git a/CODE/tools/pe-samples/gen_pe.py b/CODE/tools/pe-samples/gen_pe.py new file mode 100644 index 0000000..f572201 --- /dev/null +++ b/CODE/tools/pe-samples/gen_pe.py @@ -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(' +#include + +int __cdecl main(void) { + printf("Hello from PE32!\r\n"); + printf("Deshab x86emu32 interpreter works.\r\n"); + ExitProcess(0); + return 0; +} diff --git a/CODE/tools/pe-samples/hello32.exe b/CODE/tools/pe-samples/hello32.exe new file mode 100644 index 0000000000000000000000000000000000000000..9cd27ea51563f76b63f1cde565382011a7079449 GIT binary patch literal 2048 zcmeZ`Vjvqh0CfhqGBEfsGNPFAfPsmT8zjVrBrE`AgD@C4K-f$OC2*w*Km|Z?5MTzA zKuutMFdE4ikh};3gAG_6eF?pi)QS?2l^}2dDtU0CnsMattdZNA;8txNRgLo@Ooj?Wt4<~Fav`E5RY6?P?VWh zl7`#=t`(Uj0Y&-Asl~;(6~Se4i_4OWO7v24av(z4sYQ9IImSi^5xCO9!4N_?BLI{R VfP5Atu>_zPG9RIOlr +#include + +int main(void) { + printf("Hello from PE32+!\r\n"); + printf("Deshab PE compat layer works.\r\n"); + ExitProcess(0); + return 0; +} diff --git a/CODE/tools/pe-samples/hello64.exe b/CODE/tools/pe-samples/hello64.exe new file mode 100644 index 0000000000000000000000000000000000000000..cfb10a83bf9560df1483d95bfceb514344ff36d4 GIT binary patch literal 1536 zcmeZ`Vjvqh0CfhqGBBjHF`}69fkBCZn+Yhyf+Q>eWP>mRg9BJKhz(T31{Oll3d!D zPAx8G$Sp2QE-KMW$;n~JPA$qy%`r9ta~OmW{s{mD8IaF` PB$faaL*^q?4^kEY4n`xZ literal 0 HcmV?d00001 diff --git a/ISO/run_qemu.bat b/ISO/run_qemu.bat index bb7c5dc..969c726 100644 --- a/ISO/run_qemu.bat +++ b/ISO/run_qemu.bat @@ -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 ^ diff --git a/RE/Bug记录.txt b/RE/Bug记录.txt index a0a97db..d265dd3 100644 --- a/RE/Bug记录.txt +++ b/RE/Bug记录.txt @@ -1,6 +1,39 @@ -开发者模式中启动的Shell的提示符没有在第一行也没有显示 -shell光标没有闪烁 -Login界面没有显示鼠标指针 -在桌面上时每按下按键屏幕就会闪烁 -窗口打开和关闭时的动画太卡,而且屏幕会闪,直接去掉动画 -启动一次之后,第二次开机启动会直接退出 \ No newline at end of file +# Deshab Bug 记录 + +> 所有测试与开发过程中发现的 bug 统一登记于此。每个 bug 按 BUG-YYYYMMDD-NNN 编号。 +> 测试用例文档位于 RE/test-cases/。 + +## 严重程度定义 + +- P0 阻断: 影响主链路启动或核心功能完全不可用(必须立即修复) +- P1 严重: 功能可降级但关键路径受损(本轮内修复) +- P2 一般: 非关键路径异常或体验问题(下一里程碑前修复) +- P3 提示: 日志/小问题,不影响功能(记录备查) + +## Bug 条目格式 + +``` +### BUG-YYYYMMDD-NNN [P级] [模块] 简述 +- 现象: +- 复现步骤: +- 串口/日志证据: +- 根因分析: +- 修复方案: +- 状态: [OPEN/FIXING/FIXED/CLOSED] +- 修复提交: +- 验证: <验证方法与结果> +``` + +--- + +## Bug 列表 + + +P2: 软件Files的窗口点击叉无法关闭 +P3: 在第一页已经有了终端,所以在第四页的桌面上不用显示终端和CMD +P2: 在开发者模式中的shell输入命令并回车时文字消失没有反应 +P3: 启动时的打字机效果再快3倍 +P2: 桌面中第一页的shell中输入命令回车后,上一条命令输入的文字会跑到下一行 +P2: 桌面中第一页的Files显示的不是SYSTEM/user中的文件夹内容 +P2: Files中没有显示文件夹,直接显示了文件夹下的文件 +P2: Files没有提供“..”来供用户返回上一级的文件夹 \ No newline at end of file diff --git a/RE/test-cases/README.md b/RE/test-cases/README.md new file mode 100644 index 0000000..80b5a8e --- /dev/null +++ b/RE/test-cases/README.md @@ -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-.md +``` + +## Bug 跟踪 + +所有测试发现的 bug 登记到 [../Bug记录.txt](../Bug记录.txt),并在对应测试文档中引用 bug 编号。 diff --git a/RE/test-cases/boot-regression.md b/RE/test-cases/boot-regression.md new file mode 100644 index 0000000..65e6245 --- /dev/null +++ b/RE/test-cases/boot-regression.md @@ -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/-.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` diff --git a/RE/test-cases/driver-matrix.md b/RE/test-cases/driver-matrix.md new file mode 100644 index 0000000..c50c1c5 --- /dev/null +++ b/RE/test-cases/driver-matrix.md @@ -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 探测位置 diff --git a/RE/test-cases/linux-compat.md b/RE/test-cases/linux-compat.md new file mode 100644 index 0000000..3b38ab7 --- /dev/null +++ b/RE/test-cases/linux-compat.md @@ -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 提示符) +``` diff --git a/RE/test-cases/network-e2e.md b/RE/test-cases/network-e2e.md new file mode 100644 index 0000000..2a4824d --- /dev/null +++ b/RE/test-cases/network-e2e.md @@ -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 中无设备 diff --git a/RE/test-cases/pe-compat.md b/RE/test-cases/pe-compat.md new file mode 100644 index 0000000..ad25a95 --- /dev/null +++ b/RE/test-cases/pe-compat.md @@ -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 +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 +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 约束) diff --git a/RE/test-cases/performance-baseline.md b/RE/test-cases/performance-baseline.md new file mode 100644 index 0000000..15bccfc --- /dev/null +++ b/RE/test-cases/performance-baseline.md @@ -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: "); + +/* 各项测试包围 TSC */ +u64 t0 = rdtsc(); +/* ... 操作 ... */ +u64 t1 = rdtsc(); +u64 ms = (t1 - t0) * 1000 / tsc_freq; +logl("[shell] perf: = 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 性能差异巨大,必须固定加速方案 +- 多次测量取中位数,避免单次抖动 diff --git a/RE/test-cases/userapps-matrix.md b/RE/test-cases/userapps-matrix.md new file mode 100644 index 0000000..aef6fc9 --- /dev/null +++ b/RE/test-cases/userapps-matrix.md @@ -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 ` | 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 ` | 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) diff --git a/RE/test-cases/x86emu32-coverage.md b/RE/test-cases/x86emu32-coverage.md new file mode 100644 index 0000000..d89bb77 --- /dev/null +++ b/RE/test-cases/x86emu32-coverage.md @@ -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%。 diff --git a/SYSTEM/bin/cmd.exe b/SYSTEM/bin/cmd.exe new file mode 100644 index 0000000000000000000000000000000000000000..b3ac238458f6987f72b81d86c223911ab13edff7 GIT binary patch literal 1536 zcmeZ`Vjvqh0CfhqGBBjHF`}69fkBCZn+Yhyf+Q>eWP>mRg9BJKhz(T31{Oll3d!D z_ literal 0 HcmV?d00001 diff --git a/SYSTEM/bin/hello32.exe b/SYSTEM/bin/hello32.exe new file mode 100644 index 0000000000000000000000000000000000000000..9cd27ea51563f76b63f1cde565382011a7079449 GIT binary patch literal 2048 zcmeZ`Vjvqh0CfhqGBEfsGNPFAfPsmT8zjVrBrE`AgD@C4K-f$OC2*w*Km|Z?5MTzA zKuutMFdE4ikh};3gAG_6eF?pi)QS?2l^}2dDtU0CnsMattdZNA;8txNRgLo@Ooj?Wt4<~Fav`E5RY6?P?VWh zl7`#=t`(Uj0Y&-Asl~;(6~Se4i_4OWO7v24av(z4sYQ9IImSi^5xCO9!4N_?BLI{R VfP5Atu>_zPG9RIOlreWP>mRg9BJKhz(T31{Oll3d!D zPAx8G$Sp2QE-KMW$;n~JPA$qy%`r9ta~OmW{s{mD8IaF` PB$faaL*^q?4^kEY4n`xZ literal 0 HcmV?d00001 diff --git a/SYSTEM/boot/utsm.elf b/SYSTEM/boot/utsm.elf index 06933ebd997c005b0ce3076f7d75a2146ec390d1..f31d6eea5c9f16aa4e4197fcdfb4bc365fdb328c 100644 GIT binary patch delta 49434 zcmce930zd=`v07PQB?5Y0^&X*xTD}wf=)6DdPq=m&wWpGBb-Ssb%q9#<20onyVPy7 z?Y7>sivcdlfXaw^-7+gJMKe9)xS%GA3-kXz?>Pf#aqs>0yT8AmPdv-}JkR^Q`|~bm z`oUd))2V=UVa?Vha#kc;O!;=H%U)6 z>BW^xVv~0Lo&fzWrP-GXNPjC0^bv(l1^w(i&_xP80rUkE=&z)onzZD~b1RxeaNK37 zb+a|0mrxU?I6g)wI;$6*0(UrhLBBnhq=IH6m87W;CQZL2wQVkNm!!GPZ5F;gimK6! z6~tc)@B3*qa_{?IuUJ$-O6`-00EzZzWF&@lT#mRIT$(#3M`BBv>+LWY9vZhd~uN3h@+ z!ilwVf_XT|(Q02f)jv-C(%r0^H2BFt@Utrlx{pphd`VZ7P@MhV~3Ea6c%(AFVCf1d)=ty}0rnmn8$Zr)oirM8G@Ts9h>H*3fW z7Vxz6LW}Y3uVXx=24^|>%yU}C?>`qZZ#4jJ4{9t(w_9}7c6?T98#Z)kz|)-7JYp!H zmt3Dq%q%rUCE0Gb;xntzK8bPJ4Jb95rD~IGPAw$N`#gcDAJX$QO!7LD90kbrldyf3 z?$2^o_xosLBzHt|LLAR$bw`tXOi3;#<8j%zqYV75B@NFyRQZZh-Hc z3Z<7oia)r!KyPk_p&=@F?5BKI5RHI*UBe5U#-KsX@mY88dA$ktsFoL`Ypp^CoIB5H z68JpBDF(kZTm&?7E~B{HSahxdpVzh+4F1a-2^{*|iyxy7W4z)C!BwQcE*V?LTdr%Z z8I`|+Z911#%4fa^%vzaX{+`dv?v6C;nE>;CYe5H~iRNN!K{$}n=B?I(jz9$SW@|wN z5Tp4cYe6KC!REKE1)YHC&97PuIy2-YYe5$viDsL%pevAMKI>VO(OS?AMBq?9Yd$DG zKRfQa6xb$$yPg}<<`~x@9fL)%oA=6(VLC?@%~-@)lf^2zmqPw+h199t#N=j!X%51e z(=`Mp*y96|#Gzq=&G1*FFJV0}H9?PruUNtmXnafww&ejDQ$J9DHA)BC_35Jn6DCsS z=7TT}#i0}A{Rfag227~1Rin`l882lTkoQL;KYna|3kB9QvYYoJ{=RWI|9i_eSR|V!H~DVHt@RiS^KD_ zT0e-F31XsCoN|oM3*pX67u$E@u1ihyy{WE^*(*adeBL%{8^Sr@)I5Z1fp@$v&C_>) z{8~L~Rr*ptBN8T!*?k-Hd0%y4sHriOz6w`yD1FsY>nHW=&;h_2pFOa*;4Kki%gvYI z;F=lA1oaaN4RT((w4y`D@J(=A-hZ7H;RcQRLc+z;h}v;rwSsiUJ)3o^6*;XgH-WHed6%NwmV?b6nCT~gzGIX9clbD3bfz z%6LA{s_=eMcztX0&MCa;+Pq&CUXR+m^9rv^ZQcci7g3uhTNI|gHq$MZ7s+j^{kTYE zSZx6hahliWTp~_zZO&!l1lHzUAx=PT&Q-BIwn+A`%__H@M-9BF)WEfp#OoUQTshjs zv8qfyNr=m}p601T<6cA%{J=W+SQEPt$1`ND| z{vf|WiF0WlbGbfC17od{m_x&1H6_l+I7DK1YSBeXqDbrD3`%^dJtgX0!zeXXNu4(Z zcmkysHfO4Djg`hlw6ko7Gg(ga92g5;0+9d`nYCG?!PsmJW+XBDfa&QCDxFrBUolh4 zo4#Tq25}eI?@-nwC?hriKc@m($k1A)V>O&anP64!MlwndJ9LrCYjFt}zp_LozsO)F z8^GXlOiLaS$oDjbn<+JILA6hDPl8znCR?9wkjN+!`8kb=v}HcE!D=9H2V3!JC#ums zPnj}$U}2H2ryVw^%BcZ5HS_K zWbKcvvquCF8p}{DTQ6DT7}}7~0Ss+K=s<=B5^7**kUi5H4@?{y9OIPsbZQ!{qnw@; zD}|yn6c~xANTmtfNWr3Uym8#<)MUpFkqQjQ^X5tQJ12Bpe-TPG(Ny6CRf(dhLPS%L zmKJwz8~y~zb*(AJbS777YJ#e?3OW+|d*^oHO&$`P_j|F+ojZ0s;D)uB8duZQTw4l` zKvBpjB6EGZbZMJGa#5VR?K>Tn>rlT_UuWDbm|rLb-t4ue-d!X)|t0vEAA> zo_iBb%v}>)#TtD_tMtr}Q0cjDeDtH#;a1=BdSeh+3t~`5i0zuY4skLMF~LvQl=KuO z!B$o-rFCm*U`qXJlurCcO4Ylbq6>(?oZence<#v>`jHyUH)LTRvv9hiI@?ULfS8<` z1-j%Si_vT7jOrL)X*R*78}8o`6AJZsolt`&GBj9Iq8sYpQCinM#G>zrF-l_od%D!Z zqif;)eYnl(FBmlO8jX230)#DnkpZ9C6n_yGd<2T32SL>B_Up)@AMu&D$gs`eQ3TyY z*@I3oL1O@ucvOn$vB?1Y#zOKo>DvoQ{QC#fPoy-cN*4S6tVc?cw}1 z>)5NYdCNG}IBkmiI_Y5To+8bP>g4x0I?GE@WBe|09i?BQQU;S7{QF0jHWa*0A=YI+ z?TJt3N<78NBD`@Fa#(SL8CZh3k4F4OBJKSqajFkede5P4U`B8}&Lz2u5dwRYzAeV) zwqA0F=Kn-FrDHvNgpoj3!%mIjWs;Z{Ao%tBNYhaY>orJQwmSF8UZ=E;M^Ud#y-L|R zP47yHyyBoOm8jMMjx4>G4;1?`#heIA+~LSGYA=F+qN)6^7e_+>LaC|eM2K5 zC)k`-o_2z*R4dFcovugQI9}Hi-@DgSUvT_2K0}({cd?(YS(J35Z!BNhG>XIA5a8V_ z>b_|jC588kfU#Vm-yuJXL`!6vOdWPE{j1*NyR5JEkIF#9Vt>947_~7r(n=gzae*pB*ZM_H!=zlUg;YnU!In>ug3Hxs zjI=$rdpr0b@Pmt{>{`bLk6pJX3*FWsaOhUsvU2_7-qKozH|oU+1%8(|97)rje--_C zFfm-Z!a#PSK_hC1in?^$sxdvJSV?t5tFeXE*oJDXpc*?>jrmtw=@w{F%+Lr|4oYl> z(uUxf^s$CLK+ItnL{1=R{`mgAGK2vm`tzAQ^0pZc`ved81YL??TD9PL_-HeH?-Tsi zCwNd1+|=LVGx-gZJmr%-;gkGMkvv>&aw8M`l?k?qHJl;A=5X>SMNku~STrrM8OnV^ zmwZAO6rpIJ(Bs2whPytgTRy4liqv$URHV&Nk8019m~0=~Kh{u1f(F#cI!GkP>L4%8 zc`Wh?(u;BKA|YyUENOyQN1oc-QpRk3rxMzJsbBPqzbkVR)hIQixMNZR&}1=ll0eWA zI4LQkD7C+I^0A0WFxc+No;hi{f(K*$oHRkfv4_o^l-fTxVo;f1>tGA^GJIYF&l#hg zi8!G_*d6eAAOAhU7ODM^@Sc<|+&VmkRtWVN#%5SlKN>~rO|h~)=*fD~oVqr}NlS*b ziXQ;Rv;OLm)CojQ9APuGWvWfkM+1<8f@r3=F#-^4D655Q;?q*;kk*ajdSy@|4$$JH zt3yJ@ZqX^yv>G7|`kyimMu$shfvIwA9DuF_N@js(0F`m}mSA+TD z+4?aRf_T`|S`ZCpuq^^SVMb93v51U{#|5$922ydX$FZYE$hbo@O?oT%ZvmSO16@fd zl4D9e$?&r)DX7)y5C;t=KhHRYTPFl=pO8_^42ilSOiCB8>IxZ_JIpJ8Sg8*y=lozr z42n|3x{;WYTjv@yq6Y0ygSM+dTi2jrHE2i;+Ehhdx?o>{=DKu^7#B!AhMa6YLByg{@>5|8+rg2TdT3s1pHPx;6Mq zi7wsGDu!n6F+nOH+G5;W;M4>|cHOH!+GcncDB_b3phP`zPQf1XsWw`gGVGBl^TEvaZ-T+wj1#=YnNB)B37SNr-cliUU;0Uq2_QY% zn4=~BO*AyMP%7IE?Rsz}v>DDINyEQdgjneo7cG?!Yu)_}7tIX_I*q^SGi{aH^hpI& zOk;r_e`_K}d(c;qe?*9yB0@6B=F|(Zd(+{m?itYD(gcT*OAvJnL5QV@<#pn~09QhQ zKa8L%O#ukW#spx33TaBt0S{vpW-~OY@u;)vQ4<&$5X9r}zxL>ihIwS!s(H|(cR^Qs z^d^W0JfhM);L%`YRO1n~lFeWMPh(mh<$q61m4Fizv}U}Lm}&W zft;io$Z}3GHFxRqyvjl!>IlcJKl zkl`f6jB(PGaCDp-%F?-8BCne{T z(-IHT*$_#2+C#44G!*h8&LCWgo3LPGt#TF7q0}%4;2`}39jUlnlj&S&3s?!_d5~Fi zrP&kp;Z_}|Ny+l^nJ3U>U<+_kzEMIME~C4bwNFZao7gjmxP29`0-ux4Ozf_mGe&AW zshhU%G%0S<=h_|Vk~}F~J9C*7I5~XqvM~_@)>IG6W4V z#obI%JCr7Ar@W@kTOz$VWl*~gIKuL@fy`k<-fAx$EZM++Oo?8yY74q2pVbC#d%A7I@vvqmVnj{c+HanezFZxuy)`P=wYndtT{#_Q zP6l?|*NZcHOA^F>E%!;kS_fLbram}#6Ye|u9WJF^pKCBWuH5`xuh(Yy1{|ygAF@qu z7FAR>YAL2P+41)@5T1b$ub{c$Ll^*_PP0c#!tIV|s#b3i)-*Dk!L;?bL=|F-c-v^Q zC_+Zslo8nPS32Q1`3_b5_A$snJ_OvJJ8u{*1g>LrR=f&EZQF^XF})%5CVKw9CZ~-ZK@}IXYJjdIO^qHR$z>0qfKZiC+`>$SHQwaw7E!;}gbI`MIrd&$e5S zf!_i$A7kd#8^Csa4&*j?il=SqP&LOI*JmfvE7Yf6Wa&V0Fw>7Z|PtaVs zh^ofrnvM4WI3jVZ!jWHzPvs|6JXAuF{uv!D-Fsb~INb4ymUX<;F+0i9fex|XKq7`% z@C`H*{y`@Iv%8$>(?N0U=5p*Vu3twR;1u|0jKa&`L)3(^t~&>e%@ABUf=nmI>`};- z=)T2Y>IouEyUSP}t{13jKYNWbr^9r2+ zSPiZ#((#wNX+O9kX&qhrEMLo6jXQ^p7@6vRkBuGI1Pm%cERpNK%33UQCIVUfbFWpB zyUdZMI@%b5$8pweJPIMt)8Udmrox@9q@SWAu-_yo^i>$$CxY<{F*+)Y%k!jsM^dnK z6xw`dCt})zUOXxVzue`~Nec58ItiE~KnZ@v06r5p(qN<<7DSqC7olq=i|6I@unO|A z=cFYshidmtme#yHFZhEa%xX8&$=uz- zcJp8w2bKSPLb~=!8*S-R62C68(L!1i>%(}*XQV;vT8EBCZ%q=9%MCM;rQ;hXP95bc zo0PV$g{7^Bf-Loh#cBuv$(d>GjHQm>dA)jk?Q%KjjPCn&UT-07?{?CbJO-g((d`A8 zNZxsqjizSMTSKpJdO@#%DjuWN-tWjoHsR?}>+)EnfR(4PGCQL(8n)3}RzNyeLgxKVIANqV)CpZrY#S(wX&bwQI~9HiY(j zkGze6w=na<-jO9+uN z5fIlz#OP%|o~IcHd*0&kkiVaoxK|&wU^bcJ9{CcURXjLyt%fHdj9&XSO2o#ZT{jOL zdzv1ldV%gFe}ooFFp6;_z;)9+w@cv;WZZag@6%H8cZJ)DaTCDZgSlGHdC9^|1QUC6 zYCKiAi=&rgs}=cMzf!^&CE*s0w>d1K-xVZ4_B`FWj#5~^D6A@W*Y_ClTrL@eJKatf zgRG8l!DSq495Y(5UFt8dZL85(*prVXXkt`L>=f}2Lu@2amoD*1->JnQV3d^P$P3qS)Y}VIt``Nl7gtG5};uh5R+)4Hf(o z!UGljEy5dP4*zNT{YR3NIj7+P`-_mG!{)^IMH^Zy}(da0c@g{uYoFTGY^TH zV5>$xOQfDiEF&8ETO#Xqq0}IdyNT?EKj;7jgh$L{_~(RoQt(ZLw*tOXgM%V@J&~49 z_zRsZA|e!g1%Uk9K=>ji7Ra8SwCJ77a^mD*ccFtce@LX8@DhdC4+00=dc3Hrm)PJp zqwPQgV~5>^FO%SxOmL4vyyp|l_@`B?S#RvHr_k=-9a3x61Y4ON7gSp0>MVSP)SqGM zB?>V+QF6W>YDs21SK&Gm7*uWYkUwhF*uhn(iKp>+UjASh3Z!Mk6*-59b|4UWIc1Tk zK$cV?uOy-=BdX*jASjeF7)mqkEv9V>o@bMrT-rt|f*3dgPrzimiabW`X<& zDAq7BUb!2k_`~GR_!$sv4U*S8l}RL{Bpm14LW7b*%|fJkhf><9LM;*38F607IH-Zy zLVx0&04^GUShm5vnkvhhd)00Ylz^{!!Qy z{gJ}7EO4-q#7M?Yeh zF+eU=7y=j@X))VMw99pm!u!h<`G}FBqj8|<(9vv(EQ5)%_v3z%*1y@>ax8&dcTJq5 zM38f!?b4kbrA+$tD#+`>g^cSB#8z?u5?OqM7`=bZCYiP-$LSbCMs~_Z=afsAquOxk z)&oP8Qa0x)Krxb6_I}Sbg&!7|s~_!=u7D@6c$5iUf-S116X4;1QDRSF*s>>&4`qiV zZRN(e6URoTd5Nwg4uWX~eGi47;y!Pq;D@KfVem$w#-_NN!p>af&eY}9IBK1Nj7|-W zIcYV}Q8s4|k?({s>w$O%z2mKDzjtu1eD$rijW;nipV^H@>ge27Z@;PSTMsXZ)XmyK zs`cYmJd*i_HhJzvD63r2l$mNJ%qoW;rzuA0BVF7S$A#razB`{A*kmDRwHj{Sqp0gq zLA-(YIC4WgGKbC9auFg`I;VBMhiwsp&0stc7Wf3Z+IAH+oFVOd@5|tcJ5Zy1ChfKm z52fso#P>fPw2|VfxH_h3S?tB$iWW(K1s2=m(q52B=w?>(zXvfv`2756K5PGjE+2Z>F#n>K2Lh) z!;rc2Vb@S@ImCZ^o|#OCYO;7m{`Gk&_HU`uifMu9m0MBuvhz6-NZgA`#zp(8y>j6_ zFwjP^m`C;sj^j>OH$j+BBV5eK?1`Lp}1YxICq_vt48E{w1$aW6y909Q*- z=sqMaO6+yx1dByxye8Styg4c3yhgLI94$EIsNm_#8&IT)+zADpTw76Cxds+i0jlPTKrYsAVTb zghn!f5_;WqeGY*MzzgdES2sSg1v3DOPnlY%;ds)ZhI7-p4~;a^K>pPlIDRD!YURck>mL~0)brObC)>KmDrjey26Nv`hbeg* zW+${d;Sm0oy6;npWE&aop)?T~Koqdkb&H)Q3_K#K7U6vG3Saaxhg)dKoJ;-ovYK|^Uq{)6=pCOzY^B<%UDh% zAU9{Y?#Y|veLRMP?h%G+IJ)A-g5Z}}fJLe&m`EGI(o#vRKTz7j0qv(~5Zb#MO+1W5 z8l{KjCR9LddR{F`wy!!&7XYso>-SmjaOT!Y8D8Ye@h|LSrd)S1B4iFev1xGB49;Se zbMbH;bw#5C%hZRz(Fz@s2as&n6*3UyBp~*P*h(JB|6mo`&rXsqS8p`Be3jWSOedBy z6t~ugXtH=+HvL>ZhC-J@G!vo)6{uym232ms$Z4~Xny#~j2!XS%3F-JW^62z*E z^GN1mjvCR6n^P|ava&gc-V@9*R75ndvB%B-6f9Y#rk421D2Y494D^M8 zzP@2_6^=$D&m>0U9NP7*Z9!K{UPQlls8N% zFZKRfm3#IitDz?-AZbKjRb4lstau0KbuT78mu?S^Eh(pD(NF>u(}TtjS)8Sc(NPxm zeqVqO)@};qNZ-8;4GNcKn&s#%J15?Ql<9ailq zWiJn?(R-g};Nhfqjr?>mHS;>E4;|}mm}#7H!y1Jxv=p==g>q8(3lyjrI{5G-`8)_o z7I1GL&QVEksE_@kR8%NG;1qpwOQzvyRyrQk1-#AjZ>Uph9yGYWgq z{V&L6^$jOY8iGA37(F+T&9%jRCPh|)z0DEK*d`B)y>YgERoRDCm>h=pfA zzMa4msiAj`w%bXjdiXove1RDEj27EoJ?O>epNYP2RLJ?}gG5%Tq{&=Jq*o==&6`2O z3{#zrXM*-2xErHG%`&RBeM_ir5!)3pgvEBOyFG)2m+q!KP4Hq2(rG4|$$o+qJ5k3s z6nK)S9)BeUjB>mmDK#C>K*$r^%Q>nlwFGQ^xN0OBkC z@izriL*oQ-O&IV5@s&1G>6d2)PRH^!#r@i1x`|bsriP{m7{f1Nz#?D{Sci)qKCy_M z_o_#}lzw4=ba8V#i)#S(3Z~~=x_lsT2FGEa++{-@*hiFzf7nF51QeSn#OR=OFM{H^ zUE3+uquWk0E}dDGcIlP?qa^g8K85uXX!eeRp34=6qd$s>58{@F1%-j05U}5-fiqx{ zBz@JvGCEZri&dP$OuWR}DLk`Ae3yx!4zN~$O7avE`X3ms2vm?$I@Z!V<8 zx?dD{MdxYH_#F-Tp~%eBszetAHZ&R&K-#CcUY0+F25={?hOw(dKe4zAbQm+WDiddn zdPKqHR7gHgN9Wo_W!StXmI(z`@bvf(lx)}4gGXZGZO2Hw@UvC6SyoTHZBNjZ(UlJM zKh(p+hnE7rLvV~;hp6i{gwdN2Mq3UPqJ1^;FAZon#AogHM{D!>hADLVZC|ws_)}U> z51)g?GV=Kc#Jw>`ZrtkfI=;ZGMjNaWwuS>9}yf!yszzQv&IS1At?qb4xzUc}7 zWQ1+3{cvh+`pPI;ZW@bz^&f5@*i)eG!XhZp5dziu#uV>=i2Rf61MA7>5Cx!`3t~>nu~Tt_nSv?~;Md@b zIE`59jrgY?JKMQN+kHekMevrzJmDeja9*D*x{Y6Rh=2I3_K=1VC!b$LpJ<4Qo0;uw zvW+b#3Tojqy{LcMix}-w1H=|=UeXis$FYfn;QhiZFR z`T4{j+_b7B#$0yOJpOx^UV@QYE;%4n%O#7?rwVifs`Y0SSH=a@b|mTdj)W;rc#JGZ zQ(x5V&v+M$^0kvcRc*CT;!YWDs{=T5Up91OO38coJ=BWvYptgqoVLWATt`flu7IjB z^BpYW3E5rTIe;aa1fzXKfUW4ht@1ue@oNS_wdOTjec& z4bOP>$0@uDTV(~mrat2ZBq_X`w#u9Q8fwjw_y(gC-fy^32vvD*Yh-s>$YPIYCKc7 z64;K(N#hx{mB4n|ntFzAC9s_$7Ec_Dd$tD<-g7H~?U+V1p75;%wl^d^lzjlTy%FIJ zw-VSMh(9H`qM$x&sZah8XFJZLHSRJr`&PoXHzx83k%VnW0IG4*E@>-a+p%_P+_^*& zww=o7{s1Jp;cqf+qUR=vhIi>|$F^Ijb$I5R$Y^H7Yt>a2h|rT~<-n`{5oIN4fnet7 zhp>rnknBOFz!0?rcI;LmN`L;X;(+1;#C={q zt0!_$L+L)3ZNS)ZY%xXGt-+`cpwu%&e4sVUaF)((v%IV0a7ss&puB(=?5RH_+Xn|E z#}*lRsVK#+pcu?Llhq4jzfHR@1rN~w} z%@Sg(Oj!1mc*q-Ze;(%bf@kNan>?p&mD5dvtuoCdWK`Zm1DO-;8c(TR!1s1%boPXR zK+leXu_*~-Jzv`ghj}*JwVuxl*r58*F37e@&NMQ{nN@1eu@BaJHrV6uc^vlmGEa7l zGow<2F2TR>9Bn|@Rkz=BU1AUMFFuX_BG^Yo3wV&3IR!*U5p7H1*irWgAojr#?xBFS zdoev$zAelOa-O9+DqTHLn16&y9di<&pV{rKm)TbY2(h2ZjFph<~O+-qlhy8xT#MgsV$T9w%zcT_2|!VAxYKsp?TS&nQ_gI( zg%Srmr0(Y=ZY1N9w(T~@X9k0$Sf(+cm`fwc?FSUQkp8s7HNw~ASZ`R(SS|Jskj~jx z1k=MsnlEiGymn!qLBER`?bh)Qxg_B(M$!X?JV4yfNukP*U-Lc?F_+wRzebYAFvmD= zjsJLsW<^?T@zQ5RQ_E=2GM_06t1ueEp4nnn1CGXiMek=TPa%2{XWKj@~jZCtQW^7s}yv+m#wI*ha<>LoGi+42^-{WmDTE$IFzLJ-MZfnV})qkwr@4hp zG{mEjx#BV{zypsmtcKUmVBs=x>{}g|uHY0cV~FGX+`y4zJ76WcWssn)$`BlQOmW|t z=KIW^x-fiGi2`H=-{%W&{woCX}SvMaxvJ)$SO=s9GU~j`lcH(VdDU4SMEP-KnfDK^S zT~{JvO-`fkkQ%BxqzYuwFlS^hF4R){VFyhBKVbg0c@eoM&_={Db~Cn01=uR>#a8K9 zAyx5C`Xj%CBR862+Cog;FcV&jiL_TT+&+z2*uH8npTA7&iG3hI02dtxdx3+>mg$&1 z(ek;N;M+kRb0vIFA(X~jqHLlH)J>Gcb|Pk6q$?Y#W7y6-;m_yq6;nRPy~SocEkh(V zcO9}X!)?0%14sMv}XdaxHy>_}3osnQ6P9x$Ny~REz@d6VaV}iA{IYCUy zR_v!Tl{3um&7Wa}Dd(m}?l?{|aUR|fnX>IK4n%_!9sZ%lY&?R(+()PT$IWi{8}QP0 z7VcTfW9R`>^%)a94D64L`q#?2KSzuQ{p+uVfB2&o10MJHV^X#KbK#8jj`?$%0eTv_ zDuKHVzu3R({80Ws^w20COyYhrRwP6Oa;9cz_=JeYM(-YMH}!m07$W9GD)g6_sRY}q zO*nF+(6>Uj>Sv62K9jaiF&I-Ntd7O&*jsJ#riQsKB>zC4wy2#Nm+jwHDn0rMG@V>R zW0H1bLqJvNIw^J^0fAWp{VXz)B5T&ohp~qj*z;3&ouM`BX?m#nBnd|X)oFa@X(XXE zyXidO80=VZA^`6;CyMMC06n*hJn1(&UdJgy;^x5>30+VDJCe9PY?n_*1x)5q$k4?5 z9xak@A%o56;#~u>j+)lFl$#0cp3{F~yJti)j+DbV$D#3xgDbCX)nU{a!WwUq#YOuV zY?@ue(5o>z*^~|s_o_oQK{Orr$E}7++FT9AmB(?;;lz67ba#LaKEFba#ZfBlH~f$z z4wzWI;K4|^!j8N{(d&bE_-db%+JpvJunpOBlBRDU`yk4;Bg!p2o2P|Yx; z>|Rs2Bb9fgwZlrCblrBo{MIQ)^JrT!FU&d`-+!C+jpz(hCr2vhNDF>I!do1>&lD7& zT|g3I$WqwCr_F+0w^#&9^{A{Rqf-m5G^x$PQ6=8zMDuxg5`7o`f2)zNCbMp_)tcE( zgEPLp*aguO9#cagSQT&GK$JlTLpOxj{cMjZ@I#r97!*O7P*^2h3vFgmo&a6P^8)C1 zvX#M=>W|od7kLzF6W~w3mh^aNccErBh%O+|6O{)~tyzkzFp$I=Q;JvjC4ylbOx)|s zubGJ{-N7j9>G>6hgAtXbe^lmuWgv{m8P-af(1 zgdbHhARzEY+}yN*vt}OUv!~L6v9l8GB{xfg^WqN{w-u6w+ISRi2K6&r*ey1!Lj+)5sDzFC`$Bh`0IojN)EnM%ig>ov=_R;G`KplZUT`$+aJV7=h5&!$jjT zv_f-LN>`!tcbg%|f20Ub$&Pbp5^>q=YPDq^AAM_ z`wa~+2LTt}xyhZ!v!?n92fx%*VG0?y5_bi3P2E7D1#GwQ;==7g$EY4XtiNLPlnY|N zGYBczVqpc-MlT?*)f%cBKPCNR> zc}rpsn%ZK9$bMlnhQCGZ_aRig`^{TDTJd=7HFJPx8bQ;Om~2Lo&7f5L{g;!CmCZGM z|HWieVx_sJuNb0%t?J(`Y;ahx*h5B8rHi^@1nfcmSrcH-i}&~lEAZhEZ5M>Dhw;NH zJrv1E0k5S|0nose6KBB%k0I-!3t$G?7m_ zVziwtH{Loi)Zz$iVYC(TDYHtA)~)1ZwCNGZa*z#vNR~vsMaI}8rsl#dZ^;@aogfDF zMAix7A$;v$Pw2WgDYi7dA+De5CpzM}^sJbpUNn1j8%6iLiBk;komdqIaAWZk-W|E@HL~X#gd8 zGWxIENztJr#i<0Rw717=m7~*G&wB-zh=|>L;PYhxy#d#dw8U-GSRloY?&NOUnPB8( z62#3NMzZ|K7{+^=F4ns#xH6NSawj9RS`&bIA)gmC9>IY!2Vl%kr!im3XJ^xdF)L&c z(guVW@E1QdNHa#>TGs#mxTA7+yv1TjAf(-?r0;T#ARju9##Bauc}yuCkNerGI`ElS z&{1qvdOoW^s;Z(WIG*2F5+86pzM=%ddt-8Q-;s;9vz!=m0QPKWI5Eft#HB03Ukg=l ziEi9=1l#T^1U}dbB80}3-EhaiUh;(6E8sl6N7>`^w+V=P}%T9L&5-N&OHac;zhj3J= zJAMd!X~S{{S1o^I2n0%4O=0VbV1zy*w;A>m<_JvMh7=8-cto^>6ociihmsq`a-*%R z%9wF`6B5l+V=9o#RxfZofjj6fMM;yq2VyFW-=IaPGLj>U#A55&D(jtW(=vQ&n{j6? z68J1S92mHkeT|&JC&=s|%w0%Hc|qmn=%`lYuH<&lf{}tkQ>e+xBm=Qk#bbG;LR1am zvliaQ+1DAUp@yURtSKN%bWNxn?k9i`%&yKT$+jD%e~)EUR3!V^%IY^L5$ygq%g_}C zdo*HZe?D^`asYhpO;FpkkA-bla`w!t16QGq{GR!TmTm3-h)N_CEp1Iy;zV*${m_v1&@ zEb@=3Y>Z(oU`s*6C#VlrB;Z#(d`AH6Bi3EnLEazHQqyjEy|k1@k*T2jC%{SuD?8{0 zp+<(f34H{ptz?s}stLb_suu_Uej5J$YrKsj`Iu6w?#w0@NV$Jg+U6gmCIinb#qI6p zH?d7}6@Nv57mo0G4c!NjW)E5gH)4(s2wDM%yDt2e)+qdzRuR(NpDBi(CPVL#a7Zc% zmktO?X2M1I;h^d6ETn9uPU2Y|V=47K7gK6841@}GQS?nQs4wX+$IBdi>`8q5bI%8v z9h8T`?w$&CuE=?s({j&wf<*ih$LR#|qFjcbl1t(4u>~Fv5#629KeB`N{{U;aI1)9uWc@sWncx7~23`9(w2#Xm04RH5Hgg92iOk|GK4R}2zBl;{SLjQm$4@~jnKU}@-zg#Yo{&H>)Hd@MFx;iW$Ga50%;O{Z9 z>-ys_pZOzfVdmRJD`SFq#(L(qwKCe=2eqxaj8W$p6LA>x1poe#rS0+SNe=vAt)&!; zG1lT+C%D62r>MJm94#cl4Es<)o{9S(>;Tz!1@c%%l(6tn7C@jHjkjF%&eW8MOJs9A ze$bPOW2M*eq45YFdqc{u+XSO(eh+2$q6JA6yZlWNg6)^Po(4e8Ed|?tf0hYCHM%ci zKlSBCjrj=WS8?pRMNqE(m<@;TE|-UH_Fu-{NM3;}FYKh~nbCcEKvD^20qWT6{s|t+ zt#aA3r%(&hZ_<;&6O@jAqCNq|FVbwnLFAT3)mHk_leH}*g{JYkX_-JIKN{l$CR&U| z;`c~IoE#69>`@fy^=A&PoX?XYn%?V~v9m93|M0{p3?+(@=5J|ZsY2%>EA7%DxotRI ziFi7qa=~)YcBV5K#t&6vXF<{H1hRD(w%9I?U4B&`V^|Gekuh@<>Ll{psDu*TJJhLb zJwrg(cYsy>Fy@IV?q~CAcdpT?R>J~PQlA9jcU#D_4)DyyMh(iY{?@dp8xBE4e7<`S zTuW)3(%7%tbB87K*X=Fkw`r-rg1=rbYOFvb9nw672apKI&akA@_^T0-DAlXY zkPVIouUqXp3#Hj71bFCGhh`D{`1Ns=AI0~&zo(h>B#u|`yXID*>#0Htx|FwO*f(k9 zH@>nc%?#Da*vrYEkt%Dyx1dUjn=!qTqV)mASzlG_CD7Z~rKCsA=boeLIaR%ApB_fH zM($pt_a!|W8(<+tYAZJ_7?j~*RWben#T%QeGrUKA*pKvZXyJ9=B)k4lmfzWeCdWJL zd}JjzRkph!D0+ds9p@V6`Uw7hy|of+_(+KT5HmPWJ6O z4~C^+yB)~BEA765Yi^ZahXHhI`k>+#J-90sa`)ta=htSd4LPz0Z<~P$_~ioj#WboZ zI3_o5dp_5czAR~k0@r+5B3Zs^GlIkORa^)*S;h-4CzcuHn|XV>gyZ#xenEsD6}fS5 zw@W#@c%4>-d?$L}mxr-aJ85p{(!F(9a(^>P`)H~(bZ2 z6Dseb!K?~SDXhEn`>PYdl8zL%Y&s>Sy77XkgI+dDXLhz2xDm4WSQvlVhrpkaWes$A zJ$)gxNe@rL={Gx`rDu>Au;$CwJJSrnbGu7)ZqWEfsZ5CUo<=6&Wiap+%`5N5OMfpkbRCTO z1XE>8u=|kvM`4#ug`U=InBkk=#4>FBM@Y?_iL8Fo;&Z1uf8n(Ihe|_=BSVw1XOX8p z#`?k}_)K3dtb0saUEHSr>-fY#j(RNj-QpI0+A<+`+n&d?+Wq6CJFcVJ!->+ty3dH7Ok$v!2FZ`6E>O_zuCssePX~cnk zgEFXPclW_a`4YpG>R3LG@rDx#T=rueZacb%#@_NzNYrN9*y3g5}(vNVcsi!1TWlD$?1;X8Fnwqx^_1=&X+Nd`4M#rFg>g0h60JE_0eucr`YM9>SH0NK zPam-lcuRDXk%ssvY7QXY87?N^&|%@kgru7wNQ;j~_U{0u)zFkA)l*Ty9@U&A?Ya;K zB0~YZWxgNf#em50I?qY_kG8Qm&uK6pR6qaJWqE2jg7(|=K>9=Uxbdi>_o$yujiPZj z4c}r{FLGkgGsDQSnfS|@{P6UYMjKos23(s(pqV{#HlVjmeLrE>8HPzKzD?CSH%j}z zT|;2Zv8e>M9D7vjd|kSHEJ=I3jnx0Ukxg%6A5h!%UuEg_?~K|P`ba;0*Hb&Hj}&

DZ5*I`?i_)2y9A+(Q6?JF+c9=oRb*JP?uJkiIzE5n1GZ zd-fRD`c-sZ+&<$62m2UZ^dJo1ZVz29xHRyg_?&}mKJn|*1itz8UGic2`4Gx>+4=r( zW%K#f+QT16(HDLopBu^osN6B~d5#nYUhG8N*%u$B+}^qvqoooZzVsL+249{})bp1k zK;^!Bxd}%?Tdo}S)4ukBlzp8H=FYtFgH|iQpF6Okw;!2Gt6V{BPi0eG`#_p>uQ!Rja_<18O}RgU(*AzGCozA%-=9E>s?C&ES~Z8L zXm58W>77ZS$h#DUef1IUNdoH?aEXDOVH`IW+?=;Lg|1ZL7%evymU6aexu+<-tsnO) zqy0dG-`1a-NN@#UxQICxUoME_n&Vak-|j4HTHOlc&qm4;P9I~NLbd8 zThdl>XeXVPcv?Q_3v~P%aXTrmU5&VCQn}fP8xi(Aen=xE$uTa?rB(V5^Bg6g6vz#r zoL&p$R!@z?%!iQIDAx0|sJ6>`8q3m&gw5{?ct+v2{)lrOUtfF{wNQ`g(%ZOawMFNH z8&r9kKbS5)lWz**^c=bNm&RNaxwfY<_eu!4R#T%RV2gua^LcIv`Sk^l7J*8OU`1tU zuu@~5V6K11cQ7zEXETR3d{@gMW`sFZDjli{%bD7Q8$tPf(S-XhoGIai1*PbHjJ`Y> zkw(8~MT1J!%lWV=*9)C6=Xg`@adINO8FzzVi{{)w;*V{PW`Vto=G<)RJ{OvE(a)+WoWhTeRT(X$_AESKaRk0- zbPY{ob?h3ty#Dh!n_F`oP;vR+w&uR09+CfPTMPiInacKDGQnYbZZ$!dp8H16ni5Z~ zZ(#~-kGU_+MK=(>SrFP}!uiJX2{ef^!VgyICP|m^#t`+HI zN1$r~=0|We-{piya?cZdCz9((aCap4M0_$f!k&S-PkUm#dyV2yeJc|LthKKK7~ORv%T|U&X=9Ikor3)X7oFev%eG9yU}Uvzg0JK0y=X>D%0@J z+(Lp|J9F1suzD!<7JHhjwJ)Id(NaZSxHi;cr@L^&wW%NG_vp&4@}qwHSr2XkCFr8K znbfc6M{!2(`JB(9xF?8R6@_86?!){MJ-Pmz)^B1?b}w#7^GVo6c<}hhL%(y?hdu}Y z@^B9B4knX|AzGQxUWryroK@+*G2kEmFz3Y>u50~gs8)VBn4cGeVbtiU&RBzO=gQ>~ z2lMrPxn-QiysgS>oMfD6e9|~!!jluHPQ%t9SU7ax0K%y=uhCId+p`P={lY-VIR^Uu z%7EcK15p60sx;& zjqk;^!Tkz;1QkEXc^(vXe8C?1Z4M3|BEQSY|LPI$Q2o4k?jqN8{^I$j^x67FGt$!L z%+?P}H5&5I7&)td>q+C1Mo!VE&v|P8Qq!Dtq|Ki5?EG1C^z&yM^0y7e($Yv(T{{1{ zIXS-$<8F7DG;T!Vm?`?1OU#RB>rG4aW5Kk{!5Q@t+h&+?@!^TbNRzZatpa0zM{=svcxoh@l*O`^G)-X zm`%PSc7ajz(mCl1XDpuGA^+=C?y5E?H-&4~JWgNZ4^;`dlmGT;PRr#?e1Z!O^0m^O zMN86GrL^>5c>a>8MT_Py ziPO)Tv3T(kYK&P+X3x>jm^Ew8(xvch>Eis2W4UHrCtr)tn5{Q2Ry)K~X)_Epl}A+m z-^OuGx%SjKYFXF&s)an&FIkZP^>}W&Uqh18>zP`YN!({#vs#TXf9b3xi|5XND*w=A z?pK{bZA#RTudqvIn-`*Gs5jNPFmhxHSn0Lui`p;eA2YeCC{`i9cB|cAdgx~^Tu|+z z(sKEq&EiIBb7l~B8zTaG~y-Ws{+#ynxeN4O0;5ros=e)jvYu_Y3Ol7NZbAHhRu2;b0@oSV^4<{?>niLf({XugG`wa4?!$bwf zqzq5MZ=q^-OjhV}EVPuKGdCyB#C2*At=iM(WcJ@%~#w50}_SA04OE%*3>R4XxwX1n)gW`{dtxN-@}Vo&uMwQsC<Xtng6)zS-~vc|CR&S|7RJi`J(^-w;cFZZGsTB1~sWEBZo{FksSQ8 z(uDCUEZd;aJw5`Y@wbx@{G0RfgD|fSzE7@~Du4TPMQ_~S{}}eJgHQ65zqjYV5TNw% zf0G{jZ_?{>418t${|fiGY^%Rp(8u65OrrLKhkGCxGM*X|3&p@Uy4(BeZr zQ4!`{Q)sfUXN`B8*r8|`>*A*+Ug95E%T zV!Vi>M1o)78kOMHISTBH+jXMr&s88jHXyneZg>fra6?OQoSJbTb;qI6DE|ZH=%19T zPgCJ^720IRw|IHCB2b|)y#K%8BP?GFK2TCKw%6gv&nkbuS`$lg!b&AhJ+C;fZpAdd z^iyh$SnEh1j1yqeFQaq|kcSP-Q#Wr;T*;E)!|Ae`f#$*=r+=k3WpEwoMQXYp>6HKP zGFXNCa^gqVVW5-R7vk$kKd5GqT1Wa?)qdIqb^U+1gu%GxAP1~<82IH$C4EyJ>7U`u zoAfu=k=|9+cV1A(|HBTfRD1K`It*M@4V2Z9-U83)$bs@Y(vz1e=^BgL`5t!Qw%W$Q z52Zh&4%bQQ@YC0k-WiALCx2m1kvaSw+7`2Tz*O8v6PArG(NFS%p1Jf`4 zu>-s^VOhLsbqL^B23ZN#)sb%UjgmUj``}`dYGC^1KX$;u>{~Dn>kzQcP}}J09~&61 zj$?ft={?jYm|jQvUFt)~-@B;}ffnkVUS3D~8Fem?FaKi)H>#a@dL8K!hG8e<@3qz; z@K@Eqy7HW}8C)le^M;a9)8GCa{9o!@y#GrB|4V(e)t@c-e?<@Z|9^MDSK|aJf3I8Z z1HOc4wJ+2Kr&TI_<&+9fJgST;AFNBy$FHTY#EL$}*7X%bKEb+REdy*m@ag+tU3xx# zjlQ~8_!NE6Cs-G(F`(3hPtOPI()01F^)3JVD*Rv7fTgZBY<5Q}*`I|)s{X%$7H?ew zyjnAV7XIV5QhaI{*2 z|4T^ud+W*|)~i(EQne&L+O1VeGFMIa!BUm>!9=yhx(14TfJ#UED>MqOjDHJP&3K?U zcW$Wg*WXnf`JckQeu{pcT7cizG``QK8u)#J!ndlP8r2f_RSm9I;StsG9cmjtY-hHr zzeO#1sH!(k?OXjVD)O0{!NUe_sD0qi!pUmQ)dekeCHVMns%`3n^VE_U>S!{9+SYgq z_dv})t@0l#pvAjET|D+Zq~H^L^}f>fbwQuppM#dV+umm|qDpb#Vdz$C!Y5Z3yreeq ziii5h*HnYZ|Nm8XKG0EBcOJhlAO-~;5Sj!;2LdcuAQM0gATY2JsI53~Y>W735|bDP zGGs_5G1QhfD2na+XHb@B7i>pY)FQS?pe87G$A6%%Vv5$ayIRKoJ#~u-aJFmzZ1(rN zzwa+i-qF+3droraeSUxL-}l~~ci+A9rJe7~9R4jn7)^5$@hjN{TEhkQq_)Z)@g}52ol%KY6M+9*YakTe$h0h2xosPoo!? zm7$DyakA1@L< zAuRC-U)%-x3UF6J4ubrD6vLI5NgVSXzl7sI`MB!;o$=gWH5i2i7k`Q4?cjS6xAojH zbZ6Qr%x}f~_x7G2x_R=Q8xbGy+EYV|6YQIUDL$Z3P73@2)%o_wnA9FQ#k&ya_R8_9 ztA4|~4_RHS8W4Z9#(`G_@}epmf~3KE<9UG*hjhC*F#9D=bx7RX*7N8&k7S$xEvo3q zYjx=Ty{!GtN?+L60fjO2=NNjaHzy6iOTIV+>Cf_RZ%v!C%<1+XYI90L-J-=W0duIN zKZo)b-YmS#Qe)y2=aHnsbK0H4LRh7c=DmH=(srkyNZ)@bhw)PoXT3@m>+2B35Wf#` z*xOEGC*l31-3iS+iR@FN9C+m$XFqr{&)yfbSiD%+^1MOb+571Y%+zLY-hO9UD6iP+ zX~FKadU@u46l}TmYRpdJT(EAhZ9f$ZRw?v=XCO^^@9lRA7V8@tn_svC@ARkK|f~#5I zn$en@LELtHTn!S?Uko1F=H1)rEGul4nN|w);83Up=yans7q<9@JjbJ{qW`j)x`YBpu$>9X_!`{mWo#mzU zOO#x3TEdBR72{@k+s9{v=hk~mo^nc@^Z^>S1s~;OSg9qvcrnVKl z5M0Z24|ve0zZ-lCxEFldnOc+*r+Tu#3?-BBZg|=$si6G{IWL;WQ&vosbl1Q>;n4Ng zo~Cz8J(eKvBf)hR{>RAVSn!nfI0*soq{j*9b+lJG?Rmgl_0Og2f^BIGE%bygBHq9I z!M&M4oEtg;KQ=!uNlPI}_7@NjgY#w!oG`{AW)*lEoDOZM1;5|No53^S+LT*}(}2w} z!58ENqK`J*3xkJYpnYpM_+vi)2srQe!x>dSZQxzB#RzjcwBcFsmv)ET0fJ)?yzMji z4fu25+64a%egIrE`X}&CA3vM68sjE7)W^C0DU7v(L1wkD(@law76w{%bHI7?6fT+a z#o+Be{tfW`KK^YN0^W?JzoQ$#AM~|Z9Q;|Ielzhe>U7_QK5sU`{;6VYxC7kHhd@iP zLk+TGpm&ap@~(k=pXD@W$jWJ{`n)YXS2gc{zzHiqLY(_t#mlzOt?RXW(o>#3f*|;+ zbjE9tqtC%puL-{k{9GC$u%p21!Y89-(_S1Lwpg>GyRDeUVE0L_1+CDST~_Swv0|#! zE)i>Hex_ch4b1#6z{|i@{}u2scoxb0ia6I@7L(yKz&{r5nhXSbO*?HJ<(cdbv@&j9 z4lSl;M>=gk369PK*XjD}z$bv)X?m&`+M~~g5V)~ZyKZkUZR=vgQV0T=^HshEJP58O zt^*JG_-61hI33z>H+Te`c21#-BKNb*1Ul;-w3K$$ z0N2Ux3&FR5>m;|WQj*;Tu9MrhL;r{1I;ov+>z{=G3V}{$tD{zMoy6AO`xJN0Oq9i%jYq0ovS_%*Mj3N1O{`&LU*9g(qKJhs?t-`f_v?+(2BmUFG>H62(dnYGFVI%~Rkpmr_uLj`XM#7W9 zby<^mp;0EdE^|_TDY!0sB2M*BVHpIv4$4CMw+dX>MJc}yT-Qk{Ukk45rj$2=>pCjs zo6n*2xvF(xl?uKOgT94U5bOZgg;%Pd0oR3C%J+fm!Yt*T;G=b6mI_{gKv!-l?*cC_ zAJ|vk0oR3Hs{dPXUFfC!G`KGOlKN*Ued*(x(3N0nFbrH*hAA%u*Og+*CxYwBG36m} zT}cM+(q4&@ur4iAgXQ45)J*wR;JWloc?GyGMN?h}u1nLDw^;5vM%SvTU@HuC?V9p5 zxGrT=z8hSZwkdxMT$j2jKLBo*zVZ2|f-D5O7EbxA;JP+W`P<;SR!;ec;JS8Bc{jMO zrDJZ}|IZNU;yN|Rr>}wBCv>r$@=@Tr_)d8cTo>ahp9Zdr^OTnbD1A;qSL> zUSADGz;!X7>R$^U%##Zj4QyF|* zw}H0~7Csk#?gj4#p9=mEc<>C-AFk)$0SH16jD^9!fk)H;{55deOx#}EC!qw#!7ElE z0qCD3K7wxG!H8TxLg4No`i(1Yg7g?MSOLLTz)g{G9m$LXPl4;mXCiSSN{xn7E3_2X?_B3W{Z9q1}yF1k*UHv;r|eL;0odMm~;ET2tfjE_8AP`0dK{F z>hwer^nmA@#nE<@Y!HoFxhX?03LgbN3_N{U`12^(7~(D)bng_waeDm3VHym&Qo@hG z(OmF~+l8-zemQvdF5x;-S`D5<&1sWWtN%ib|EEa0W*r1UbjA|!jo=Y*9VNGD0^rM_ ze=B$hO?d_Qec&nZ;TV6I$011f3B=+#;@qnPD+iu7Z$Q6woRs)Y)X)d&=R)CH;?KY{ z;13`}j(eW8S>PfuxDbMo;NhTfz2Hm$uNW`95&AR1bKqOR!{CVtqOYBC6}THJ7QtZ{ ztcM_SiEu5!t>9*&aQ<;D4m-g^lZ0O^*gOWF0M~*Y1WyZhd#hm3r3p+HNA=*pRY%}B z3Cv%>GvKArFQnlzcj_E?6?*$L@DOTRo;T3>w-|!1X_D~$a1;TLz+e_iTmv5V)lf5d z`m5sSP58MTJOTf!!FMv}@qc2v7*Id6;iqa)B0PzNJHfj{!u6}x5pYu~oPM0hhGXEt zGU2ZYHlL^;;ynK7Rl%Rt(JV3e0StzWmMTqye*=6Bcn&-WJ_Ec0HPjBi2s}Gi{L}Xi zzWy0=6$FuaBH+D$IjjQ@%@^*WhPHrrEf7vWU|_=z@NRJJypMpJg`%H?{*$9AeeNtN z2&UqSb_52kOU1z8A=KN-FBd)<89G6nuM;`n&FoCdnZ15srcyc`y*xmi(~Y>e83fQ~ zGa*Q|NCHnFr}M!>o4}Fq*T8c(3#XI9hBe^kyTVU`$CQ6hxU1)7GXy~hcHwTejra&N z*yswV7C71g{oF0$NUM4ec<5H)8&R-!@Blc5Xs&q~-W6Gu9BdmlWDo0BH| zKj0Z;Xf`r>CO!DGH#u;A=Ym6l2ea*gPzLG~p)obC@`ve_DcDAjq_d!5lcck2p8MFg!ok2fvSi2Ye6Q4=T^e^KjKa z3O=eIx98uxB(P@z=ZJ!ihJUBLQ#c*kFqFEn)qh>McDk<+=ZvP2fC-9zG4TM^M9BA` z;}W|6OBEr3M$tE`;K;y{Ug@g9^L@OTxNYNKN`|zrYy}U#C|qm$pOrt1@ju<3ZP)`r z7;Ux(yd6AupEx=Pz4}$~?j6GUhtN6Xl*7**g3bSehaMOG71aN17+xeL$o)(N{HhCw zV({+0!e3~O{T{f1zShL=H31~7XU+eC zXTY`d4x{ss`%1uGSLpmBJsXN32;qXGd@^{%cOkk2yaHT1>DR#%;1kgsuLd_CNfV6b z8shVBEd;@jMWAo0CBai43fJCy2YBYcgj1ig;ePNexOVEj;5l$TT@Mkr<9~y%T6Ao~ z>oCx@pX0%ggXl)D@?xKd;j;lKTfn;<#{0s-ret_}+We@}*==PO(j&eL& z)#3AH;Gt8JKzykle@hM>Zi@J{fAkM9Lf`*>CpKvU|2h@;>c zJSCrk487|@&;^0swSEsC=#i4?kn1nt72rAxHk>+*t!i-n=5zsg7CeH4OTgX0Nl9Q0 z1dAX@fL{&10=x@+HFyPh+V^z3mUw{f1tyMJN$9sIpKf`_kzYHH4=JpRZD?wV-Wc0Z z9hL3FqH()@n71+K6uWj8v?%QXHlxbaavZgL)Zw%2oEGJM>_Bu=HC2`^-wYcz)KpeS zW#ck?ys@dKsU}MMs>N>H1ogVg#$>dznYM6)h1*z@G_qNm>?X#!Xr?X2$WhaVBn}!m zj@8=~HmN%8o@et+abKSKbG))@Mu-gbb7{ZNGy8p>#h+am%ob+E$uPAzx^{ri) z_s^qT$v%<_rEAqvCrcz78kN#Ufzf1rbZvcO-}xenH#Ef)QQA4NS~cO6I!<$JKJRg8 zPpqijKF@G|rJ2Q=djm?&9kYKXJ0AVa2@RrG!v}lsoTMF2XD#>sc+x2ufv;hvLPjAi z-}2_2a!Q7_V)7*GHJoxT8Jd&B{G8W*%2|}36Xln^o_n0J-l)^gxY0miTu%8Ge}?0g hpLQ-9S{RTa)B;v7@Y1K9;91`UyX}1MJExrs{vY#DHV6O! delta 36185 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 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 diff --git a/SYSTEM/system/deshab64/cmd.elf b/SYSTEM/system/deshab64/cmd.elf index 97689a0c442d4409b3ef38846930113a07729102..56f15e8741fb720aed7b57a6efa283d9c58093e4 100644 GIT binary patch literal 511584 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

y}7 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 z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf 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 diff --git a/SYSTEM/system/deshab64/desktop.elf b/SYSTEM/system/deshab64/desktop.elf index 61bf351c0cb03f625f9a707a916213ba2a7516d9..c72a862e61a5a4b607a6b34ea11c653aa2f7739f 100644 GIT binary patch delta 44826 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 yKYCOLCE%mFN-z;Yz|L5Z~=RC_f=Q+WZdP~# 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 z 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 delta 17902 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 y8&pNlKs&#$)xV|6@%B#q{Th%P3fD@F!M;B diff --git a/SYSTEM/system/user/use/FirstInit.elf b/SYSTEM/system/user/use/FirstInit.elf index 888cbba4c2de77907e0f08fc36c3e72abd0039b9..9cb972d1b209b247628d6c9694e5b007c59b1ff8 100644 GIT binary patch delta 610 zcmX@{l=H+>&JAq}?7ch<9=#?Wllv8v*mVE=|DTpNd8>kr;{6i>4E*g+fB*l_FYm&@ z@Oi@j)_}kN|968)+Xp8ECJQN=K{VTVOb%33V%`VTS)ynQ)mi!&JAq}oSnx!4nAY`U_3UtQA3GE`_KRXlea3^D9(>EV&HFo`uqQXet8!L zhR+lJw+8(E|Gyhd+AfGPnk=Mf#@Tt?(9b#%bRHa<3^w