CPU 指令集模拟——从 386 到 x86-64 每代覆盖哪些特性
Bochs 的 CPU 模型设计哲学
Bochs 的 CPU 不是一个"黑盒"——它内部有完整的指令解码器、微指令执行器、寄存器文件、特权级切换逻辑、TLB、预取队列。这种细粒度建模带来了巨大的开销,但换来了:
- 可以对任意一条指令下断点(不是只可以对 PC 下断点)
- 可以观察每一次特权级切换(中断、异常、CALL gate、IRET)
- 可以精确模拟页表遍历的每一步(对内核开发者调试分页极其有用)
- 可以模拟指令延迟(某些边界 bug 只在特定时序下触发)
指令集覆盖情况
Bochs 模拟的指令集扩展按 CPU model 自动选择启用。
基础集(所有 model 都有)
- x86 基础指令(386):MOV、ADD、SUB、AND、OR、XOR、INC、DEC、CMP、TEST、JMP、Jcc、CALL、RET、PUSH、POP、LEA、MOVZX/MOVSX、IMUL、IDIV、MUL、DIV(所有 32 位变体)
- FPU 基础指令(x87):FLD、FST、FADD、FMUL、FSUB、FDIV、FCOM、FSQRT 等全套
- 段寄存器操作:MOV AX, FS / GS、LFS / LGS / CALL gate / IRET / IRETD / IRETQ
- 特权级指令:HLT、WAIT / FWAIT、UD2、NOP(多字节变体)
MMX(Pentium 及以上)
8 个 MMX 寄存器(mm0-mm7,别名 FPU 栈),支持:
- 整数向量运算(PADD、PSUB、PMUL、PAND、POR、PXOR、PSLL、PSRL、PSRA、PACK、UNPACK)
- 64 位 SIMD
SSE(Pentium III 及以上)
8 个 XMM 寄存器(xmm0-xmm7):
- 浮点/整数向量运算
- 支持 128 位 SIMD(4×float 或 2×double)
SSE2(Pentium 4 及以上)
在 SSE 基础上扩展:
- 64 位整数 SIMD
- 128 位双精度 SIMD(2×double)
- 缓存优化指令(LFENCE、MFENCE、SFENCE、CLFLUSH)
SSE3 / SSSE3 / SSE4.x / AVX / AVX2 / AVX-512
后续版本逐步支持,Bochs 会根据 model 自动启用。AVX 引入 16 个 YMM 寄存器(256 位),AVX-512 引入 32 个 ZMM 寄存器(512 位)。
x86-64(AMD64 / EM64T)
32 位模式基础上的扩展,核心变化:
- 通用寄存器从 8 个变成 16 个(RAX/RBX/RCX/RDX/RSI/RDI/RBP/RSP + R8-R15)
- 寻址从 32 位变 64 位
- 段寄存器 CS/DS/ES/SS 在 64 位模式下被忽略(GS/FS 例外,内核用来放 per-CPU 数据)
- 引入
REX前缀和新的movsxd指令 - 新的 syscall/sysret 快速系统调用路径
注意:x86-64 支持需要编译时 --enable-x86-64 开关,bochsrc 里 CPU model 选 pentium4 或更高就会打开。
特权级模型
Bochs 正确实现了 x86 的四个特权级(CPL 0 ~ 3):
| CPL | 典型使用 | 特权 |
|---|---|---|
| 0 | 内核(Ring 0) | 全部指令、全部 I/O、全部寄存器 |
| 1/2 | 旧式 OS 使用 | 部分特权指令 |
| 3 | 用户态(Ring 3) | 普通指令,访问受限 |
特权级切换通过:
- 中断/异常:硬件自动切换(栈切换 + CPL=0)
- CALL gate / JMP gate:显式调用门
- INT N / INTO / BOUND:软件中断
- SYSENTER/SYSEXIT / SYSCALL/SYSRET:快速系统调用路径
Bochs 调试器可以观察到每一次特权级切换——在 bochsdbg 里设置 watchpoint 跟踪 CR3 变化就能跟踪进程/内核切换。
编译时未实现指令
某些非常新的指令集扩展(如 AMX、AVX-512 VPTERNLOG)在 Bochs 里可能是 stub——遇到时会触发 #UD(invalid opcode)异常。OS 开发时如果内核触发了这个异常,检查:
- bochsrc 里的 CPU model 是不是太旧了?(换成
alderlake或更新) - 编译时有没有开对应的扩展?(如
--enable-avx) - Bochs 版本是不是太旧了?(检查 Bochs 3.x 的 change log)
可以在 bochsrc 里加 cpu: ... exception_on_unimplemented=1 让 Bochs 遇到未实现指令时抛出可诊断异常而不是静默跳过。
和真实硬件的差异
Bochs 是模拟,不是完美复刻。有几个已知的行为差异:
- 指令延迟:Bochs 默认不模拟指令执行的真实时钟周期(模拟精度到指令级,不到时钟周期级)。某些对延迟敏感的老软件(如精确游戏计时)可能行为不同
- TLB 行为:Bochs 的 TLB 是一个简化模型,不像真实 CPU 那样有多层 TLB 和预取
- 微架构细节:分支预测器、乱序执行、加载/存储队列这些 x86 现代微架构特性 Bochs 不模拟(Bochs 看到的是"架构状态",不是微架构状态)
- 性能监控计数器(PMC):Bochs 不实现真实 PMC,perf_events 会报空
对 OS 内核开发来说这些差异不重要——内核不依赖指令延迟,只依赖架构行为。对逆向工程和精确性能分析来说可能有影响。