【问题标题】:Loading second stage of a bootloader加载引导加载程序的第二阶段
【发布时间】:2016-03-22 22:38:30
【问题描述】:

我正在尝试为 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 启动并像任何其他硬盘驱动器一样启动它们。代码似乎没有执行的原因是什么?

【问题讨论】:

  • 如果你在第二阶段的顶部使用[ORG 0x0000],并且第一行代码用mov ax, csmov ds, ax初始化DS会发生什么?
  • 如何知道引导扇区运行成功?
  • 第二个也是最重要的,你跳转到jmp 0x7E00:0x00。这是物理地址 (0x7e00jmp 0x07E0:0x00,即 (0x07e0
  • 您确定第一阶段运行正常吗?也许它应该输出一个字符来确定。在这种环境下,您确定dl 有驱动器号吗?
  • 当您开始定义数据变量并尝试在第二阶段使用它们时,我的第一条评论的重要性将变得显而易见。如果没有正确设置 DS,所有对变量的引用都将位于错误的偏移量。您的代码目前没有变量,因此您不会注意到问题。如果您远 jmp 到第二阶段,ORG 指令应该等于跳转到的偏移量。如果您在上面进行了建议的 jmp 更改 (jmp 0x07E0:0x00),那么 JMP 会将 CS:IP 更改为 CS=0x07E0, IP=0x0000 。您希望 ORG 等于 IP。所以 ORG 应该是 0x0000。

标签: assembly x86 nasm bootloader real-mode


【解决方案1】:

您的代码中似乎存在许多问题。我会尝试找出其中的一些。在我为 Stackoveflow 编写的一些答案中可以找到一些有用的参考资料。

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

堆栈

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

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 错误地返回了该区域的错误大小。在大多数情况下,它的大小为 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使用这个计算映射到一个物理内存地址:(segment

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

0x07E0:0x00 是物理内存地址 (0x07E0FAR 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,所有对变量的引用都将使用错误的段,并且可能不会指向它们在内存中的实际位置。您的代码目前没有变量,因此您不会注意到问题。


不要假设 1st Stage 由 BIOS 调用 CS:IP=0x0000:0x7c00

在这个答案的序言中提到的我的通用引导加载程序提示中,提示 #1 非常重要:

  • 当 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 对我们的代码进行 FAR JMP(或等效),并且一些仿真器和真实硬件会这样做。 0x07c0:0x0000 是 (0x07c0IP = 0x0000。我们设置了[ORG 0x7c00]。那将是一个不匹配!如果我们实际上不知道 BIOS 调用我们的 CS:IP 对,我们如何解决这个问题?简单 - 不要在引导加载程序的第一阶段将 CS 复制到 DS。由于我们需要 0x7c00 的偏移量,DS 需要为 0x0000 才能工作。我们应该在数据段 (DS) 中明确放置 0x0000。代码可能如下所示:

[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

【讨论】:

    猜你喜欢
    • 2013-05-05
    • 2019-08-24
    • 2019-07-08
    • 2011-12-04
    • 2016-02-14
    • 1970-01-01
    • 2020-10-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多