编写简易OS时ISR寄存器加载异常及错误码问题求助
中断处理机制配置问题排查与修复
问题描述
开发简易操作系统时配置中断处理机制,遇到两个核心问题:
- 调用带错误码的异常时,CPU推送的错误码触发General Protection Fault;
- 寄存器未正确压入栈,打印值要么为0要么是0x38e7,使用Bochs调试查看栈时频繁冻结。
相关代码
汇编ISR实现
%macro ISR_NOERRORCODE 1 global ISR%1: ISR%1: push 0 ; 虚拟错误码 push %1 ; 中断号 jmp isr_common %endmacro %macro ISR_ERRORCODE 1 global ISR%1: ISR%1: ; CPU自动推送错误码 push %1 ; 中断号 jmp isr_common %endmacro %include "isr_functions.as" extern ISR_regsHandler isr_common: pusha xor eax, eax mov ax, ds push eax ; 保存ds到栈 mov ax, 0x10 ; 切换到内核段 mov ds, ax mov es, ax mov fs, ax mov gs, ax push esp ; 传递栈指针给C函数 call ISR_regsHandler add esp, 4 ; 清理参数 pop eax ; 恢复段寄存器 mov ds, ax mov es, ax mov fs, ax mov gs, ax popa add esp, 8 ; 清理错误码和中断号 iret ; 中断返回
寄存器结构体定义
typedef struct { // 原定义:栈顺序完全错位 u32 ds; // 手动push的ds u32 edi, esi, ebp, cs_kernel, ebx, edx, ecx, eax; // pusha压入的寄存器(顺序错误) u32 interrupt, error; // 中断号、错误码(逻辑顺序正确但整体偏移错误) u32 eip, cs, eflags, esp, ss; // CPU自动压入的内容 } PACKED Registers_t;
C语言中断处理函数
void CDECL ISR_regsHandler(Registers_t* regs){ if (regs->interrupt >= 32) { QemuPrintf("未处理中断 %d --> 0x%x\n", regs->interrupt, regs->interrupt); } else { QemuPrintf("未处理异常 %x: ", regs->interrupt); QemuPrintf("%s\n", g_Exceptions[regs->interrupt]); QemuPrintf("寄存器值: \neax->%x ebx->%x ecx->%x edx->%x ebp->%x esi->%x edi->%x\n", regs->eax, regs->ebx, regs->ecx, regs->edx, regs->ebp, regs->esi, regs->edi); QemuPrintf("eip->%x cs->%x eflags->%x esp->%x ss->%x\n", regs->eip, regs->cs, regs->eflags, regs->esp, regs->ss); QemuPrintf("中断号->%x, 错误码->%x\n", regs->interrupt, regs->error); QemuPrintf("内核崩溃!!\n"); } }
触发int 0x01的错误输出
未处理异常 1: Double Fault 寄存器值: eax->0 ebx->0 ecx->0 edx->0 ebp->0 esi->0 edi->0 eip->38e7 cs->38e7 eflags->38e7 esp->38e7 ss->38e7 中断号->1, 错误码->1 内核崩溃!!
问题根源与修复方案
1. 寄存器结构体与栈布局完全不匹配
核心问题:
pusha的压栈顺序是EDI → ESI → EBP → ESP(执行pusha前的值) → EBX → EDX → ECX → EAX,栈顶最后压入的是EAX,但原结构体将EDI放在最前面,完全颠倒了顺序;- 原结构体中错误地将
cs_kernel作为成员,实际应为pusha压入的旧ESP; - 整体成员顺序未对齐栈的生长逻辑(栈向下生长,结构体指针指向栈顶,成员需按栈到栈底的顺序定义)。
修复后的结构体:
typedef struct { u32 ds; // 手动push的ds(栈顶) u32 eax, ecx, edx, ebx; // pusha最后压入的四个寄存器 u32 esp_pusha; // pusha压入的旧ESP u32 ebp, esi, edi; // pusha先压入的三个寄存器 u32 interrupt; // 手动push的中断号 u32 error; // CPU/手动push的错误码 u32 eip; // CPU自动压入的指令指针 u32 cs; // CPU自动压入的代码段选择子 u32 eflags; // CPU自动压入的标志寄存器 // 特权级切换时CPU自动压入的用户态栈信息,按需保留 u32 esp_user; u32 ss_user; } PACKED Registers_t;
2. 错误码导致的GPF问题
核心问题:
原结构体的偏移错误导致读取错误码时拿到的是栈中其他数据(如eflags),中断返回时栈平衡被破坏,触发GPF。修复结构体后,错误码与中断号的读取会恢复正常,栈平衡得到保证,GPF问题会解决。
3. Bochs调试冻结问题
核心问题:
栈布局错误导致调试器无法正确解析栈结构,甚至触发中断递归(如GPF触发中断,中断处理又引发GPF)。修复栈布局后,调试器可正常解析栈,冻结问题会缓解。调试时可先在ISR入口设置断点,逐步执行观察栈变化。
修复验证
修正结构体后,触发int 0x01(Debug异常),寄存器值会正确显示,中断号为1,错误码为0,不会再出现全0或0x38e7的异常值。
内容的提问来源于stack exchange,提问作者neta cohen
相关产品推荐
相关产品推荐

