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

为何我的操作系统无法切换到保护模式?求故障原因分析

保护模式切换失败故障分析求助

我原本计划全程用汇编语言开发实模式操作系统,后来意识到实模式的局限性,改成实模式写引导加载程序(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)

核心故障点分析

  1. 数据段选择子定义错误
    代码中DATA_SEG equ code_descriptor - GDT_Start将数据段和代码段指向了同一个描述符,这会导致保护模式下权限校验失败。正确应为:

    DATA_SEG equ data_descriptor - GDT_Start
    
  2. GDT物理地址错误
    GDT定义在BOOT_Loaded代码块内部,汇编时GDT_Start是相对于段起始的偏移,而非物理地址。实模式下lgdt需要物理地址,因此必须在汇编开头添加org 0x7C00声明代码加载基地址,让汇编器直接生成物理地址:

    bits 16
    org 0x7C00
    
  3. 保护模式栈设置错误
    代码中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位置,避免与引导扇区/内核重叠
    
  4. 引导扇区编译格式错误
    引导加载程序是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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 12:32:04