我正在为 32 位内核开发引导加载程序,并且在引导加载程序从第 1 阶段过渡到第 2 阶段时遇到了问题。该代码似乎没有按预期跳转到第 2 阶段。我使用 NASM 进行汇编,使用 QEMU 进行仿真。这是两个阶段的代码以及用于构建引导加载程序的 Makefile。
第一阶段代码:
[BITS 16] ; We are working in 16-bit Real Mode
[org 0x7c00] ; The origin (starting address) of the bootloader in memory, which is 0x7C00 as loaded by the BIOS.
; This is the physical address where the bootloader is loaded into memory.
start: ; Start of execution, this label marks the entry point of the code.
jmp main ; Jump to the 'main' label to skip over data (if present), ensuring the code runs properly.
main: ; Main routine of the bootloader begins here.
; -------------------------
; Setup segment registers
; -------------------------
cli ; Clear interrupts to ensure no interrupts occur while setting up segments.
mov ax, 0x7C0 ; Set AX to 0x7C0 (which is 0x7C00 >> 4).
; Explanation: We are using segment:offset addressing in real mode.
; Physical address = Segment * 16 + Offset
; So, the segment 0x7C0 * 16 = 0x7C00 (physical address).
; This is the base segment for our code loaded by BIOS at the physical address 0x7C00.
mov ds, ax ; Set Data Segment (DS) to 0x7C0. DS points to the bootloader code/data in memory.
mov es, ax ; Set Extra Segment (ES) to 0x7C0. ES is also set to point to our code/data.
mov fs, ax ; Set FS to 0x7C0.
mov gs, ax ; Set GS to 0x7C0.
; -------------------------
; Setup stack
; -------------------------
xor ax, ax ; Set AX to 0 (clear register).
mov ss, ax ; Set Stack Segment (SS) to 0 (base of memory).
mov sp, 0xFFFF ; Set the Stack Pointer (SP) to the top of memory. The stack grows downwards from 0xFFFF.
; Although SS is set to 0x0000 here, the actual physical address for the stack
; will be 0x0000:0xFFFF = 0xFFFF (top of the 64KB memory block).
sti ; Re-enable interrupts after segment and stack setup is complete.
; -------------------------
; Load Stage 2 bootloader from disk
; -------------------------
mov ah, 02h ; BIOS Interrupt 13h, Function 02h: Read sectors from the disk.
mov al, 01h ; Read 63 sectors (this should correspond to the size of Stage 2). Ensure this number does not exceed the size of Stage 2 to avoid reading unnecessary code.
mov ch, 00h ; Set Cylinder number to 1 (since Stage 1 is at Cylinder 0, Stage 2 starts at Cylinder 1).
mov cl, 02h ; Set Sector number to 1 (the first sector on the cylinder to read from).
mov dh, 00h ; Set Head number to 0 (assuming we are using Head 0 for now).
mov dl, 0x80
mov es, ax ; Set ES to the address where Stage 2 should be loaded (0x7C0).
mov bx, 0x8000 ; Set BX to 0x8000, the memory address where Stage 2 will be loaded.
; Stage 2 will be loaded into the physical address 0x8000:0000 (0x08000 physical address).
int 13h ; Call BIOS interrupt 13h to read the specified sectors into memory.
jc disk_read_error ; If carry flag is set (indicating an error), jump to the error handler.
pass: ; If the disk read was successful (carry flag is cleared), continue from here.
jmp 0x0800:0x0000 ; Jump to the loaded Stage 2 at address 0x0800:0x0000 (this is where Stage 2 resides).
; Here, 0x0800 is the segment, and 0x0000 is the offset.
; Physical address = 0x0800 * 16 + 0x0000 = 0x8000, where Stage 2 is loaded.
disk_read_error:
int 18h ; If the disk read fails, call INT 18h to attempt a boot from a different device (like network boot).
; This error massage will occur --> IO write(0x01f0): current command is 20h
TIMES 510-($-$$) DB 0 ; Pad the bootloader to ensure it is exactly 512 bytes, with zeros filling the remaining space.
DW 0xAA55 ; The boot signature (magic number) required for the BIOS to recognize this as a bootable sector.
第 2 阶段代码:
[BITS 16]
[ORG 0x8000]
start:
mov si, message
call print_string
cli
hlt
print_string:
mov ah, 0x0E
.next_char:
lodsb
cmp al, 0
je done
int 0x10
jmp .next_char
done:
ret
message db "Hello from Stage 2! :)", 0
TIMES 510-($-$$) db 0
DW 0xAA55
生成文件:
PROJECT_ROOT := $(CURDIR)
STAGE1_SRC := $(PROJECT_ROOT)\Boot\Stage1\stage1.asm
STAGE2_SRC := $(PROJECT_ROOT)\Boot\Stage2\stage2.asm
STAGE1_BIN := $(PROJECT_ROOT)\Boot\Stage1\stage1.bin
STAGE2_BIN := $(PROJECT_ROOT)\Boot\Stage2\stage2.bin
IMG := C:\Users\ilanv\OS_32Bit\Code\bootloader.img
QEMU_IMG := "C:/QEMU/qemu-img.exe"
DD := "C:/dd/dd.exe"
output: $(STAGE1_BIN) $(STAGE2_BIN) $(IMG)
$(STAGE1_BIN): $(STAGE1_SRC)
@echo "Assembling Stage 1..."
nasm -f bin "$(STAGE1_SRC)" -o "$(STAGE1_BIN)"
$(STAGE2_BIN): $(STAGE2_SRC)
@echo "Assembling Stage 2..."
nasm -f bin "$(STAGE2_SRC)" -o "$(STAGE2_BIN)"
$(IMG): $(STAGE1_BIN) $(STAGE2_BIN)
@echo "Creating bootloader.img..."
$(QEMU_IMG) create -f raw "$(IMG)" 10M
$(DD) if=$(STAGE1_BIN) of=$(IMG) bs=512 count=1
$(DD) if=$(STAGE2_BIN) of=$(IMG) bs=512 seek=1
clean:
@echo "Cleaning up..."
if exist "$(STAGE1_BIN)" del "$(STAGE1_BIN)"
if exist "$(STAGE2_BIN)" del "$(STAGE2_BIN)"
if exist "$(IMG)" del "$(IMG)"
在 QEMU 中运行引导加载程序时,它不会按预期跳转到第 2 阶段。消息“Hello from Stage 2!:)”不会出现。我已经仔细检查了段和偏移值,并使用 HxD 验证了
.img
文件,一切似乎都是正确的。
对于可能导致此问题的原因有什么建议吗?是否有任何常见问题或错误可能会阻止跳转到第 2 阶段?
我编写并组装了一个具有两个阶段的简单引导加载程序。第一阶段应该是:
设置段寄存器和堆栈。
将第二阶段从磁盘读入内存。
跳转到第二阶段加载的地址。
第二阶段是一个简单的程序,使用 BIOS 中断 0x10 进行文本输出,显示消息“Hello from Stage 2!:)”。
我使用 NASM 来汇编代码并使用 QEMU 进行仿真。使用 Makefile 将引导加载程序和阶段组合成可引导映像,该文件创建一个映像文件,其中第一阶段位于开头,第二阶段紧随其后。
ORG
是关于什么当您编写
[org 0x7C00]
时,您向汇编器(本例中为 NASM)承诺,您的代码将驻留在内存中指定的偏移地址(本例中为 0x7C00)。如果例如。您要包含(在下面的代码片段中)指令mov bx, Start
,您会看到 NASM 对该指令进行编码,就像它一样 mov bx, 0x7C00
。ORG 0x7C00
来说是正确的。
Start:
xor ax, ax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov sp, 0x7C00
cli
/sti
,只要您按顺序一起加载 SS 和 SP,并且您知道处理器支持 FS 和 GS。mov sp, 0xFFFF
,而是写
mov sp, 0xFFFE
。
mov al, 01h ; Read 63 sectors
mov ch, 00h ; Set Cylinder number to 1
mov cl, 02h ; Set Sector number to 1
0x00008000物理地址0x0000:0x8000
(零段)或
0x0800:0x0000
(零偏移)。因为您在第 2 阶段中包含了
[ORG 0x8000]
,所以更明智的选择是将段保留为零,所以写下
jmp 0x0000:0x8000
。
mov dl, 0x80
”。
mov dh, 0 ; Head 0
mov cx, 0x0002 ; Cylinder 0, Sector 2
mov bx, 0x8000 ; Buffer ES:BX = 0000h:8000h
mov ax, 0x0201 ; Function 2, Sectors 1
int 13h
jc disk_read_error
jmp 0x0000:0x8000
第二阶段cld
指令可能是明智之举,因为您正在使用
lodsb
字符串原语并且您希望依赖自动递增 SI。 第二阶段由
TIMES 512-($-$$) db 0
结束。