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

自定义Bootloader可加载汇编内核却无法加载C内核问题排查

自制Bootloader切换C语言内核加载失败问题

我正在开发一款带有自制Bootloader的自定义操作系统,之前使用汇编内核(kernel.asm)时一切正常,但替换为C语言内核(kernel.c)后,Bootloader无法加载内核并报错。

环境详情

  • Bootloader:自制(使用NASM编写)
  • 模式切换:Bootloader从16位实模式启动,调用内核前切换至32位保护模式
  • 内核:kernel.c通过i686-elf-gcc编译,链接器为i686-elf-ld
  • 工具链:M2芯片macOS上的交叉编译器
  • 磁盘镜像:MBR引导扇区
  • 测试工具:qemu-system-i386 -hda os-image.bin

Bootloader(boot.asm)说明

  • 将kernel.bin加载至0x10000地址
  • 切换至保护模式
  • 跳转至0x10000作为内核入口点

保护模式跳转代码

mov eax, 0x10000
jmp eax  ; Jump to kernel

C内核问题

替换为以下kernel.c代码:

void kernel_main() {
    char *video_memory = (char *)0xB8000;
    *video_memory = 'X';  // 屏幕打印'X'
    while (1);
}

同时定义了_start作为入口点:

__attribute__((section(".text"))) 
__attribute__((noreturn)) 
void _start() {
    kernel_main();
    while (1);  // 防止返回
}

链接脚本(linker.ld)

OUTPUT_FORMAT(elf32-i386)
ENTRY(_start)

SECTIONS
{
    . = 0x1000;          /* 内核加载地址0x1000 */
    .text ALIGN(4) : { *(.text) }
    .rodata ALIGN(4) : { *(.rodata) }
    .data ALIGN(4) : { *(.data) }
    .bss ALIGN(4) : { *(.bss) }
}

构建命令

nasm -f bin boot.asm -o boot.bin
i686-elf-gcc -ffreestanding -m32 -c kernel.c -o kernel.o
i686-elf-ld -m elf_i386 -T linker.ld -o kernel.bin --oformat binary kernel.o
cat boot.bin kernel.bin > os-image.bin
qemu-system-x86_64 -fda os-image.bin

测试时出现加载失败错误,Qemu启动后显示"Booting from Hard Disk..."后报错,无法正常加载内核。

完整boot.asm代码

[bits 16]                  ; 16位实模式
[org 0x7c00]               ; Bootloader加载地址0x7C00

start:
    mov al, dl
    call print_hex_byte  ; 打印启动驱动器号
    cli                    ; 禁用中断
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00         ; 设置栈

    ; 打印"LOADING KERNEL"信息
    mov si, loading_msg
    call print_string

disk_read:
    mov ah, 0x00   ; 读取前重置磁盘
    mov dl, 0x00   ; 驱动器0
    int 0x13
    
    mov ah, 0x02   ; BIOS读扇区功能
    mov al, 1      ; 读取1个扇区(512字节)
    mov ch, 0      ; 柱面0
    mov dh, 0      ; 磁头0
    mov cl, 2      ; 扇区2
    mov dl, 0x00   ; 驱动器0(硬盘)
    mov ax, 0x1000 
    mov es, ax
    mov bx, 0x0000

    mov cx, 3      ; 重试次数
read_retry:
    int 0x13
    jnc read_ok    ; 成功则跳转
    
    mov si, int_cmd
    call print_string
    jnc read_ok    ; 成功则跳转

    loop read_retry  ; 重试3次
    jmp disk_error  ; 全部失败则显示错误

; 16字节磁盘地址包(DAP)
dap:
    db 0x10         ; 包大小(16字节)
    db 0            ; 固定为0
    dw 1            ; 读取扇区数
    dw 0x0000       ; 缓冲区偏移
    dw 0x1000       ; 缓冲区段地址
    dq 1            ; LBA扇区号(扇区2)

read_ok:
    mov si, kernel_loaded_msg
    call print_string
    mov esi, 0x10000
    mov ecx, 16
print_loop:
    mov al, [esi]
    call print_hex_byte
    inc esi
    loop print_loop

    ; 加载全局描述符表(GDT)
    cli                  ; 禁用中断
    lgdt [gdt_descriptor] ; 加载GDT

    ; 设置CR0寄存器的保护模式位
    mov eax, cr0
    or eax, 0x1          ; 设置PE(保护模式启用)位
    mov cr0, eax

    ; 远跳转到32位模式(刷新CPU流水线)
    jmp 0x08:init_protected_mode

; ----------------- 32位保护模式 -----------------
[bits 32]
init_protected_mode:
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov esp, 0x90000

    ; 打印标记确认进入保护模式
    mov dword [0xB8000], 0x2F4D2F4F   ; 红色"MO"
    mov dword [0xB8004], 0x2F442F45   ; 红色"DE"

    ; 读取0x10000前4字节
    ;mov esi, 0x10000
    ;mov al, [esi]
    ;call print_hex_byte  ; 打印第一字节
    ;mov al, [esi + 1]
    ;call print_hex_byte  ; 打印第二字节
    ;mov al, [esi + 2]
    ;call print_hex_byte  ; 打印第三字节
    ;mov al, [esi + 3]
    ;call print_hex_byte  ; 打印第四字节
    ;mov esi, 0x10000
    ;mov al, [esi]
    ;call print_hex_byte

    ; 若内核缺失则永久循环
    cmp dword [esi], 0x00000000
    je halt  ; 前4字节为0则认为内核缺失,停机

    ; 跳转到内核
    ;call dword 0x10000
    mov eax, 0x10000
    jmp eax

; ----------------- 全局描述符表(GDT) -----------------
gdt:
    dq 0x0000000000000000  ; 空描述符
    dq 0x00CF9A000000FFFF  ; 32位代码段
    dq 0x00CF92000000FFFF  ; 32位数据段

gdt_descriptor:
    dw gdt_descriptor - gdt - 1  ; GDT大小
    dd gdt  ; GDT地址

loading_msg db "LOADING KERNEL", 0
kernel_loaded_msg db "JUMP!", 0
int_cmd db "INT-T", 0
in_protected_mode db "IN-PRT", 0

disk_error:
    mov si, error_msg
    call print_string
    jmp halt

error_msg db "ERR!", 0

halt:
    hlt                   ; 停机

; ----------------- 字符串打印函数 -----------------
print_string:
    pusha
.loop:
    lodsb         ; 从SI加载字符到AL
    or al, al     ; 检查是否为终止符
    jz .done
    mov ah, 0x0E  ; BIOS电传打字功能
    int 0x10
    jmp .loop
.done:
    popa
    ret

print_hex_byte:
    push ax
    push bx
    push cx
    push dx

    mov ah, 0x0E  ; BIOS电传打字输出

    ; 转换高4位为十六进制
    mov bl, al
    shr bl, 4
    cmp bl, 9
    jg hex_letter
    add bl, '0'
    jmp print1
hex_letter:
    add bl, 'A' - 10
print1:
    mov al, bl
    int 0x10

    ; 转换低4位为十六进制
    mov bl, al
    and bl, 0x0F
    cmp bl, 9
    jg hex_letter2
    add bl, '0'
    jmp print2
hex_letter2:
    add bl, 'A' - 10
print2:
    mov al, bl
    int 0x10

    pop dx
    pop cx
    pop bx
    pop ax
    ret

times 510 - ($ - $$) db 0  ; 填充至510字节
dw 0xAA55                  ; 引导扇区签名

内容的提问来源于stack exchange,提问作者Sunil KhoRwal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 05:44:58