设置PE位并执行mov cr0,ax开启保护模式时Qemu意外退出排查
x86保护模式切换代码在Qemu与Bochs中的差异问题
汇编代码片段
GDT与全局定义部分
; ---------- GDT ---------- GDT_BASE: dd 0x00000000 dd 0x00000000 CODE_DESC: dd 0x0000ffff dd DESK_CODE_HIGH4 DATA_STACK_DECK: dd 0x0000ffff dd DESK_DATA_HIGH4 VIDEO_DESC: dd 0x80000007 dd DESK_VIDEO_HIGH4 GDT_SIZE equ $ - GDT_BASE GDT_LIMIT equ GDT_SIZE - 1 times 60 dq 0 ; ; ---------- selector ---------- SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0 SELECTOR_DATA equ (0x0002 << 3) + TI_GDT + RPL0 SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0 total_mem_bytes dd 0 ; 4 gdt_ptr: ; 6 dw GDT_LIMIT dd GDT_BASE ; 4 + 6 + 232 + 2 + 12 = 256 bytes ards_buf times 232 db 0 ards_nr dw 0 loader_msg db "REAL LOADER." msg_length equ $ - loader_msg
模式切换逻辑部分
loader_start: ; ------ open A20 ------ in al, 0x92 or al, 0x02 ; 0000_0010B out 0x92, al ; ------ load GDT ------ lgdt [gdt_ptr] mov eax, cr0 or eax, 0x1 jmp $ ; 移除该行后Qemu会意外退出 mov cr0, eax jmp $
问题分析与差异说明
你的代码存在核心逻辑错误,这是导致Qemu异常退出的根本原因,Bochs的宽松兼容性掩盖了这个问题:
- 保护模式切换的硬性要求
x86架构规定,将CR0寄存器的PE位(第0位)置1进入保护模式后,必须立即执行远跳转指令。这一步的作用是清空CPU指令流水线(流水线中还残留实模式预取的指令),同时强制加载GDT中定义的代码段描述符到CS寄存器的缓存区。
你的代码在移除中间的jmp $后,执行流程变成:修改eax→设置cr0→死循环。此时CPU进入保护模式,但CS寄存器仍然是实模式下的选择子(值为0x0000,对应GDT的空描述符),后续执行jmp $时,会因为CS指向无效的代码段触发#GP(一般保护异常),Qemu没有异常处理机制就直接退出。
- Bochs与Qemu的行为差异
- Bochs对实模式到保护模式的切换做了兼容性宽松处理,允许PE位置位后不立即执行远跳转,仍能执行后续实模式指令(比如死循环)而不触发异常。
- Qemu(尤其是系统级的
qemu-system-i386,你使用的qemu-i386是用户态模拟器,本身就不适合系统级操作)更贴近真实硬件行为,严格按照保护模式规则检查段寄存器有效性,因此会触发异常导致退出。
修复方案
设置CR0的PE位后,必须添加远跳转指令切换到GDT中定义的代码段,示例如下:
mov eax, cr0 or eax, 0x1 mov cr0, eax ; 远跳转到保护模式代码,刷新流水线并加载CS描述符 jmp SELECTOR_CODE:protect_mode_entry protect_mode_entry: ; 保护模式下初始化数据段等寄存器 mov ax, SELECTOR_DATA mov ds, ax mov es, ax mov ss, ax ; ... 后续业务逻辑 jmp $
内容的提问来源于stack exchange,提问作者handrix Richard
相关产品推荐
相关产品推荐

