切换至保护模式后Bootloader无法打印字符的问题求助
问题分析与修复方案
你的Bootloader无法在保护模式下打印字符,核心问题集中在保护模式段寄存器未初始化以及实模式寻址环境不明确,以下是具体问题和修复后的代码:
核心问题点
- 保护模式段寄存器未初始化:跳转到保护模式后,DS、ES等寄存器仍保留实模式的段值(通常为0x0000),在保护模式下会被当作段选择子,指向GDT的NULL条目,导致内存访问异常,无法写入视频内存。
- 实模式DS寄存器未显式设置:若实模式下DS未初始化为0,直接寻址
gdt_pointer时可能指向错误的物理地址,导致GDT加载失败。 - 缺少栈初始化:虽然当前代码未用到栈,但保护模式下未初始化栈会导致后续操作存在潜在异常风险。
修复后的代码
.code16 .global _start _start: cli mov $0x0000, %ax mov %ax, %ds # 初始化DS为0,保证实模式下寻址正确 gdt_start: .quad 0x0 # NULL描述符(必须存在) gdt_data: .short 0xffff # 段限长低16位 .short 0x0 # 段基址低16位 .byte 0x0 # 段基址中8位 .byte 0b10010010 # 数据段属性:可读写、非特权级、存在 .byte 0b11001111 # 粒度4KB、32位模式、段限长高4位 .byte 0x0 # 段基址高8位 gdt_code: .short 0xffff .short 0x0 .byte 0x0 .byte 0b10011010 # 代码段属性:可执行、可读、非特权级、存在 .byte 0b11001111 .byte 0x0 gdt_end: DATASEG = gdt_data - gdt_start CODESEG = gdt_code - gdt_start gdt_pointer: .short gdt_end - gdt_start - 1 # GDT限长(总字节数-1) .long gdt_start # GDT物理基址 lgdt gdt_pointer # 加载GDT指针(AT&T语法直接引用符号) mov %cr0, %eax or $1, %eax mov %eax, %cr0 # 置位CR0.PE,进入保护模式 ljmp $CODESEG, $protected_mode # 远跳转刷新指令流水线,确保进入纯32位模式 .code32 protected_mode: mov $DATASEG, %ax mov %ax, %ds # 设置数据段选择子 mov %ax, %es # 设置附加段选择子 mov %ax, %fs # 设置FS选择子 mov %ax, %gs # 设置GS选择子 mov %ax, %ss # 设置栈段选择子 mov $0x90000, %esp # 初始化栈指针到安全内存区域(0x90000) mov $'C', %al mov $0x0f, %ah # 字符属性:黑底白字 mov %ax, 0xb8000 # 写入视频内存首地址 jmp . # 无限循环 .fill 510-(.-_start), 1, 0 .word 0xaa55
修改点说明
- 初始化实模式DS寄存器:显式设置
DS=0,保证实模式下直接寻址的物理地址与代码链接地址(0x7c00)一致,避免GDT指针加载错误。 - 保护模式段寄存器初始化:跳转到32位代码后,立即将所有段寄存器设置为数据段选择子
DATASEG,确保内存访问使用GDT中定义的合法数据段(基址0、限长0xFFFFF,覆盖整个低1MB内存)。 - 初始化栈:设置SS为数据段选择子,ESP指向0x90000(避开bootloader和视频内存的安全区域),避免后续操作触发栈异常。
- 简化lgdt语法:AT&T语法中直接使用
lgdt gdt_pointer即可,编译器会自动处理正确的寻址方式,避免潜在的段前缀错误。
测试方法
使用QEMU运行修复后的bootloader:
qemu-system-i386 -boot a -fda boot.bin
正常情况下会在屏幕左上角看到白色的字符"C"。
内容的提问来源于stack exchange,提问作者user26135221
相关产品推荐
相关产品推荐

