二级引导程序与链接脚本配置故障:启动流程排查求助
二级引导程序与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
相关产品推荐
相关产品推荐

