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

自定义Bootloader进入保护模式后Qemu模拟器频繁闪烁

自定义Bootloader进入保护模式后异常问题

自定义Bootloader进入保护模式后出现异常:将cr0最低位置1进入保护模式后,执行远跳转jmp 0x8:start_kernel重置CS寄存器(0x8对应GDT中第8字节的内核代码段),随后将其他段寄存器设为0x10(GDT第16字节的内核数据段选择子),开中断并调用kernel_main。但进入保护模式后Qemu持续闪烁,内核无输出。


相关代码

boot.asm

[bits 16]
extern kernel_main
global start_kernel



xor ax, ax
mov es, ax
mov ds, ax
mov bp, 0x8000
mov sp, bp

mov dl, 0x80 ; first hard disk
mov ah, 0x02 ; int 0x13 function number
mov al, 0x05 ; kernel is only 512 bytes (# sector's we have to read) 
mov ch, 0x00 ; cylinder number 
mov cl, 0x02 ; #starting sector (starts from 1 not 0) first 512 bytes for boot loader
mov dh, 0x00 ; head number  
mov bx, 0x7e00 ; code for bootloader starts at 7c00 ~ 31744 bytes + 512 bytes (for bootlaoder) = 7E00

int 0x13 

lgdt [gdtr]

mov ah, 0x0 ; service for setting the video mode
mov al, 0x03 ; setting video mode to be text mode w/ 16 colors
int 0x10

cli 

mov eax, cr0
or eax, 1
mov cr0, eax

jmp 0x08:start_kernel

[bits 32]
start_kernel:

    mov eax, 0x10
    mov ss,  eax
    mov ds,  eax
    mov fs,  eax
    mov gs,  eax
    mov es,  eax
    sti
    call kernel_main


gdt:
    null_descriptor: db 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
    kernel_code: db 0x0, 0xCF, 0x9A, 0x0, 0x0, 0x0, 0xFF, 0xFF
    kernel_data: db 0x0, 0xCF, 0x92, 0x0, 0x0, 0x0, 0xFF, 0xFF
    user_code: db 0x0, 0xCF, 0xFC, 0x0, 0x0, 0x0, 0xFF, 0xFF
    user_data: db 0x0, 0xCF, 0xF2, 0x0, 0x0, 0x0, 0xFF, 0xFF
gdt_end:

gdtr:
    size: dw gdt_end - gdt - 1
    base: dd gdt

times 510-($-$$) db 0
db 0x55, 0xaa

makefile

ASM=nasm
CC=x86_64-elf-gcc
BOOTLOADER=boot.asm
BOOTLOADER_BIN=boot.o
KERNEL=kernel.c 
KERNEL_FLAGS= -Wall -m32 -ffreestanding -fno-asynchronous-unwind-tables -fno-pie -c
KERNEL_OBJECT= kernel.o
KERNEL_IMG=kernel.img
LINKER_FILE=linker.ld
OUTPUT_BIN=kernel.bin

build: $(BOOTLOADER) $(KERNEL)

    $(ASM) -f elf32 $(BOOTLOADER) -o $(BOOTLOADER_BIN)
    $(CC) $(KERNEL_FLAGS) $(KERNEL) -o $(KERNEL_OBJECT) 
    x86_64-elf-ld -melf_i386 -T$(LINKER_FILE) $(BOOTLOADER_BIN) $(KERNEL_OBJECT) -o linked_kernel.elf
    x86_64-elf-objcopy -O binary linked_kernel.elf $(OUTPUT_BIN)
    dd if=$(OUTPUT_BIN) of=$(KERNEL_IMG) conv=notrunc
    qemu-system-x86_64 -s kernel.img
clean:
    rm -f $(BOOTLOADER_BIN) $(KERNEL_OBJECT) $(OUTPUT_ELF) $(OUTPUT_BIN) $(KERNEL_IMG)

kernel.c

volatile unsigned char* video = (volatile unsigned char*)0xB8000;

void kernel_main() {
    // Write 'H' and 'i' at the top-left of the screen.
    video[0] = 'H'; // Character 'H'
    video[1] = 0x04; // Attribute byte (light gray on black)
    video[2] = 'i'; // Character 'i'
    video[3] = 0x04; // Attribute byte (light gray on black)

    // Infinite loop to keep the kernel running
    while (1);
}

GDT表定义(Python描述)

gdt = '''
[
    {   \"name\": \"null_descriptor\", \"type\": \"null\" },
    
    {   \"name\": \"kernel_code\", \"base_address\": \"0\", 
        \"limit\": \"fffff\", \"granularity\": \"4kb\", 
        \"system_segment\": false, \"type\": \"code\", 
        \"accessed\": false, \"read_enabled\": true, \"conforming\": false,
        \"privilege_level\": 0, \"present\": true, \"operation_size\": \"32bit\", \"64bit\": false  },
        
    {   \"name\": \"kernel_data\", \"base_address\": \"0\", 
        \"limit\": \"fffff\", \"granularity\": \"4kb\", 
        \"system_segment\": false, \"type\": \"data\", 
        \"accessed\": false, \"expands\": \"up\", \"write_enabled\": true,
        \"privilege_level\": 0, \"present\": true, \"upper_bound\": \"4gb\", \"64bit\": false  },
        
    {   \"name\": \"userspace_code\", \"base_address\": \"0\", 
        \"limit\": \"fffff\", \"granularity\": \"4kb\", 
        \"system_segment\": false, \"type\": \"code\", 
        \"accessed\": false, \"read_enabled\": true, \"conforming\": false,
        \"privilege_level\": 3, \"present\": true, \"operation_size\": \"32bit\", \"64bit\": false  },
        
    {   \"name\": \"userspace_data\", \"base_address\": \"0\", 
        \"limit\": \"fffff\", \"granularity\": \"4kb\", 
        \"system_segment\": false, \"type\": \"data\", 
        \"accessed\": false, \"expands\": \"up\", \"write_enabled\": true,
        \"privilege_level\": 3, \"present\": true, \"upper_bound\": \"4gb\", \"64bit\": false  }
]
''';

问题排查与修复

1. GDT描述符字节顺序错误

手动定义的GDT描述符字节顺序不符合x86规范。x86 GDT描述符(8字节)的固定结构为:

  • 低2字节:段限制低16位
  • 接下来2字节:段基址低16位
  • 接下来1字节:段基址中8位
  • 接下来1字节:属性位(P/DPL/S/Type)
  • 接下来1字节:段限制高4位 + 属性位(G/D/B/L/AVL)
  • 最后1字节:段基址高8位

按你的需求,修正后的内核代码段和数据段描述符应为:

kernel_code: db 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x9A, 0xCF, 0x00
kernel_data: db 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x92, 0xCF, 0x00

用户态段需按同样规则修正。错误的GDT会导致CPU寻址异常,引发模拟器闪烁。

2. 未处理磁盘读取错误

当前代码未检查int 0x13的返回值,读盘失败会加载无效数据导致崩溃。需添加错误检查:

int 0x13
jc disk_error ; CF置位表示读盘失败,跳转至错误处理

disk_error:
    mov ah, 0x0e
    mov al, 'E'
    int 0x10
    jmp $ ; 死循环等待

3. 保护模式栈设置不完整

进入32位模式后,仅设置SS寄存器不够,需重新指定32位ESP地址,避免与代码区冲突:

[bits 32]
start_kernel:
    mov eax, 0x10
    mov ss, eax
    mov esp, 0x80000 ; 将栈设置到0x80000位置
    ; 其他段寄存器设置...

4. 缺失链接脚本(linker.ld)

Makefile中使用了linker.ld但未提供,必须通过链接脚本指定代码加载地址。示例脚本:

ENTRY(start)
SECTIONS {
    . = 0x7C00;
    .boot : { *(.boot) }
    . = 0x7E00;
    .text : { *(.text) }
    .data : { *(.data) }
    .bss : { *(.bss) }
}

同时在boot.asm开头添加[section .boot]标识引导代码段,确保链接器正确放置代码。

5. Qemu运行模式适配

运行32位内核时,建议使用qemu-system-i386适配32位环境,或给qemu-system-x86_64添加兼容性参数:

qemu-system-i386 -kernel kernel.img

修复上述问题后重新编译运行,内核应能正常输出"Hi",模拟器不再闪烁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 04:24:51