自定义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
相关产品推荐
相关产品推荐

