Как исправить «qemu: fatal: Попытка выполнить код вне ОЗУ или ПЗУ в 0x000a0000» - PullRequest
1 голос
/ 26 марта 2019

Я занимаюсь разработкой собственного загрузчика + ядра. Я создаю проект и помещаю его на github: https://github.com/rprata/ubootlua (ветка tmp-libc-реализация)

Я попытался запустить свой boot.bin, используя QEMU:

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)
        }
    }

Соответствующая часть моего make-файла:

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 ]

4 голосов
/ 27 марта 2019

Основная проблема заключается в том, что вы не читаете все свое ядро ​​в память. В конечном итоге ваш код выполняет неинициализированную память (скорее всего, заполненную нулями), достигает расширенной области данных BIOS (чуть ниже видеопамяти в 0xa0000), а затем в конечном итоге начинает выполнять видеопамяти в 0xa0000. QEMU не позволяет использовать видеопамять, поэтому вы получаете источник ошибки.

Исправить это не так просто, как может показаться на первый взгляд. Ваш код в моей системе был около 47300 байт. 1 сектор для MBR и 92 для ядра. Первая проблема заключается в том, что не все оборудование (и эмуляторы) могут считывать 92 сектора одновременно. QEMU и BOCH максимально на 72 для гибких дисков и 128 для жестких дисков. Это число может быть меньше для некоторых аппаратных средств (до числа секторов на дорожку).

Некоторое оборудование не будет читать секторы:

  • Это выходит за пределы сегмента 64 КБ.
  • Это охватывает более одного трека. Не все BIOS поддерживают многодорожечное чтение и запись. QEMU и BOCHS поддерживают их.
  • Если BIOS использует передачи прямого доступа к памяти (DMA) для доступа к диску, вы не сможете записать несколько секторов, которые пересекают границу 64 КБ (в физической памяти). Это означает, что вы не можете гарантировать успешную запись, если она начинается до физического адреса 0x10000 и заканчивается после. То же самое для 0x20000, 0x30000, 0x40000 ... 0x90000. QEMU и BOCHS не допускают перенос дисков через такие границы.

Простой способ загрузить ядро ​​размером до 64 КБ с помощью BOCHS и QEMU - это прочитать 64 сектора (32 КБ) по физическому адресу 0x0000: 0x8000, а затем сделать вторую копию из 64 секторов в 0x1000: 0x0000. Вы можете прочитать ядро ​​большего размера, прочитав дополнительные куски по 32 КБ. 512 байт между 0x0000: 0x7e00 и 0x0000: 0x8000 будут неиспользованными. Единственный реальный улов - определение значений сектора цилиндра (CHS) 1 , которые будут использоваться для чтения диска Int 21h / AH = 02 .

Другие вопросы:

  • При чтении секторов диска в память вы должны установить в стек ( SS: SP ) место, которое вы случайно не перезапишите. Если вы загружаете ядро ​​после загрузчика, хорошее расположение будет SS: SP 0x0000: 0x7c000 ниже загрузчика. Чтобы избежать прерываний, возникающих при установке SS: SP , установите SP в инструкции, следующей сразу за инструкцией, которая загружает SS .
  • Никогда не полагайтесь на значение любого регистра общего назначения или сегмента, содержащего ожидаемое вами значение. DL является исключением, поскольку почти во всех случаях на современном оборудовании он будет содержать номер загрузочного диска. См. Мои советы по загрузчику для получения дополнительной информации.
  • QEMU и другие эмуляторы могут не считывать сектора, которых нет в файле. Если вы читаете больше секторов, чем указано в образе диска, чтение сектора может завершиться ошибкой. Чтобы обойти это, создайте образ диска (удобно использовать дискету размером 1,44 МБ) и скопируйте содержимое ядра и загрузчика в начало файла без усечения образа диска. DD может использоваться для этой цели.
  • Чтобы облегчить отладку, а не выводить сценарий компоновщика как двоичный , по умолчанию используйте его для вывода в формате ELF. Используйте OBJCOPY, чтобы скопировать файл ELF в двоичный файл. Файл ELF может использоваться для хранения отладочной информации. Это полезно при использовании QEMU и GDB в качестве удаленного отладчика.
  • Вы не можете полагаться на память, содержащую нули. GCC требует, чтобы раздел .bss был заполнен нулями. Используйте скрипт компоновщика, чтобы определить экстенты раздела .bss и обнулить память перед вызовом точки входа C .
  • Перед вызовом точки входа C GCC требует, чтобы флаг направления (DF) был очищен, чтобы строковые инструкции по умолчанию передавали движение вперед.
  • В вашем make-файле вы используете 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:

Изменить makefile , добавив следующие переменные make:

OC:=objcopy
DD:=dd
ELF:=$(DEPLOY)/boot.elf

Изменить makefile , изменив LDFLAGS на:

LDFLAGS:=-Wl,--build-id=none

Измените makefile , изменив правило all на:

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:

Эта загрузка.asm свободно основан на загрузчике, который я предложил в другом вопросе Stackoverflow и ответе .Основное отличие состоит в том, что компоновщик вычисляет большую часть необходимой информации с помощью сценария компоновщика, а не кодируется / включается непосредственно в файл сборки.Этот код также перемещает включение линии A20 и переход в защищенный режим на второй этап.Это освобождает пространство, если вам необходимо расширить возможности загрузчика в будущем.

Если вы строите свой загрузчик для использования на реальном оборудовании в качестве неразмеченного носителя - копия блока параметров BIOS 1.44MiB(BPB) можно найти в файле bpb.inc .Это может быть полезно для загрузки с USB-носителя с использованием эмуляции дискеты (FDD).Чтобы включить его, просто удалите ; из этой строки:

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

Сноски

  • 1 Существует формула для преобразования нулевого логического адреса Bock в набор значений CHS:

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

    LBA 0 - это загрузочный сектор.Если ядро ​​находится в смежных секторах после загрузчика, то начало ядра находится на LBA 1. Второй кусок ядра 32 КБ будет на LBA 65 (64 + 1).Для 1.44MiB дискеты HPC = 2 и SPT = 18.Из расчета LBA 0 = CHS (0,0,2) и LBA 65 = CHS (1,1,12).Это значения, используемые 64-секторными считываниями диска в первой версии boot.asm .

4 голосов
/ 26 марта 2019

Эта ошибка («Попытка выполнить код вне ОЗУ или ПЗУ в 0x000a0000») обычно указывает на проблемы с потоком управления - например, ЦП перескочил или вызвал или возвратился по изворотливому адресу, затем начал выполнять нули в неинициализированной ОЗУ (которые интерпретируютсяЦП как add инструкции), пока ЦП не достиг устаревшей области VGA (в 0x000A0000).

По причине ошибки я не выглядел слишком усердно.

ПричинаЯ действительно не смотрю, что это не имеет значения.В конечном счете, ваш загрузчик должен делать такие вещи, как получение карты памяти из BIOS (например, «int 0x15, eax = 0xE820»), он захочет автоматически определить размер ядра (вместо того, чтобы предполагать, что ядро ​​всегда будет точно 30 КиБ), либо захочет работать с ядрами, размер которых превышает 1 МБ (например, Linux часто превышает 5 МБ), либо потребуется также загрузить некий «начальный RAM-диск» (для микроядер, что является единственным вероятным случаем).где вы можете предположить, что ядро ​​будет меньше, чем ~ 640 КБ ОЗУ, к которому у вас есть доступ в реальном режиме), возможно, вы захотите распаковать ядро ​​и / или «начальный RAM-диск», захотите проверить, является ли ядро ​​нормальным (например, возможнопроверяя заголовки и CRC), и, возможно, захотите настроить хороший графический режим видео (например, 1920 * 1600 с миллионами цветов).Он также будет нуждаться в «блоке параметров BIOS» (для неразмеченных устройств - например, дискеты) или должен будет обрабатывать схему разбиения (и не предполагать, что раздел начинается в начале диска).

Все эти вещи (и даже больше, такие как проверка, был ли A20 фактически включен или нет) будут слишком большими, чтобы уместиться в 512 байтов (и все это подразумевает, что переключение в защищенный режим в первых 512 байтах всегда является ошибкой).

Это означает, что вам нужно будет перепроектировать, а затем переписать ваш код, и существующий код будет удален независимо от того, найдете ли вы / исправите текущую ошибку / и, поэтому нет причин тратить время на поиск / исправление.текущий баг / с.

...