引导加载程序不会跳转到第 2 阶段并显示消息

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

我正在为 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)"

HxD 测试: enter image description here 问题:

在 QEMU 中运行引导加载程序时,它不会按预期跳转到第 2 阶段。消息“Hello from Stage 2!:)”不会出现。我已经仔细检查了段和偏移值,并使用 HxD 验证了

.img
文件,一切似乎都是正确的。

对于可能导致此问题的原因有什么建议吗?是否有任何常见问题或错误可能会阻止跳转到第 2 阶段?

我编写并组装了一个具有两个阶段的简单引导加载程序。第一阶段应该是:

  1. 设置段寄存器和堆栈。

  2. 将第二阶段从磁盘读入内存。

  3. 跳转到第二阶段加载的地址。

第二阶段是一个简单的程序,使用 BIOS 中断 0x10 进行文本输出,显示消息“Hello from Stage 2!:)”。

我使用 NASM 来汇编代码并使用 QEMU 进行仿真。使用 Makefile 将引导加载程序和阶段组合成可引导映像,该文件创建一个映像文件,其中第一阶段位于开头,第二阶段紧随其后。

assembly nasm x86-16 bootloader osdev
1个回答
0
投票

ORG
是关于什么

当您编写

[org 0x7C00]
时,您向汇编器(本例中为 NASM)承诺,您的代码将驻留在内存中指定的偏移地址(本例中为 0x7C00)。如果例如。您要包含(在下面的代码片段中)指令
mov bx, Start
,您会看到 NASM 对该指令进行编码,就像它一样
mov bx, 0x7C00

由于 BIOS 将引导加载程序加载到线性地址 0x00007C00 的内存中,因此段寄存器只能有一项设置是正确的。零对于
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
评论很重要

“从磁盘加载第 2 阶段引导加载程序”部分有很多不正确的注释。

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
  • 无需重新加载我们之前设置为 0 的 ES。无论如何,您希望它保存 0x7C0,但正在将 0x0201 加载到其中。这两个值都是错误的!
  • 既然您到目前为止都没有修改 DL 寄存器,所以您最好相信 BIOS 已经提供了可供使用的驱动器号,换句话说“不要包含
  • 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。 第二阶段由
you加载,因此不需要0xAA55签名。您可以安全地以TIMES 512-($-$$) db 0
结束。

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