为何我的操作系统无法切换到保护模式?求故障原因分析
保护模式切换失败故障分析求助
我原本计划全程用汇编语言开发实模式操作系统,后来意识到实模式的局限性,改成实模式写引导加载程序(bootloader)、保护模式写内核(kernel)。但在引导加载程序中配置全局描述符表(GDT)后,系统始终无法切换到保护模式,求分析故障原因。
相关代码
bootloader.asm
bits 16 section .boot global _start KERNEL_ADDRESS equ 0x7E00 _start: cld xor ax, ax mov ds, ax mov es, ax mov ss, ax mov sp, 9000h mov di, 5 ; 允许重试5次 BOOT_TryNext: mov dh, 0 ; CHS = (0,0,2), DL为BIOS传入的驱动器号 mov cx, 0002h mov bx, 7E00h ; ES:BX作为缓冲区地址 mov ax, 0201h ; BIOS磁盘读扇区功能 int 13h ; 返回值存于AX,CF标志位表示结果 jnc BOOT_Loaded mov ah, 00h ; BIOS磁盘重置功能 int 13h ; 返回值存于AH,CF标志位表示结果 dec di jnz BOOT_TryNext BOOT_Exit: call BOOT_notLoaded BOOT_Exit_Loop: cli hlt jmp BOOT_Exit_Loop BOOT_Loaded: GDT_Start: null_descriptor: dd 0 dd 0 code_descriptor: dw 0xffff dw 0 db 0 db 10011010 db 11001111 db 0 data_descriptor: dw 0xffff dw 0 db 0 db 10010010 db 11001111 db 0 GDT_End: GDT_Descriptor: dw GDT_End - GDT_Start - 1 dd GDT_Start CODE_SEG equ code_descriptor - GDT_Start DATA_SEG equ code_descriptor - GDT_Start cli lgdt [GDT_Descriptor] mov eax, cr0 or eax, 1 mov cr0, eax jmp CODE_SEG:boot_trampoline boot_trampoline: ; 为内核设置段寄存器 mov ax, DATA_SEG ; 加载内核数据段 mov ds, ax mov es, ax mov fs, ax mov gs, ax ; 为内核设置栈指针 mov ax, KERNEL_STACK_SIZE mov ss, ax mov sp, KERNEL_STACK_START ; 跳转到内核 jmp KERNEL_ADDRESS ; 定义内核栈常量 KERNEL_STACK_SIZE equ 0x1000 ; 内核栈大小(4KB) KERNEL_STACK_START equ 0x8000 - KERNEL_STACK_SIZE BOOT_notLoaded: pusha mov ax, 0x0700 ; 功能07,AL=0表示滚动整个窗口 mov bh, 0x07 ; 字符属性:黑底白字 mov cx, 0x0000 ; 起始行=0,起始列=0 mov dx, 0x184f ; 结束行=24(0x18),结束列=79(0x4f) int 10h ; 调用BIOS视频中断 popa mov ah, 0x02 xor bh, bh xor dx, dx int 10h mov si, boot_err mov ah, 0x0e boot_characterLoop2: lodsb or al, al jz BOOT_Exit_Loop int 10h jmp boot_characterLoop2 boot_msg: db "操作系统加载成功!", 0 boot_err: db "操作系统加载失败!", 0 times 510-($-$$) db 0 dw 0xAA55
kernel.asm
bits 32 section .kernel global _start_kernel _start_kernel: mov al, 'A' mov ah, 0x0f mov [0xb8000], ax
linker.ld
OUTPUT_FORMAT(binary) ENTRY(_start) SECTIONS { . = 0x7c00; bootloader : { bootloader.bin(.boot) } kernel : { kernel.bin(.kernel) } /DISCARD/ : { *(.bss*); *(COMMON*); } }
Makefile
boot = bootloader.asm kernel = kernel.asm boot_o = bootloader.bin kernel_o = kernel.bin bin = myos.bin asm_compiler = nasm qemu = qemu-system-i386 compile: $(asm_compiler) -f elf32 -g $(boot) -o $(boot_o) $(asm_compiler) -f elf32 -g $(kernel) -o $(kernel_o) ld -m elf_i386 -nmagic -T linker.ld -o $(bin) $(boot_o) $(kernel_o) run: $(qemu) -fda $(bin) clean: -rm $(boot_o) -rm $(kernel_o) -rm $(bin)
核心故障点分析
数据段选择子定义错误
代码中DATA_SEG equ code_descriptor - GDT_Start将数据段和代码段指向了同一个描述符,这会导致保护模式下权限校验失败。正确应为:DATA_SEG equ data_descriptor - GDT_StartGDT物理地址错误
GDT定义在BOOT_Loaded代码块内部,汇编时GDT_Start是相对于段起始的偏移,而非物理地址。实模式下lgdt需要物理地址,因此必须在汇编开头添加org 0x7C00声明代码加载基地址,让汇编器直接生成物理地址:bits 16 org 0x7C00保护模式栈设置错误
代码中mov ax, KERNEL_STACK_SIZE把栈大小当成了段选择子,完全不符合保护模式规则。同时应使用32位栈指针寄存器esp,建议将栈放到远离加载区域的地址:boot_trampoline: mov ax, DATA_SEG mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov esp, 0x90000 ; 将栈放到0x90000位置,避免与引导扇区/内核重叠引导扇区编译格式错误
引导加载程序是16位实模式代码,不能用-f elf32编译,会引入额外ELF头破坏512字节引导扇区结构。应改为二进制格式编译:compile: $(asm_compiler) -f bin -g $(boot) -o $(boot_o) $(asm_compiler) -f elf32 -g $(kernel) -o $(kernel_o) ld -m elf_i386 -nmagic -T linker.ld -o $(bin) $(boot_o) $(kernel_o)
内容的提问来源于stack exchange,提问作者Federico Occhiochiuso
相关产品推荐
相关产品推荐

