如何修复“ qemu:致命的:试图在RAM或ROM外部执行代码为0x000a0000”

时间:2019-03-26 16:38:05

标签: assembly x86 qemu bootloader osdev

我正在开发自己的Bootloader +内核。我创建一个项目并将其放置在github上:https://github.com/rprata/ubootlua(分支tmp-libc-implemenation)

我尝试使用QEMU运行boot.bin:

  

qemu-system-i386 -fda boot.bin -nographic -serial stdio -monitor none

但是发生崩溃:

> qemu-system-i386 -fda ./deploy/boot.bin -nographic -serial stdio -monitor none
> WARNING: Image format was not specified for './deploy/boot.bin' and probing guessed raw.
>         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
>         Specify the 'raw' format explicitly to remove the restrictions.
> qemu: fatal: Trying to execute code outside RAM or ROM at 0x000a0000
> 
> EAX=00000055 EBX=00018eb4 ECX=00018eb3 EDX=00000000
ESI=00000001 EDI=00000000 EBP=00016058 ESP=00015f94
EIP=0009ffae EFL=00000896 [-OS-AP-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00007c36 00000018
IDT=     00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000055 CCD=000000d1 CCO=ADDB    
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
> makefile:26: recipe for target 'run' failed
> make: *** [run] Aborted (core dumped)

我的boot.asm和linker.ld:

section .boot
bits 16                     ; We're working at 16-bit mode here
global boot

boot:
    mov ax, 0x2401          
    int 0x15                ; Enable A20 bit 

    mov ax, 0x3             ; Set VGA text mode 3
    int 0x10                ; Otherwise, call interrupt for printing the char   

    mov [disk],dl

    mov ah, 0x2             ;read sectors
    mov al, 60              ;sectors to read
    mov ch, 0               ;cylinder idx
    mov dh, 0               ;head idx
    mov cl, 2               ;sector idx
    mov dl, [disk]          ;disk idx
    mov bx, copy_target     ;target pointer
    int 0x13

    cli                     ; Disable the interrupts
    lgdt [gdt_pointer]      ; Load the gdt table
    mov eax, cr0            ; Init swap cr0...
    or eax,0x1              ; Set the protected mode bit on special CPU reg cr0
    mov cr0, eax
    jmp CODE_SEG:boot32     ; Long jump to the code segment


; base a 32 bit value describing where the segment begins
; limit a 20 bit value describing where the segment ends, can be multiplied by 4096 if granularity = 1
; present must be 1 for the entry to be valid
; ring level an int between 0-3 indicating the kernel Ring Level
; direction:
;  > 0 = segment grows up from base, 1 = segment grows down for a data segment
;  > 0 = can only execute from ring level, 1 = prevent jumping to higher ring levels
; read/write if you can read/write to this segment
; accessed if the CPU has accessed this segment
; granularity 0 = limit is in 1 byte blocks, 1 = limit is multiples of 4KB blocks
; size 0 = 16 bit mode, 1 = 32 bit protected mode
gdt_start:
    dq 0x0
gdt_code:
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 10011010b
    db 11001111b
    db 0x0
gdt_data:
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0
gdt_end:
gdt_pointer:
    dw gdt_end - gdt_start
    dd gdt_start
disk:
    db 0x0

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

;; Magic numbers
times 510 - ($ - $$) db 0

dw 0xaa55
copy_target:
bits 32
    msg:    db "Hello, World more than 512 bytes!", 0

boot32:
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax  
    ;mov esi, msg            ; SI now points to our message
    ;mov ebx, 0xb8000       ; vga memory position (0) 

.loop   lodsb               ; Loads SI into AL and increments SI [next char]
    or al, al               ; Checks if the end of the string
    jz halt                 ; Jump to halt if the end
    or eax,0x0200           ; The top byte defines the character colour in the buffer as an int value from 0-15 with 0 = black, 1 = blue and 15 = white. 
                            ; The bottom byte defines an ASCII code point
    mov word [ebx], ax      
    add ebx, 2              
    jmp .loop               ; Next iteration of the loop

halt:   
    mov esp, kernel_stack_top
    extern __start
    call __start
    cli
    hlt                     ; CPU command to halt the execution

section .bss
align 4
kernel_stack_bottom: equ $
    resb 16384 ; 16 KB
kernel_stack_top:

    ENTRY(boot)
    OUTPUT_FORMAT("binary")
    SECTIONS {
        . = 0x7c00;
        .text :
        {
            *(.boot)
            *(.text)
        }

        .rodata :
        {
            *(.rodata)
        }

        .data :
        {
            *(.data)
        }

        .bss :
        {
            *(.bss)
        }
    }

我的makefile的相关部分是:

NASM:=nasm
CC:=gcc
SRC_NASM:=./src/init/boot.asm
SRC_C:=./src/init/boot.c ./src/init/init.c ./src/init/version.c
LINKER:=./src/init/linker.ld
DEPLOY=./deploy
BUILD:=./build
BIN:=$(DEPLOY)/boot.bin
OBJ_NASM:=$(BUILD)/boot.o
CFLAGS:=-Wall -Werror -m32 -fno-pie -ffreestanding -mno-red-zone -fno-exceptions -nostdlib -I./src/include
LDFLAGS:=

export ARCH:=i386
export ZLIB_SUPPORT:=false

DEPENDENCIES:=libc
ifeq ($(ZLIB_SUPPORT),true)
DEPENDENCIES:=$(DEPENDENCIES) zlib
endif

all: $(DEPENDENCIES)
    mkdir -p $(DEPLOY)
    mkdir -p $(BUILD)
    $(NASM) $(SRC_NASM) -f elf32 -o $(OBJ_NASM)
    $(CC) $(SRC_C) $(OBJ_NASM) -o $(BIN) $(CFLAGS) -T $(LINKER) $(LDFLAGS)

run:
    qemu-system-i386 -fda $(BIN) -nographic -serial stdio -monitor none

为什么它无法通过这种方式解决?

2 个答案:

答案 0 :(得分:4)

此错误(“尝试在0x000a0000处在RAM或ROM外部执行代码”)通常表示控制流问题-例如CPU跳转或调用或返回到一个不可靠的地址,然后开始在未初始化的RAM(由CPU解释为add指令)中执行零,直到CPU到达旧版VGA区域(在0x000A0000处)。

对于导致错误的原因,我看起来并不难。

我没有真正看的原因是那并不重要。最终,您的引导加载程序必须执行一些操作,例如从BIOS获取内存映射(例如“ int 0x15,eax = 0xE820”),并希望自动检测内核的大小(而不是假设内核始终为30 KiB) ),要么要处理大于1 MiB的内核(例如Linux通常大于5 MiB),要么要加载某种“初始RAM磁盘”(对于微内核,这是唯一可能的情况)您可以假设内核小于可以在实模式下访问的约640 KiB RAM),可能想解压缩内核和/或“初始RAM磁盘”,将要检查内核是否健全(例如,可能通过检查标头和CRC),并且可能希望能够设置一个不错的图形视频模式(例如1920 * 1600,具有数百万种颜色)。它还将需要“ BIOS参数块”(用于未分区的设备,例如软盘),或者必须处理分区方案(并且不假定分区从磁盘的开始处开始)。

所有这些东西(以及更多的东西,例如检查A20是否真正启用)将太大而无法容纳512字节(所有这些都意味着在前512字节中切换到保护模式总是一个错误。 )。

这意味着您需要重新设计然后重新编写代码,无论您是否找到/修复当前的错误,现有的代码都将被丢弃,因此没有理由花费时间查找/修复当前的错误。错误/秒。

答案 1 :(得分:4)

主要问题是您没有将整个内核读入内存。您的代码最终将执行未初始化的内存(最有可能填充为零),到达扩展BIOS数据区域(在0xa0000的视频内存下方),然后最终在0xa0000的位置开始执行视频内存。 QEMU不允许执行视频内存,因此会导致错误。

修复此问题并不像初看起来那么容易。您在我的系统上的代码约为47300字节。 MBR 1个扇区,内核92个扇区。第一个问题是,并非所有硬件(和仿真器)都可以一次读取92个扇区。对于软盘驱动器,QEMU和BOCH的最大值为72;对于硬盘驱动器,QEMU和BOCH的最大值为128。对于某些硬件,此数字可能较小(低至每个磁道的扇区数)。

某些硬件将无法读取扇区:

  • 超出了64KiB段的限制。
  • 跨越多条轨道。并非所有的BIOS都支持多磁道读取和写入。 QEMU和BOCHS确实支持他们。
  • 如果BIOS使用直接内存访问(DMA)传输进行磁盘访问,则可能无法写入多个跨越64KiB边界的扇区(在物理内存中)。这意味着如果写入在物理地址0x10000之前开始并且在物理地址0x10000之后结束,则不能保证写入成功。与0x20000、0x30000、0x40000 ... 0x90000相同。 QEMU和BOCHS不允许跨此类边界进行磁盘传输。

使用BOCHS和QEMU加载高达64KiB的内核的简单方法是读取64个扇区(32KiB)到物理地址0x0000:0x8000,然后将64个扇区的第二个副本复制到0x1000:0x0000。您可以通过读取其他32KiB块来读取更大的内核。 0x0000:0x7e00和0x0000:0x8000之间的512个字节将未被使用。唯一真正的问题是确定要用于Int 21h/AH=02磁盘读取的气缸盖扇区(CHS)值 1

其他问题:

  • 将磁盘扇区读入内存时,应将堆栈( SS:SP )设置到不会意外覆盖的位置。如果在引导加载程序之后加载内核,则良好的位置是引导加载程序下方的 SS:SP 0x0000:0x7c000。为避免在设置 SS:SP 时发生中断,请在加载 SS 的指令之后紧接着在指令中设置 SP
  • 从不依赖任何包含您期望值的通用寄存器或段寄存器的值。 DL 是一个例外,因为在现代硬件上的几乎所有情况下,它都将包含启动驱动器号。有关更多信息,请参见我的bootloader tips
  • QEMU和其他仿真器可能无法读取文件中不存在的扇区。如果您读取的扇区多于磁盘映像中的扇区,则读取的扇区可能会失败。要解决此问题,请创建磁盘映像(方便使用1.44MiB软盘映像),然后将内核和引导加载程序的内容复制到文件的开头,而不会截断磁盘映像。 DD可以用于此目的。
  • 为帮助调试而不是将链接描述文件输出为 binary ,请使其默认为在ELF中输出。使用OBJCOPY将ELF文件复制到二进制文件。 ELF文件可用于存储调试信息。如果将QEMU和GDB用作远程调试器,这将很有用。
  • 您不能依赖包含零的内存。 GCC要求将.bss部分填充为零。使用链接程序脚本确定.bss节的范围,并在调用 C 入口点之前将内存清零。
  • 在调用 C 入口点之前,GCC要求清除方向标志(DF),以便字符串指令默认为向前移动。
  • 在您的 makefile 中,您使用GCC进行链接。如果不使用交叉编译器,则GCC可能会生成一个名为.note.gnu.build-id的特殊部分,该部分可能会干扰您的链接描述文件。要解决此问题,您可以告诉GCC使用LDFLAGS:=-Wl,--build-id=none取消显示此特殊部分。如果直接与LD链接,则不会创建此部分。

将所有这些更改考虑在内:

linker.ld

ENTRY(boot)
SECTIONS {
    . = 0x7c00;
    .boot :
    {
        *(.boot)
    }
    /* Place kernel right after boot sector on disk but set the
     * VMA (ORiGin point) to 0x8000 */
    . = 0x8000;
    __kernel_start = .;
    __kernel_start_seg = __kernel_start >> 4;
    .text : AT(0x7e00)
    {
        *(.text.start)
        *(.text*)
    }
    .rodata :
    {
        *(.rodata*)
    }
    .data :
    {
        *(.data)
    }
    /* Compute number of sectors that the kernel uses */
    __kernel_end = .;
    __kernel_size_sectors = (__kernel_end - __kernel_start + 511) / 512;

    .bss :
    {
        __bss_start = .;
        *(COMMON)
        *(.bss)
        . = ALIGN(4);
        __bss_end = .;
        /* Compute number of DWORDS that BSS section uses */
        __bss_sizel = (__bss_end - __bss_start) / 4;
    }
}

boot.asm

section .boot
bits 16                     ; We're working at 16-bit mode here
global boot

boot:
    xor ax, ax
    mov ds, ax
    mov ss, ax
    mov sp, 0x7c00          ; Set SS:SP just below bootloader

    cld                     ; DF=0 : string instruction forward movement
    mov ax, 0x2401
    int 0x15                ; Enable A20 bit

    mov ax, 0x3             ; Set VGA text mode 3
    int 0x10                ; Otherwise, call interrupt for printing the char

    mov [disk],dl

    ; Read 64 sectors from LBA 1, CHS=0,0,2 to address 0x0800:0
    mov ax, 0x0800
    mov es, ax              ;ES = 0x800

    mov ah, 0x2             ;read sectors
    mov al, 64              ;sectors to read
    mov ch, 0               ;cylinder idx
    mov dh, 0               ;head idx
    mov cl, 2               ;sector idx
    mov dl, [disk]          ;disk idx
    mov bx, 0               ;target pointer, ES:BX=0x0800:0x0000
    int 0x13

    ; Read 64 sectors from LBA 65, CHS=1,1,12 to address 0x1000:0
    mov ax, 0x1000
    mov es, ax              ;ES=0x1000

    mov ah, 0x2             ;read sectors
    mov al, 64              ;sectors to read
    mov ch, 1               ;cylinder idx
    mov dh, 1               ;head idx
    mov cl, 12              ;sector idx
    mov dl, [disk]          ;disk idx
    mov bx, 0x0000          ;target pointer, ES:BX=0x1000:0x0000
    int 0x13

    cli                     ; Disable the interrupts
    lgdt [gdt_pointer]      ; Load the gdt table
    mov eax, cr0            ; Init swap cr0...
    or eax,0x1              ; Set the protected mode bit on special CPU reg cr0
    mov cr0, eax
    jmp CODE_SEG:boot32     ; Long jump to the code segment


; base a 32 bit value describing where the segment begins
; limit a 20 bit value describing where the segment ends, can be multiplied by 4096
; if granularity = 1
; present must be 1 for the entry to be valid
; ring level an int between 0-3 indicating the kernel Ring Level
; direction:
;  > 0 = segment grows up from base, 1 = segment grows down for a data segment
;  > 0 = can only execute from ring level, 1 = prevent jumping to higher ring levels
; read/write if you can read/write to this segment
; accessed if the CPU has accessed this segment
; granularity 0 = limit is in 1 byte blocks, 1 = limit is multiples of 4KB blocks
; size 0 = 16 bit mode, 1 = 32 bit protected mode
gdt_start:
    dq 0x0
gdt_code:
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 10011010b
    db 11001111b
    db 0x0
gdt_data:
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0
gdt_end:
gdt_pointer:
    dw gdt_end - gdt_start
    dd gdt_start
disk:
    db 0x0

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

;; Magic numbers
times 510 - ($ - $$) db 0
dw 0xaa55

section .data
msg: db "Hello, World more than 512 bytes!", 0

bits 32
section .text.start
boot32:
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov esi, msg        ; SI now points to our message
    mov ebx, 0xb8000    ; vga memory position (0)

.loop:
    lodsb               ; Loads SI into AL and increments SI [next char]
    or al, al           ; Checks if the end of the string
    jz halt             ; Jump to halt if the end
    or eax,0x0200       ; The top byte defines the character colour in the buffer as
                        ; an int value from 0-15 with 0 = black, 1 = blue and 15 = white.
                        ; The bottom byte defines an ASCII code point
    mov word [ebx], ax
    add ebx, 2
    jmp .loop           ; Next iteration of the loop

halt:
    mov esp, kernel_stack_top
    extern __start
    extern __bss_start
    extern __bss_sizel

    ; Zero the BSS section
    mov ecx, __bss_sizel
    mov edi, __bss_start
    xor eax, eax
    rep stosd

    ; Call C entry point
    call __start
    cli
    hlt                 ; CPU command to halt the execution

section .bss
align 4
kernel_stack_bottom:
    resb 16384          ; 16 KB stack
kernel_stack_top:
通过添加以下make变量

修改 makefile

OC:=objcopy
DD:=dd
ELF:=$(DEPLOY)/boot.elf
通过将LDFLAGS更改为:

修改 makefile

LDFLAGS:=-Wl,--build-id=none
通过将all规则更改为:

修改 makefile

all: $(DEPENDENCIES)
        mkdir -p $(DEPLOY)
        mkdir -p $(BUILD)
        $(NASM) $(SRC_NASM) -f elf32 -o $(OBJ_NASM)
        $(CC) $(SRC_C) $(OBJ_NASM) -o $(ELF) $(CFLAGS) -T $(LINKER) $(LDFLAGS)
        $(OC) -O binary $(ELF) $(BIN)
        $(DD) if=/dev/zero of=$(BIN).tmp count=1440 bs=1024
        $(DD) if=$(BIN) of=$(BIN).tmp conv=notrunc
        mv $(BIN).tmp $(BIN)

替代解决方案

鉴于使用 Int 13 / AH = 2 失败的方式有很多,可以一次读取一个扇区并始终均匀地读取存储单元,从而避免了大多数问题512。

使用链接器脚本在内核旁边构建引导加载程序时,可以使用链接器确定内核的大小并计算需要读取的扇区数。

可以完成所需工作的上述先前代码的修订如下。

linker.ld

ENTRY(boot)
SECTIONS {
    . = 0x7c00;
    .boot :
    {
        *(.boot)
    }
    __kernel_start = .;
    __kernel_start_seg = __kernel_start >> 4;
    .text :
    {
        *(.text.start)
        *(.text*)
    }
    .rodata :
    {
        *(.rodata*)
    }
    .data :
    {
        *(.data)
    }
    /* Compute number of sectors that the kernel uses */
    __kernel_end = .;
    __kernel_size_sectors = (__kernel_end - __kernel_start + 511) / 512;

    .bss :
    {
        __bss_start = .;
        *(COMMON)
        *(.bss)
        . = ALIGN(4);
        __bss_end = .;
        /* Compute number of DWORDS that BSS section uses */
        __bss_sizel = (__bss_end - __bss_start) / 4;
    }
}

主要区别在于此链接描述文件从0x07e00而不是0x08000开始将内核加载到物理内存中。更完善的 boot.asm 可以使用链接器生成的值遍历所需的扇区,一次读取一个扇区,直到完成:

extern __kernel_size_sectors    ; Size of kernel in 512 byte sectors
extern __kernel_start_seg       ; Segment start of kernel will be laoded at

global boot

STAGE2_LBA_START equ 1          ; Logical Block Address(LBA) Stage2 starts on
                                ;     LBA 1 = sector after boot sector
                                ; Logical Block Address(LBA) Stage2 ends at
STAGE2_LBA_END   equ STAGE2_LBA_START + __kernel_size_sectors
DISK_RETRIES     equ 3          ; Number of times to retry on disk error

bits 16
section .boot

boot:
; Include a BPB (1.44MB floppy with FAT12) to be more compatible with USB floppy media
;%include "src/init/bpb.inc"

boot_start:
    xor ax, ax                  ; DS=SS=ES=0 for stage2 loading
    mov ds, ax
    mov ss, ax                  ; Stack at 0x0000:0x7c00
    mov sp, 0x7c00
    cld                         ; Set string instructions to use forward movement

    ; Read Stage2 1 sector at a time until stage2 is completely loaded
load_stage2:
    mov [bootDevice], dl        ; Save boot drive
    mov di, __kernel_start_seg  ; DI = Current segment to read into
    mov si, STAGE2_LBA_START    ; SI = LBA that stage2 starts at
    jmp .chk_for_last_lba       ; Check to see if we are last sector in stage2

.read_sector_loop:
    mov bp, DISK_RETRIES        ; Set disk retry count

    call lba_to_chs             ; Convert current LBA to CHS
    mov es, di                  ; Set ES to current segment number to read into
    xor bx, bx                  ; Offset zero in segment

.retry:
    mov ax, 0x0201              ; Call function 0x02 of int 13h (read sectors)
                                ;     AL = 1 = Sectors to read
    int 0x13                    ; BIOS Disk interrupt call
    jc .disk_error              ; If CF set then disk error

.success:
    add di, 512>>4              ; Advance to next 512 byte segment (0x20*16=512)
    inc si                      ; Next LBA

.chk_for_last_lba:
    cmp si, STAGE2_LBA_END      ; Have we reached the last stage2 sector?
    jl .read_sector_loop        ;     If we haven't then read next sector

.stage2_loaded:
    jmp stage2                  ; Jump to second stage

.disk_error:
    xor ah, ah                  ; Int13h/AH=0 is drive reset
    int 0x13
    dec bp                      ; Decrease retry count
    jge .retry                  ; If retry count not exceeded then try again

error_end:
    ; Unrecoverable error; print drive error; enter infinite loop
    mov si, diskErrorMsg        ; Display disk error message
    call print_string
    cli
.error_loop:
    hlt
    jmp .error_loop

; Function: print_string
;           Display a string to the console on display page 0
;
; Inputs:   SI = Offset of address to print
; Clobbers: AX, BX, SI

print_string:
    mov ah, 0x0e                ; BIOS tty Print
    xor bx, bx                  ; Set display page to 0 (BL)
    jmp .getch
.repeat:
    int 0x10                    ; print character
.getch:
    lodsb                       ; Get character from string
    test al,al                  ; Have we reached end of string?
    jnz .repeat                 ;     if not process next character
.end:
    ret

;    Function: lba_to_chs
; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
;              Works for all valid FAT12 compatible disk geometries.
;
;   Resources: http://www.ctyme.com/intr/rb-0607.htm
;              https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
;              https://stackoverflow.com/q/45434899/3857942
;              Sector    = (LBA mod SPT) + 1
;              Head      = (LBA / SPT) mod HEADS
;              Cylinder  = (LBA / SPT) / HEADS
;
;      Inputs: SI = LBA
;     Outputs: DL = Boot Drive Number
;              DH = Head
;              CH = Cylinder (lower 8 bits of 10-bit cylinder)
;              CL = Sector/Cylinder
;                   Upper 2 bits of 10-bit Cylinders in upper 2 bits of CL
;                   Sector in lower 6 bits of CL
;
;       Notes: Output registers match expectation of Int 13h/AH=2 inputs
;
lba_to_chs:
    push ax                     ; Preserve AX
    mov ax, si                  ; Copy LBA to AX
    xor dx, dx                  ; Upper 16-bit of 32-bit value set to 0 for DIV
    div word [sectorsPerTrack]  ; 32-bit by 16-bit DIV : LBA / SPT
    mov cl, dl                  ; CL = S = LBA mod SPT
    inc cl                      ; CL = S = (LBA mod SPT) + 1
    xor dx, dx                  ; Upper 16-bit of 32-bit value set to 0 for DIV
    div word [numHeads]         ; 32-bit by 16-bit DIV : (LBA / SPT) / HEADS
    mov dh, dl                  ; DH = H = (LBA / SPT) mod HEADS
    mov dl, [bootDevice]        ; boot device, not necessary to set but convenient
    mov ch, al                  ; CH = C(lower 8 bits) = (LBA / SPT) / HEADS
    shl ah, 6                   ; Store upper 2 bits of 10-bit Cylinder into
    or  cl, ah                  ;     upper 2 bits of Sector (CL)
    pop ax                      ; Restore scratch registers
    ret

; Uncomment these lines if not using a BPB (via bpb.inc)
%ifndef WITH_BPB
numHeads:        dw 2           ; 1.44MB Floppy has 2 heads & 18 sector per track
sectorsPerTrack: dw 18
%endif

bootDevice:      db 0x00
diskErrorMsg:    db "Unrecoverable disk error!", 0

; Pad boot sector to 510 bytes and add 2 byte boot signature for 512 total bytes
TIMES 510-($-$$) db  0
dw 0xaa55

section .data
msg: db "Hello, World more than 512 bytes!", 0

; base a 32 bit value describing where the segment begins
; limit a 20 bit value describing where the segment ends, can be multiplied by 4096
; if granularity = 1
; present must be 1 for the entry to be valid
; ring level an int between 0-3 indicating the kernel Ring Level
; direction:
;  > 0 = segment grows up from base, 1 = segment grows down for a data segment
;  > 0 = can only execute from ring level, 1 = prevent jumping to higher ring levels
; read/write if you can read/write to this segment
; accessed if the CPU has accessed this segment
; granularity 0 = limit is in 1 byte blocks, 1 = limit is multiples of 4KB blocks
; size 0 = 16 bit mode, 1 = 32 bit protected mode
gdt_start:
    dq 0x0
gdt_code:
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 10011010b
    db 11001111b
    db 0x0
gdt_data:
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0
gdt_end:
gdt_pointer:
    dw gdt_end - gdt_start
    dd gdt_start
disk:
    db 0x0

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

bits 16
section .text.start
stage2:
    cli                         ; Disable the interrupts
    mov ax, 0x2401
    int 0x15                    ; Enable A20 bit

    lgdt [gdt_pointer]          ; Load the gdt table
    mov eax, cr0                ; Init swap cr0...
    or eax,0x1                  ; Set the protected mode bit on special CPU reg cr0
    mov cr0, eax
    jmp CODE_SEG:startpm        ; FAR JMP to the code segment

bits  32
startpm:
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov esi, msg                ; SI now points to our message
    mov ebx, 0xb8000            ; vga memory position (0)

.loop:
    lodsb                       ; Loads SI into AL and increments SI [next char]
    or al, al                   ; Checks if the end of the string
    jz halt                     ; Jump to halt if the end
    or eax,0x0200               ; The top byte defines the character colour in the
                                ; buffer as an int value from 0-15 with 0 = black,
                                ; 1 = blue and 15 = white.
                                ; The bottom byte defines an ASCII code point
    mov word [ebx], ax
    add ebx, 2
    jmp .loop                   ; Next iteration of the loop

halt:
    mov esp, kernel_stack_top
    extern __start
    extern __bss_start
    extern __bss_sizel

    ; Zero the BSS section
    mov ecx, __bss_sizel
    mov edi, __bss_start
    xor eax, eax
    rep stosd

    ; Call C entry point
    call __start
    cli
    hlt                         ; CPU command to halt the execution

section .bss
align 4
kernel_stack_bottom:
    resb 16384                  ; 16 KB stack
kernel_stack_top:

boot.asm 大致基于我在另一个Stackoverflow question and answer中提出的引导程序。主要区别在于,链接器通过链接器脚本计算许多所需的信息,而不是直接将其编码/包含在汇编文件中。此代码还将A20线路的启用和进入保护模式的状态转移到第二阶段。如果将来需要扩展引导加载程序中的功能,则可以释放空间。

如果要构建引导加载程序以在真实硬件上用作未分区的介质,则可以在文件bpb.inc中找到1.44MiB BIOS参数块(BPB)的副本。这对于使用软盘仿真(FDD)在USB介质上启动很有用。要启用它,只需从此行中删除;

; %include "src/init/bpb.inc"

脚语

  • 1 有一个formula可以将基于零的逻辑博克地址转换为一组CHS值:

    C = LBA ÷ (HPC × SPT)
    H = (LBA ÷ SPT) mod HPC
    S = (LBA mod SPT) + 1
    

    LBA 0是引导程序。如果内核位于引导加载程序之后的连续扇区中,则内核的起始位置为LBA1。内核的第二个32KiB块将位于LBA 65(64 + 1)。对于1.44MiB软盘,HPC = 2和SPT = 18。从计算得出LBA 0 = CHS(0,0,2),LBA 65 = CHS(1,1,12)。这些是 boot.asm 的第一版中64个扇区磁盘读取所使用的值。