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

5
我正在尝试为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

第二阶段引导程序的代码:
[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启动,并像其他硬盘一样引导它们。为什么这段代码似乎无法执行呢?


1
如果在第二阶段的顶部使用 [ORG 0x0000],并且代码的前几行使用 mov ax, cs mov ds, ax 初始化了 _DS_,会发生什么? - Michael Petch
你如何知道引导扇区成功运行? - Kenney
2
其次,最重要的是你跳转到 jmp 0x7E00:0x00。这是物理地址 (0x7e00<<4)+0 或 0x7E000。我怀疑这不是你想要的。也许你的意思是 jmp 0x07E0:0x00,它将是 (0x07e0<<4)+0=0x7e00。 - Michael Petch
你确定第一阶段运行正常吗?也许应该输出一个字符来确认。在这种环境下,你确定dl有驱动器号码吗? - wallyk
当你开始定义数据变量并尝试在第二阶段中使用它们时,我的第一个评论的重要性将变得明显。如果没有正确设置_DS_,所有对变量的引用都将位于错误的偏移量上。目前你的代码没有变量,所以你没有注意到这个问题。如果你远跳到第二阶段,ORG指令应该等于跳转到的偏移量。如果你按照上面建议的jmp更改(jmp 0x07E0:0x00),那么JMP将会更改CS:IP为CS=0x07E0,IP=0x0000。你想让ORG等于IP。因此ORG应该是0x0000。 - Michael Petch
显示剩余3条评论
1个回答

6
你的代码似乎存在一些问题。我会尝试指出其中一些。在我的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。但那里也有一个问题。扩展BIOS数据区(EBDA)可能在该区域内。一些BIOS错误地返回此区域的错误大小。在大多数情况下,它的大小为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位段:偏移量对使用以下计算方法映射到物理内存地址:(段<<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:IP 更改为 CS =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调用第一阶段

在我的通用引导程序提示中提到的第一个提示非常重要:

  • 当BIOS跳转到您的代码时,您不能依赖于CS、DS、ES、SS、SP寄存器具有有效或预期值。它们应该在引导加载程序启动时适当设置。您只能保证引导加载程序将从物理地址0x00007c00加载和运行,并且引导驱动器号已加载到DL寄存器中。

在您的代码中,您的引导加载程序应该这样写:

[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来跳转到我们的代码,有些仿真器和实际硬件也是这样做的。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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接