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

保护模式下VGA打印导致系统崩溃问题求助

第二阶段引导加载程序VGA打印导致启动循环问题解决

问题描述

在第二阶段引导加载程序末尾实现VGA打印功能(输出“Starting kernel...”)时,系统陷入启动循环,需要将该字符串打印在屏幕第8行(首行计为第1行)后让系统停机。

原代码

ORG 0x7E00
BITS 16
stage2:
    cli
    xor ax, ax
    mov ds, ax
    mov ss, ax
    mov sp, 0x7C00
    sti

    mov [BootDrive], dl

    mov ah, 0x02
    mov bh, 0x00
    mov dh, 0x02
    mov dl, 0x00
    int 0x10

    push ds
    mov si, load_gdt
    call print
    lgdt [gdtinfo]
    mov si, enter_pmode
    call print
    mov eax, cr0
    or eax, 1
    mov cr0, eax
    jmp 0x08:pmode

unreal:
    pop ds
    xor ax, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    sti
    mov si, enter_unreal
    call print
    jmp boot

print:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0E
    mov bh, 0x00
    mov bl, 0x07
    int 0x10
    jmp print
.done:
    ret

boot:
    mov si, load_disk
    call print

    mov dl, [BootDrive]
    xor ax, ax
    int 0x13
    jc disk_error

    mov ax, 0x1000
    mov es, ax
    mov bx, 0

    mov dl, [BootDrive]
    mov dh, 0
    mov ch, 0
    mov cl, 4
    mov al, 16
    mov ah, 0x02

    int 0x13
    jc disk_error

    cmp al, 0
    je disk_error

    mov ax, [es:0]
    cmp ax, 0
    je no_kernel

    jmp kernel

disk_error:
    mov si, error_disk_read
    call print
    jmp halt

no_kernel:
    mov si, error_no_kernel
    call print
    jmp halt

kernel:
    mov si, enter_pmode
    call print

    lgdt [gdtinfo]
    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp 0x08:kernel32

halt:
    cli
    hlt
    jmp halt

load_disk db "Loading kernel...",0x0D, 0x0A, 0
load_gdt db "Loading GDT...", 0x0D, 0x0A, 0
enter_pmode db "Entering protected mode...", 0x0D, 0x0A, 0
enter_unreal db "Entering unREAL mode...", 0x0D, 0x0A, 0
error_disk_read db "Disk read error!", 0x0D, 0x0A, 0
error_no_kernel db "No kernel found!", 0x0D, 0x0A, 0
BootDrive db 0

align 8
gdt:
    dd 0,0
flatcode:
    db 0xff,0xff,0,0,0,10011010b,10001111b,0
flatdata:
    db 0xff,0xff,0,0,0,10010010b,11001111b,0
gdt_end:
gdtinfo:
    dw gdt_end - gdt - 1
    dd gdt

[BITS 32]
pmode:
    mov bx, 0x10
    mov ds, bx
    mov es, bx
    mov fs, bx
    mov gs, bx
    mov ss, bx
    mov eax, cr0
    and eax, 0xFFFFFFFE
    mov cr0, eax
    jmp 0x0:unreal

kernel32:

    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov esp, 0x90000

    mov esi, start_kernel
    mov edi, 0xB8000
    mov ah, 0x0F  
.print_loop:
    lodsb
    or al, al
    jz .done
    stosw
    jmp .print_loop
.done:

.hang:
    cli
    hlt
    jmp .hang

start_kernel db "Starting kernel...", 0

问题分析与修复步骤

1. 启动循环的根源:进入保护模式未关闭中断

在kernel标签的代码中,进入保护模式前没有执行cli指令。切换到保护模式时若有中断触发,会导致CPU异常,进而引发系统重启循环。

2. 调整打印位置到第8行

VGA文本模式的显存起始地址为0xB8000,每行包含80个字符,每个字符占2字节(1字节字符+1字节属性)。第8行的起始偏移为:
(8-1) * 80 * 2 = 1120,对应的显存地址为0xB8000 + 1120 = 0xB8460。

修复后的关键代码修改

kernel:
    mov si, enter_pmode
    call print
    ; 进入保护模式前关闭中断
    cli
    lgdt [gdtinfo]
    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp 0x08:kernel32

; ...

kernel32:

    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov esp, 0x90000

    mov esi, start_kernel
    ; 设置打印起始地址为第8行
    mov edi, 0xB8460
    mov ah, 0x0F  
.print_loop:
    lodsb
    or al, al
    jz .done
    stosw
    jmp .print_loop
.done:

.hang:
    cli
    hlt
    jmp .hang

其他注意事项

  • 确保GDT的定义正确,32位模式下的段选择子(0x08为代码段,0x10为数据段)配置无误。
  • kernel32中的停机代码cli+hlt+jmp .hang能确保系统稳定停机,避免意外唤醒。

内容的提问来源于stack exchange,提问作者Connor Thomson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 00:05:54