我正在开发自己的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
为什么它无法通过这种方式解决?
答案 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。对于某些硬件,此数字可能较小(低至每个磁道的扇区数)。
某些硬件将无法读取扇区:
使用BOCHS和QEMU加载高达64KiB的内核的简单方法是读取64个扇区(32KiB)到物理地址0x0000:0x8000,然后将64个扇区的第二个副本复制到0x1000:0x0000。您可以通过读取其他32KiB块来读取更大的内核。 0x0000:0x7e00和0x0000:0x8000之间的512个字节将未被使用。唯一真正的问题是确定要用于Int 21h/AH=02磁盘读取的气缸盖扇区(CHS)值 1 。
其他问题:
.bss
部分填充为零。使用链接程序脚本确定.bss
节的范围,并在调用 C 入口点之前将内存清零。.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个扇区磁盘读取所使用的值。