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

从GRUB 0.97切换至GRUB 2时内核触发GPF异常的问题排查

GRUB 2启动内核触发GPF故障的排查与解决

我最近在把操作系统的启动从GRUB 0.97(stage2_eltorito)切换到GRUB 2,生成可启动ISO时遇到了棘手的问题:内核在GRUB Legacy下运行完全正常,键盘输入、定时器都没问题,但用GRUB 2启动就会触发**General Protection Fault(GPF)**直接停机。我反复检查代码都没找到问题,想问问这到底是哪儿出了问题,该怎么修复?

补充背景:我给内核加了异常处理ISR,还有两个IRQ——一个处理键盘输入,一个处理定时器计时。另外用kvm -kernel启动内核是正常的,所以我觉得不是Multiboot加载的问题,因为两种GRUB都能成功加载内核。


相关代码展示

内核汇编入口点(带Multiboot头)

extern toc
extern init
extern kmain
extern load_gdt
global start32
MBALIGN equ 1<<0
MEMINFO equ 1<<1
MAGIC_NUMBER equ 0x1BADB002
FLAGS equ MBALIGN | MEMINFO
CHECKSUM equ -(MAGIC_NUMBER + FLAGS)
section .text align 4
dd MAGIC_NUMBER
dd FLAGS
dd CHECKSUM
start32:
call load_gdt
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, 0x9c000
call init
push ebx
call kmain

load_gdt及GDT定义

global toc
global load_gdt
section .text
load_gdt:
lgdt[toc]
ret
section .rodata
gdt32:
dd 0
dd 0
dw 0x0FFFF
dw 0
db 0
db 0x9A
db 0xCF
db 0
dw 0x0FFFF
dw 0
db 0
db 0x92
db 0xCF
db 0
gdt_end:
toc:
dw gdt_end - gdt32 - 1
dd gdt32

系统初始化函数init()

#include <kernel.h>
void init() {
interrupt_disable();
terminal_init(LGREEN, BLACK);
idt_install();
isr_install();
irq_install();
interrupt_enable();
timer_install();
keyboard_install();
return;
}

主函数kmain()

#include <kernel.h>
#include <multiboot.h>
char logo[1024] = {
":::::::::::///oyhhyo//:::::::::::/:\n" \
"://////////+yNNmhysoo+////////////:\n" \
"::/://:://yNdy+//::::::+o/:://////:\n" \
"::////://sNdy+//::-:-:::++/://////:\n" \
"::::::://dmdyo/:::::--::++/::::::::\n" \
":::::::/+mdhhysoo+:/++//+y/::::::::\n" \
":::::::/oddyooo+/o//+so+/o/::::::::\n" \
"+ = = + + + +++++ Welcome to LunaOS!\n" \
"+ = = + + + +===+ A simple 32-bit operating system\n" \
"+ = = + + + + + Written soley by Safal Aryal\n" \
"++++++ = = = = + + + + All components of this OS,\n" \
":::::::/+hhyo///+s///::/+//:::::::: are in the public domain!\n" \
"::::::://oshs+/+hdhs+/////::::::::: (excluding the GRUB bootloader)\n" \
"::::::::/++so+shdyhys+////::::::::: Type `help` for a list of commands\n" \
"::::::::/+o++oosso++/+/:/::::::::::\n" \
":::::::/ossssysoso+//+/:/::::::::::\n" \
":::://ohysosysso+oo/:///:::::::::::\n" \
"::::--:/ssyoshhsoo+/::+:/-:::::::::\n" \
":::-----:+shysdyo/-.`-/::--:----:::\n" \
"--::------:+sssyo/.`.:/:----::-----\n" \
"-::/:--::-::---//////--------::----\n" \
};
void kmain(multiboot_info_t *mbd, uint32_t magic) {
uint32_t mmap;
if((mbd->flags >> 6) & 1) {
mmap = mbd->mmap_addr;
}
terminal_puts(logo);
terminal_puts("SHELL> ");
for(;;){asm volatile("sti"); asm volatile ("hlt");};
}

问题根源分析

GRUB 2和GRUB Legacy在启动后对硬件状态的初始化有明显差异,最可能的问题出在中断控制器(PIC)的状态上:

  • GRUB Legacy会主动将PIC设置为兼容模式(比如屏蔽所有IRQ或设置默认中断向量偏移),但GRUB 2可能会保留BIOS初始化的PIC状态,甚至没有预先屏蔽IRQ。
  • 你的irq_install()负责重新映射PIC,但如果GRUB 2没有先屏蔽IRQ,在重新映射过程中极易触发未处理的中断,进而导致GPF。
  • 另外,GDT加载的完整性也可能有问题:虽然你已经重新加载了段寄存器,但CS寄存器无法通过mov指令直接修改,可能存在刷新不彻底的情况。

修复步骤

  1. 初始化PIC前强制屏蔽所有IRQ
    在irq_install()函数最开头,先执行以下代码屏蔽主从PIC的所有中断,避免重新映射时出现意外触发:

    outb(0x21, 0xFF);  // 屏蔽主PIC所有IRQ
    outb(0xA1, 0xFF);  // 屏蔽从PIC所有IRQ
    
  2. 完善GDT加载逻辑
    修改load_gdt函数,通过远跳转强制刷新CS寄存器(CS无法直接用mov修改),同时把段寄存器加载逻辑整合到这里,避免重复操作:

    load_gdt:
        lgdt [toc]
        jmp 0x08:.flush  ; 0x08是GDT中代码段的选择子
    .flush:
        mov ax, 0x10     ; 0x10是GDT中数据段的选择子
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov ss, ax
        ret
    

    记得删除start32中原有的段寄存器加载代码。

  3. 验证IRQ向量偏移的正确性
    确保你把IRQ0-15映射到了和CPU异常向量不重叠的位置(比如从0x20开始),如果向量重叠,触发IRQ时会被当成未处理的CPU异常,直接导致GPF。

  4. 检查内存映射的访问边界
    GRUB 2提供的内存映射可能和GRUB Legacy不同,访问mbd->mmap_addr时一定要结合mbd->mmap_length判断边界,避免越界访问内存引发GPF。

额外测试建议

  • 暂时注释掉irq_install()、keyboard_install()和timer_install(),用GRUB 2启动测试,如果不再触发GPF,就能确定问题出在IRQ/PIC相关逻辑上。
  • 给异常处理添加详细日志,打印GPF触发时的错误码、EIP寄存器值,精准定位出错的代码位置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:02:20