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

问题描述 投票:1回答:2

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

我尝试使用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

为什么它会以这种方式失败,我该如何解决?

assembly x86 qemu bootloader osdev
2个回答
4
投票

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

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

某些硬件不会读取扇区:

  • 这超出了64KiB段限制。
  • 这跨越多个轨道。并非所有BIOS都支持多轨读写。 QEMU和BOCHS确实支持他们。
  • 如果BIOS使用直接内存访问(DMA)传输进行磁盘访问,则可能无法写入遍历64KiB边界(在物理内存中)的多个扇区。这意味着如果写入在物理地址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可用于此目的。
  • 为了帮助调试而不是将链接器脚本输出为二进制,请将其默认输出为ELF。使用OBJCOPY将ELF文件复制到二进制文件。 ELF文件可用于存储调试信息。如果将QEMU和GDB用作远程调试器,这将非常有用。
  • 您不能依赖包含零的内存。海湾合作委员会要求将.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将基于零的逻辑Bock地址转换为一组CHS值: C = LBA ÷ (HPC × SPT) H = (LBA ÷ SPT) mod HPC S = (LBA mod SPT) + 1 LBA 0是引导程序。如果内核在引导加载程序之后位于连续扇区中,则内核的启动位于LBA 1.内核的第二个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扇区磁盘读取使用的值。

4
投票

该错误(“试图在RAM或ROM外执行代码为0x000a0000”)通常表示控制流程问题 - 例如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字节中切换到保护模式始终是一个错误)。

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

© www.soinside.com 2019 - 2024. All rights reserved.