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

二级引导程序与链接脚本配置故障:启动流程排查求助

二级引导程序与C内核启动异常排查

第一阶段引导程序可打印测试字符'B',但无法跳转到第二阶段,第二阶段的'L'及内核_core函数的显示内容均无输出。怀疑跳转地址、链接脚本或Makefile配置存在问题,相关代码及修正方案如下:


原始代码文件

osboot.asm - 第一阶段

[BITS 16]
org 0x7C00

osboot:
     mov ah, 0x0e        ; BIOS teletype function
     mov al, 'B'         ; Character to print
     int 0x10            ; Call BIOS interrupt

     call read_osload

     jmp 0:0x7e00

%include "read_osload.inc"

times 510-($-$$) db 0       ; Limit the sector to 510 bytes
dw 0xAA55                   ; Boot signature and last 2 bytes of the first sector

osload.asm - 第二阶段

[BITS 16]
section .osload
global _start

_start: jmp switch_pmode

%include "gdt.inc"

switch_pmode:
    mov ah, 0x0e        ; BIOS teletype function
    mov al, 'L'         ; Character to print
    int 0x10            ; Call BIOS interrupt

    cli
    lgdt [gdt_descriptor]

    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp code_seg:p_mode

[BITS 32]
p_mode:
    mov ax, cs
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ebp, 0x90000
    mov esp, ebp

    [EXTERN _core]
    call _core
    hlt

core.c - 内核

void _core() 
{
    char* video_memory = (char*)0xb8000;

    video_memory[0] = 'E';
    video_memory[1] = 'L';
    video_memory[2] = 'L';
    video_memory[3] = 'E';

    video_memory[4] = 'O';
    video_memory[5] = 'S';
}

read_osload.inc

[BITS 16]

read_osload:
    mov ah, 0        ; reset the drive function
    mov dl, 0        ; drive 0 is the floppy drive
    int 0x13          ; call the BIOS interrupt to reset drive

    mov ax, 0x7E00   ; read address 0x7E00 so we can jmp to the second stage
    mov es, ax
    xor bx, bx

    mov ah, 0x02     ; read sector function
    mov al, 1        ; read 1 sector
    mov ch, 0        ; 0 = track 1  (18 sectors per track) no need to move from track 0
    mov cl, 2        ; read sector 2
    mov dh, 0        ; head number
    mov dl, 0        ; drive number. drive 0 is the floppy drive
    int 0x13          ; call the BIOS interrupt to read drive
    ret

gdt.inc

; null segment descriptor
gdt_start:
    dq 0x0

; code segment descriptor
gdt_code:
    dw 0xffff    ; segment length, bits 0-15
    dw 0x0       ; segment base, bits 0-15
    db 0x0       ; segment base, bits 16-23
    db 10011010b ; flags (8 bits)
    db 11001111b ; flags (4 bits) + segment length, bits 16-19
    db 0x08       ; segment base, bits 24-31

; data segment descriptor
gdt_data:
    dw 0xffff    ; segment length, bits 0-15
    dw 0x0       ; segment base, bits 0-15
    db 0x0       ; segment base, bits 16-23
    db 10010010b ; flags (8 bits)
    db 11001111b ; flags (4 bits) + segment length, bits 16-19
    db 0x10       ; segment base, bits 24-31

gdt_end:

; GDT descriptor
gdt_descriptor:
    dw gdt_end - gdt_start - 1 ; size (16 bit)
    dd gdt_start ; address (32 bit)

code_seg equ gdt_code - gdt_start
data_seg equ gdt_data - gdt_start

link.ld

ENTRY(_core)
OUTPUT_FORMAT("binary")
OUTPUT(core.bin)

SECTIONS
{
    . = 0x7C00;                  /* osboot.asm starts at 0x7C00*/
    .osboot : AT(0x7C00)
    {
        *(.text)
        *(.rodata)
        *(.data)
        _bss_start = .;
        *(.bss)
        *(COMMON)
        _bss_end = .;
    }

    . = 0x7E00;                 /* osload.asm starts at 0x7E00? we need a place to jmp to from osboot */
    .osload : AT(0x7E00)
    {
        *(.text)
        *(.rodata)
        *(.data)
        _bss_start = .;
        *(.bss)
        *(COMMON)
        _bss_end = .;
    }

    . = 0x1000;                /* core.c starts at 0x1000? we need a place to call kernel _core */
    ._core : AT(0x1000)
    {
        *(.text)
        *(.rodata)
        *(.data)
        _bss_start = .;
        *(.bss)
        *(COMMON)
        _bss_end = .;
    }

    kernel_sectors = (SIZEOF(._core) + 511) / 512;

    . = ALIGN(4); /* Ensure alignment between sections */

    /DISCARD/ : {
        *(.eh_frame)
    }
}

makefile

all: elle.flp

osboot.bin: osboot.asm
    nasm -f bin osboot.asm -o osboot.bin

osload.o: osload.asm
    nasm osload.asm -f elf32 -o osload.o

core.o: core.c
    gcc -m32 -fno-pie -Wall -ffreestanding --no-builtin -c core.c -o core.o

core.bin: core.o osload.o
    ld -T link.ld -melf_i386 -o core.bin core.o osload.o 

elle.flp: osboot.bin core.bin
    cat osboot.bin core.bin > elle.flp

clean:
    rm -f osboot.bin osload.o core.o load_core.o core.bin elle.flp

问题根源与修正方案

1. 核心问题分析

  • 链接脚本ENTRY指向_core而非第二阶段入口_start,且段映射与加载地址不匹配
  • 扇区读取未检查BIOS中断返回值,仅读1扇区无法容纳第二阶段+内核代码
  • GDT段基址错误(代码/数据段基址设为0x08000000/0x10000000),导致保护模式寻址异常
  • osload使用.osload段,但链接脚本收集.text段,代码未被正确放置

2. 修正后的关键文件

修正链接脚本(link.ld)
ENTRY(_start)
OUTPUT_FORMAT("binary")
OUTPUT(stage2.bin)

SECTIONS
{
    . = 0x7E00;
    .text : AT(0x200)
    {
        *(.osload)
        *(.text)
    }

    .data : AT(0x200 + SIZEOF(.text))
    {
        *(.data)
    }

    .bss : AT(0x200 + SIZEOF(.text) + SIZEOF(.data))
    {
        _bss_start = .;
        *(.bss)
        *(COMMON)
        _bss_end = .;
    }

    stage2_sectors = (SIZEOF(.text) + SIZEOF(.data) + SIZEOF(.bss) + 511) / 512;

    /DISCARD/ : {
        *(.eh_frame)
    }
}
修正GDT基址(gdt.inc)
; code segment descriptor
gdt_code:
    dw 0xffff    ; segment length, bits 0-15
    dw 0x0       ; segment base, bits 0-15
    db 0x0       ; segment base, bits 16-23
    db 10011010b ; flags (8 bits)
    db 11001111b ; flags (4 bits) + segment length, bits 16-19
    db 0x0       ; 修正为0x0,基址设为0

; data segment descriptor
gdt_data:
    dw 0xffff    ; segment length, bits 0-15
    dw 0x0       ; segment base, bits 0-15
    db 0x0       ; segment base, bits 16-23
    db 10010010b ; flags (8 bits)
    db 11001111b ; flags (4 bits) + segment length, bits 16-19
    db 0x0       ; 修正为0x0,基址设为0
修正扇区读取逻辑(read_osload.inc)
[BITS 16]

read_osload:
    mov ah, 0        ; reset the drive function
    mov dl, 0        ; drive 0 is the floppy drive
    int 0x13          ; call the BIOS interrupt to reset drive
    jc disk_error     ; 重置失败跳转错误处理

    mov ax, 0x7E00   ; read address 0x7E00
    mov es, ax
    xor bx, bx

    mov ah, 0x02     ; read sector function
    mov al, 3        ; 读取3个扇区(足够容纳第二阶段+内核)
    mov ch, 0        ; track 0
    mov cl, 2        ; read sector 2
    mov dh, 0        ; head number
    mov dl, 0        ; drive number
    int 0x13         ; call the BIOS interrupt to read drive
    jc disk_error     ; 读取失败跳转错误处理
    ret

disk_error:
    mov ah, 0x0e
    mov al, 'E'
    int 0x10
    hlt
修正Makefile
all: elle.flp

osboot.bin: osboot.asm
    nasm -f bin osboot.asm -o osboot.bin

osload.o: osload.asm
    nasm osload.asm -f elf32 -o osload.o

core.o: core.c
    gcc -m32 -fno-pie -Wall -ffreestanding --no-builtin -c core.c -o core.o

stage2.bin: core.o osload.o
    ld -T link.ld -melf_i386 -o stage2.bin osload.o core.o 

elle.flp: osboot.bin stage2.bin
    cat osboot.bin stage2.bin > elle.flp
    truncate -s 1474560 elle.flp  ; 填充为标准1.44MB软盘大小

clean:
    rm -f osboot.bin osload.o core.o stage2.bin elle.flp
修正保护模式段初始化(osload.asm)
[BITS 32]
p_mode:
    mov ax, data_seg  ; 使用GDT定义的数据段选择子
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ebp, 0x90000
    mov esp, ebp

    [EXTERN _core]
    call _core
    hlt

验证方式

执行make clean && make生成镜像后,用QEMU启动:

qemu-system-i386 -fda elle.flp

正常流程会依次打印'B'、'L',最终在屏幕左上角显示'ELLOOS'。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 04:19:58