为何IA-32保护模式下Pagefault Interrupt不能使用RET指令?
问题
我正在学习IA-32保护模式下的IDT表,已创建IDT描述符并触发Pagefault Interrupt(0xe),程序运行正常。但在int 0xe的处理代码中添加RET指令后,VMware虚拟机出现“VMware Workstation unrecoverable error: (vcpu-0)”错误。Pagefault等价于int 0xe软中断,单独调用int 0xe无异常,为何无法用RET指令返回至原执行代码?
相关代码
org 0x7c00 flatcode equ 0x0008 flatdata equ 0x0010 flatstack equ 0x0010 section .text [bits 16] bsp16: cli xor ax,ax mov ds,ax lgdt [GDT_DESC] lidt [IDT_DESC] mov ax,0 mov ds,ax mov es,ax mov si,pagedir mov di,00001000h mov cx,pagedir_end - pagedir cld rep movsb mov ax,0 mov ds,ax mov es,ax mov si,page_l2_0 mov di,00002000h mov cx,page_l2_end - page_l2_0 cld rep movsb mov ax,0 mov ds,ax mov es,ax mov si,int0 ;transport int 0 mov di,0000a000h mov cx,int0_end - int0 cld rep movsb mov ax,0 mov ds,ax mov es,ax mov si,int0xe ;transport int 0xe mov di,0000b000h mov cx,int0xe_end - int0xe cld rep movsb in al,92h or al,00000010b out 92h,al mov eax,00001000h mov cr3,eax mov eax,80000001h mov cr0,eax jmp dword flatcode:bsp32 [bits 32] bsp32: mov ax,flatstack mov ss,ax mov esp,0x2fff int 0h ;int 0x0 mov byte [0x6000],'a' ;pagefault interrupt! halt:mov byte [0xa00],'r' mov byte [0xa02],'e' mov byte [0xa04],'t' hlt int0: mov ax,flatdata mov ds,ax mov byte [0xa0],'I' mov byte [0xa2],'n' mov byte [0xa4],'t' mov byte [0xa6],' ' mov byte [0xa8],'0' ret int0_end: int0xe: mov ax,flatdata mov ds,ax mov byte [0x0],'P' mov byte [0x1],0fh mov byte [0x2],'a' mov byte [0x3],0fh mov byte [0x4],'g' mov byte [0x5],0fh mov byte [0x6],'e' mov byte [0x7],0fh mov byte [0x8],'F' mov byte [0xa],'a' mov byte [0xc],'u' mov byte [0xe],'l' mov byte [0x10],'t' ;jmp dword flatcode:halt ---> OK ret ---> problem int0xe_end: pagedir: dd 00002001h ;page-directory-table pagedir_end: page_l2_0: dd 000b8001h ;text mode 80x25 page_l2_1: dd 0000a001h ;int 0h page_l2_2: dd 0000f001h ;stack page_l2_3: dd 0000b001h ;int 0xe page_l2_4: dd 00000000h page_l2_5: dd 00000000h page_l2_6: dd 00000000h page_l2_7: dd 00007001h ;bsp32 page_l2_end: GDT: dq 0000000000000000h GDT_CODE: dq 00cf9a000000ffffh GDT_DATA: dq 00cf92000000ffffh GDT_END: GDT_DESC: dw GDT_END - GDT - 1 ;GDTR register value 32(addr)+16(limit) dd GDT IDT: dq 00008e0000081000h ;Interrupt discriptor table dq 0h dq 0h dq 0h dq 0h dq 0h ;idt-discriptor stored a selector and a offset address. dq 0h dq 0h dq 0h dq 0h dq 0h dq 0h dq 0h dq 0h dq 00008e0000083000h ;int 0xe IDT_END: IDT_DESC: dw IDT_END - IDT - 1 ;IDTR register value 32(addr)+16(limit) dd IDT times 510 - ($ - $$) db 0 db 0x55 db 0xaa
解答
核心问题是硬件异常(如页错误)和软中断int 0xe的栈帧结构完全不同,直接用ret会导致栈指针失衡,破坏系统运行状态。
1. 两种场景的栈帧差异
- 软中断
int 0xe:执行该指令时,CPU仅向栈中压入三个值:EFLAGS(4字节)、CS(4字节)、EIP(4字节),总栈帧大小12字节。此时ret指令可以正确弹出这三个值,完成返回(因为int在这里的行为类似带特权检查的call,ret正好匹配call的栈操作)。 - 页错误(硬件触发的0xe异常):当CPU检测到页错误时,除了压入
EFLAGS、CS、EIP,还会额外压入一个4字节的错误码,总栈帧大小16字节。此时用ret指令只能弹出12字节,栈指针ESP会停留在错误码的位置,后续代码会使用错误的栈数据,直接导致栈混乱,触发虚拟机崩溃。
2. 代码问题点
- 单独调用
int 0xe属于软中断场景,栈帧符合ret的要求,所以能正常返回; - 访问
[0x6000]触发页错误时,CPU自动压入错误码,栈帧多了4字节,ret无法正确处理这个额外的栈数据,直接引发系统异常。
3. 正确的返回方式
硬件异常处理程序必须使用iret指令返回,而非ret。对于带错误码的异常(如页错误),需要先手动弹出错误码,再执行iret:
int0xe: mov ax,flatdata mov ds,ax ; 页错误处理逻辑... add esp,4 ; 弹出额外的错误码 iret ; 用iret恢复EIP、CS、EFLAGS并返回
iret指令会严格按照硬件异常的栈帧结构恢复执行环境,而ret只适用于函数调用或不带错误码的软中断返回。
内容的提问来源于stack exchange,提问作者sanzenyou
相关产品推荐
相关产品推荐

