You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

启用中断时崩溃:x86 32位保护模式下IDT配置问题排查

问题修复方案

核心问题分析

你的代码导致QEMU崩溃主要有3个关键错误:

  • 中断服务程序(ISR)不符合x86保护模式规范
    32位保护模式下,CPU触发中断时会自动向栈中压入EFLAGS、CS、EIP三个值,而你的interrupt_test是普通C函数,既没有处理这个固定栈帧,也没有用iret指令返回(普通ret无法恢复EFLAGS),会直接导致栈混乱、CPU执行流异常。

  • IDT描述符(IDTR)结构错误
    x86的IDTR是一个6字节的打包结构:低16位是IDT的限长(sizeof(idt)-1),高32位是IDT的基地址。你用uint32_t idt_ptr[2]存储,会因为32位数组的对齐问题,把限长放在第一个32位的低16位,高16位填充垃圾数据,CPU加载IDTR时会读取错误的限长,直接触发异常崩溃。

  • 寄存器上下文未保存
    C函数默认会修改部分通用寄存器(如EAX、ECX等),但中断服务程序需要保证所有寄存器的值在返回时与进入时一致,否则会破坏被中断程序的上下文。

修复后的代码示例

1. 修正IDTR结构与IDT初始化

#include "idt.h"
#include "screen.h"

// 正确的IDT条目结构(已正确打包)
struct IDTEntry 
{
    uint16_t base_low;
    uint16_t selector;
    uint8_t reserved;
    uint8_t flags;
    uint16_t base_high;
} __attribute__((packed));

// 正确的IDTR结构(6字节打包)
typedef struct {
    uint16_t limit;
    uint32_t base;
} __attribute__((packed)) IDTR;

struct IDTEntry idt[256];
IDTR idt_reg;

void set_idt_entry(int index, uint32_t isr_address, uint16_t selector, uint8_t flags) 
{
    idt[index].base_low = (uint16_t)(isr_address & 0xFFFF);
    idt[index].base_high = (uint16_t)((isr_address >> 16) & 0xFFFF);
    idt[index].selector = selector;
    idt[index].reserved = 0;
    idt[index].flags = flags;
}

// 汇编包装的ISR入口,负责保存寄存器、调用C函数、恢复寄存器并返回
void interrupt_wrapper() {
    __asm__ volatile (
        "pusha\n"                  // 保存所有通用寄存器
        "call interrupt_test\n"    // 调用C语言处理函数
        "popa\n"                   // 恢复所有通用寄存器
        "iret\n"                   // 中断返回,恢复EFLAGS/CS/EIP
    );
}

void init_idt() 
{
    for(int i = 0; i < 256; i++)
    {
        set_idt_entry(i, (uint32_t)interrupt_wrapper, 0x08, 0x8E);      
    }

    // 正确初始化IDTR
    idt_reg.limit = sizeof(idt) - 1;
    idt_reg.base = (uint32_t)&idt;
    __asm__ volatile("lidt (%0)" : : "r"(&idt_reg));
}

void disable_interrupts() {
    __asm__ volatile("cli");
}

void enable_interrupts() 
{
    __asm__ volatile("sti");
}

void interrupt_test() 
{
    print_str("Yay", 100, 100, to_color(0, 255, 0));
    // 注意:如果是可屏蔽中断,需要发送EOI信号给PIC,避免重复触发
    // 比如对于主PIC:outb(0x20, 0x20);
}

2. 额外注意事项

  • 如果是使用PIC(可编程中断控制器)的场景,在处理完中断后必须向PIC发送EOI(中断结束)信号,否则PIC会持续发送该中断请求,导致系统反复触发中断崩溃。例如主PIC的EOI指令是outb(0x20, 0x20),从PIC需要先发送给从PIC再发送给主PIC。
  • 确保0x08这个段选择符对应的是GDT中正确的代码段(可读可执行、DPL=0的32位代码段),如果段选择符错误,CPU会触发段异常。

内容的提问来源于stack exchange,提问作者Endrix

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 08:57:32