加载引导加载程序的第二阶段

Loading second stage of a bootloader

我正在尝试为 x86 机器创建一个小型操作系统,并开始为一个相当小的引导加载程序编写代码。我创建的引导加载程序非常简单,它从直接位于主引导记录之后的扇区加载第二个小引导加载程序并跳转到该代码。主引导记录中的引导加载程序代码似乎 运行 没问题,当它试图跳转到第二阶段引导加载程序时出现问题。第二阶段引导加载程序应该输出一个指示成功的字母(字母 S),这样我就可以知道代码正在执行。问题是屏幕上什么也没有出现,所以我怀疑第二阶段引导加载程序从未执行过。我使用的代码如下:

主引导记录中的引导加载程序:

[BITS 16] ; 16 bit mode
[ORG 0x7C00] ; Boot loader start address

Boot:
    ; Initial, dl contains drive number
    ; Set data segment to code segment
    mov ax, cs
    mov ds, ax
    mov es, ax
    ; Set the stack segment to 0xA000
    add ax, 0xA000
    mov ss, ax
    mov sp, 0x00
    ; Reset the drive, dl contains drive number
    mov ah, 0x00
    int 0x13
    ; Read from drive, dl contains drive number
    ;     Set up output location to 0x7E00: 0x00
    mov ax, 0x7E00
    mov es, ax ; Load to 0x7E00 : 0x00
    mov bx, 0x00
ReadDrive:
    mov ah, 0x02
    mov al, 0x01 ; Read 1 sector
    mov ch, 0x00 ; Read on cylinder 0
    mov cl, 0x02 ; Read sector 2
    mov dh, 0x00 ; Head number 0
    int 0x13

    jnc Success
    ; Print error (character F)
    mov al, 0x46
    call PrintChar
    jmp ReadDrive ; Retry

PrintChar: ; Prints a single character
    pusha
    mov ah, 0x09
    mov bh, 0x00
    mov bl, 0x0F
    mov cx, 0x01
    int 0x10
    popa
    ret

Success:
    jmp 0x7E00:0x00 ; Jump to 2nd stage bootloader

TIMES 510 - ($ - $$) db 0
DW 0xAA55 ; Boot signature

第二阶段bootloader的代码:

[BITS 16]
[ORG 0x7E00]

Boot2:
    ; Prints the character S to the screen
    mov al, 0x53
    mov ah, 0x09
    mov bh, 0x00
    mov bl, 0x0F
    mov cx, 0x01
    int 0x10
    jmp $ ; Loop forever

TIMES 512 - ($ - $$) db 0 ; Fill rest of block

此代码是使用以下代码编译并写入驱动器的:

nasm boot.asm -o boot.bin -f bin
nasm boot2.asm -o boot2.bin -f bin
dd if=boot.bin of=/dev/sd3 bs=512
dd if=boot2.bin of=/dev/sd3 bs=512 seek=1

写入此代码的设备是一个 16GB 的 USB 驱动器。我用来启动此代码的计算机支持从 USB 启动,并像任何其他硬盘驱动器一样启动它们。代码好像没有执行是什么原因?

您的代码中似乎存在许多问题。我将尝试识别其中的一些。一些有用的参考 material 可以在我为 Stackoveflow 写的一些答案中找到。

  • 给出了您不想在引导加载程序中做出的一般准则和假设
  • 关于未正确设置 DS 以及在访问内存变量时出现垃圾的陷阱。这在某种程度上适用于您的第二阶段
  • 与您的问题相似的 也可以提供一些有用的信息。

堆栈

您确实设置了堆栈,但它可能会与视频内存重叠。虽然这可能与您的问题无关,但它是一个潜在问题。使用此代码:

add ax, 0xA000
mov ss, ax
mov sp, 0x00

您设置 SS =0xa000,并且 SP =0x0000。这将设置堆栈,但不幸的是,压入堆栈的第一个值将位于 0xa000:(0x0000-2)= 0xa000:0xfffe 。 0xa000:0xfffe 恰好可能属于视频内存。也许你打算做 ss=0x9000 所以堆栈上的第一个值将在 0x9000:0xfffe 。那里也有一个障碍。 Extended Bios Data Area (EBDA) 可以在那个区域。某些 BIOS 错误地 return 该区域的大小错误。在大多数情况下,它的大小为 0k 到 4k,正好在物理地址 0xa0000 下方。如果您考虑到最坏的情况,我会选择低于该情况的堆栈。

add ax, 0x9000
mov ss, ax
mov sp, 0xF000  ; Bottom of stack at 0x9000:0xF000

内存地址 0x7e00

这里有两个问题。在您的问题中,您建议您尝试将第二阶段读入引导加载程序上方的区域。那将位于物理地址 0x7e00。您的代码执行此操作:

; Read from drive, dl contains drive number
;     Set up output location to 0x7E00: 0x00
mov ax, 0x7E00
mov es, ax ; Load to 0x7E00 : 0x00
mov bx, 0x00

16 位 Segment:Offset pairs 使用此计算映射到物理内存地址:(段<<4)+偏移量(<<4 与乘以 16 相同)。这意味着 0x7E00:0x00 是物理内存地址 (0x7E00<<4)+0=0x7e000 。这显然是错误的。我相信您的意图是这样的:

mov ax, 0x07E0
mov es, ax ; Load to 0x07E0:0x00
mov bx, 0x00

0x07E0:0x00 是物理内存地址 (0x07E0<<4)+0=0x7e00 。这是加载到物理地址 0x7c00 内存中的引导加载程序正上方的区域。当您使用以下代码 FAR JMP 到第二阶段时会出现类似的问题:

jmp 0x7E00:0x00 ; Jump to 2nd stage bootloader

应该是:

jmp 0x07E0:0x00 ; Jump to 2nd stage bootloader  

第二阶段的潜在问题

如果您进行前面提到的建议更改 (jmp 0x07E0:0x00),那么 FAR JMP 将更改 CS:IPCS =0x07E0(segment), IP= 0x0000(offset) 并在那里继续执行。您需要 ORG 指令来匹配您从第一阶段跳转到的偏移量 (IP)。由于偏移量 (IP) 是 0x0000,因此您的 ORG 指令应该匹配:

[ORG 0x0000]

您还需要确保当您的第二阶段开始加载时 DS 也设置为匹配。实现此目的的一种方法是将代码段 CS 显式复制到数据段 DS 。这可以通过第二阶段顶部的代码来完成,如下所示:

mov ax, cs 
mov ds, ax

如果没有正确设置数据段 DS,所有对变量的引用都将使用错误的段,并且很可能不会指向它们在内存中的实际位置。您的代码目前没有变量,因此您没有注意到这个问题。


不要假设第一阶段由 BIOS 调用 CS:IP=0x0000:0x7c00

在这个答案的序言中提到的我的一般引导加载程序技巧中,技巧 #1 非常重要:

  • When the BIOS jumps to your code you can't rely on CS,DS,ES,SS,SP registers having valid or expected values. They should be set up appropriately when your bootloader starts. You can only be guaranteed that your bootloader will be loaded and run from physical address 0x00007c00 and that the boot drive number is loaded into the DL register.

在您的代码中,您的引导加载程序具有以下内容:

[BITS 16] ; 16 bit mode
[ORG 0x7C00] ; Boot loader start address

Boot:
    ; Initial, dl contains drive number
    ; Set data segment to code segment
    mov ax, cs
    mov ds, ax
    mov es, ax

[ORG 0x7C00] 很好,但是假设 CS 段到达我们的引导加载程序时设置为 0x0000。然后我们设置DS=CS。对于简单的引导加载程序,传统观点认为 BIOS 会跳转到 0x0000:0x7c00 (CS:IP)。 ORG 应该匹配偏移量(在本例中为 IP)。问题是实际上 BIOS 跳转到物理地址 0x00007c00 但它可以用各种 CS:IP 对来完成。

BIOS 可以使用 jmp 0x07c0:0x0000 对我们的代码进行 FAR JMP(或等效),一些仿真器和真实硬件就是这样做的。 0x07c0:0x0000 是 (0x07c0<<4)+0=0x7c00 的物理地址。这很好,但请注意 IP = 0x0000。我们设置了 [ORG 0x7c00]。那将是不匹配的!如果我们实际上不知道 BIOS 将我们与什么 CS:IP 配对,我们如何解决这个问题?简单 - 不要在引导加载程序的第一阶段将 CS 复制到 DS。由于我们需要 0x7c00 的偏移量,因此 DS 需要为 0x0000 才能工作。我们应该明确地将 0x0000 放入我们的数据段 (DS)。代码可能如下所示:

[BITS 16] ; 16 bit mode
[ORG 0x7C00] ; Boot loader start address

Boot:
    ; Initial, dl contains drive number
    ; Set data segment to code segment
    xor ax, ax   ; AX=0
    mov ds, ax   ; DS=0  
    mov es, ax   ; ES=0