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

时间:2015-12-16 17:10:00

标签: assembly x86 nasm bootloader real-mode

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

主引导记录中的

Bootloader:

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

1 个答案:

答案 0 :(得分:5)

您的代码中似乎存在许多问题。我会尝试识别其中一些。在我为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错误地为此区域返回了错误的大小。在大多数情况下,它在物理地址0xa0000之下的大小为0k到4k。如果您考虑到最糟糕的情况,我会选择低于该值的堆栈。

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使用该计算映射到物理存储器地址:(段&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt; 4这意味着0x7E00:0x00是物理存储器地址(0x7E00 <&lt;&lt; 4)+ 0 = 0x7e000。这显然是错的。我相信你的意图是这样的:

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

0x07E0:0x00是物理存储器地址(0x07E0 <&lt; 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(段), IP = 0x0000(偏移量)并继续执行。您需要 ORG 指令来匹配从第一阶段跳转到的偏移量( IP )。由于偏移量( IP )为0x0000,因此ORG指令应匹配:

[ORG 0x0000]

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

mov ax, cs 
mov ds, ax

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

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

在我的一般Bootloader提示这个答案的序言中提到的提示,提示#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是(0x07c0 <&lt; 4)+ 0 = 0x7c00的物理地址。这很好,但请注意 IP = 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