【问题标题】:Bootloader not loading second sector引导加载程序不加载第二个扇区
【发布时间】:2017-01-24 20:24:36
【问题描述】:

我在汇编中编写了一个引导加载程序。 以下是它的工作原理:

  1. 首先,BIOS 正常加载引导加载程序。指示到200h。

  2. 在 200h 时,有一些代码位于 200h 和 21Eh 之间。它只是切换到 VGA 模式,使用 VGA 功能在坐标 1,1 上绘制一个洋红色像素。它永远循环该代码。

但是,当我加载它时,它只是在闪烁的光标上,而普通的 VGA .bin 文件不会这样做,并且会显示一个像素。我检查了一个像素,但什么也没看到。我看到的意思是 VGA 代码没有运行,只是加载了引导加载程序,没有别的。

引导加载程序的代码:

 cli
 startload:
 push 0x00000200
 ret

 times 510-($-$$) db 0
 db 0x55
 db 0xAA

您可以看到它只是转到下一个扇区(从 200h 开始) 以及 200h-21Eh 处的代码:

BITS 16
org 200h
data:
xcoord DB '1'
ycoord DB '1'
color DB 'D'
.code:
vga:
mov ax, 13h
int 10h
mov ax, ycoord
mov bx, xcoord
mov cx, 320
mul cx
add ax, bx
mov di, ax
mov dl, color
mov [es:di],dl
jmp vga

(是的,我知道这不是 230h 字节,这是编译输出的大小,即 230h。)

有什么问题? 注意:这里不是讨论如何使它进入第二扇区。它在问为什么它不去那里。我还没有找到任何解决方案。

【问题讨论】:

  • 你能显示加载第二个扇区的代码吗?
  • 不,跳转到CS:200h(顺便说一句,跳转会更清晰,预测更准确)。 BIOS 只加载第一个扇区,其余的由您决定。
  • 在上一个答案(不是这个答案的副本)中,我举了一个加载扇区并作为第二阶段跳转到它的示例:stackoverflow.com/a/34095896/3857942
  • 我感觉您根本没有加载第二个扇区,这是问题的根源。
  • BIOS 只读取一个扇区。如果您有前 512 个字节之外的代码,那么您需要使用 int 13h/AH=2 来读取它,否则您的引导加载程序将跳转到可能只有零字节的内存中。计算机没有t 从驱动器中读取额外的扇区,您需要手动执行。

标签: assembly x86-16 bootloader bios vga


【解决方案1】:

您似乎遗漏了一些非常重要的代码来将第二个扇区实际读入内存。像这样的:

mov ah,0x02 ; read sectors into memory
mov al,0x10 ; number of sectors to read (16)
mov dl,[bootDrive]  ; drive number
mov ch,0    ; cylinder number
mov dh,0    ; head number
mov cl,2    ; starting sector number
mov bx,Main ; address to load to
int 0x13    ; call the interrupt routine

这假定您已将 dl 的值保存到带有标签 bootDrive 的字节中。显然,您需要更改加载代码的地址。

使用ORG 0x200 只是将汇编器配置为处理不可重定位地址引用的生成。

【讨论】:

  • 去试试。
  • 返回;刚刚跳到硬盘驱动器上的正常引导加载程序。我看到一个闪烁的硬盘光标,该代码似乎来自引导加载程序 - 硬盘引导加载程序 - 初始化/等。图形 - 安装在硬盘中的启动菜单。
  • @StarOS:使用像 BOCHS 这样的模拟器进行开发可能是个好主意:然后您可以在内置调试器中单步执行引导扇区代码,并检查寄存器/内存。这比盲人工作要容易得多。
【解决方案2】:

回答您的一个问题。这段代码:

startload:
push 0x00000200
ret

几乎相当于几乎绝对跳转到 CS:0x200。我们不知道 CS 中的值是什么,但许多 BIOS 将以 CS=0 和 IP=0x7c00 开头(但并非总是如此)。你不能真正依赖 CS 是一个特定的值,除非你自己设置它。在大多数情况下,CS 可能为零,这意味着您可能会跳转到物理内存地址 0x00200 (0x0000:0x0200)。这恰好位于从物理地址 0x00000 到 0x003FF 的实模式中断表的中间。跳转到该位置可能会导致某种未定义的行为。

您可以将引导加载程序加载到 BOCHS 中,该 BOCHS 具有理解 16 位实模式的合理调试器。您将能够单步执行代码并准确确定 CS 是什么以及它跳转到的位置。


您可能想要完成的工作可以使用以下代码来完成。 这是我之前的Stackoverflow answer 对另一个问题的简单更改。要了解这段代码在做什么,请参阅我之前的回答。

简而言之,BIOS 从物理内存地址 0x7C00 开始的磁盘读取单个磁盘扇区(512 字节)。如果您想加载其他扇区,您必须编写将它们加载到内存中的代码,然后在加载后跳转到该代码。

在此示例中,第一阶段是引导加载程序,第二阶段从磁盘的第 2 扇区(引导扇区之后的扇区)加载单个扇区。在此示例中,我将通过 segment:offset pair 0x7e0:0x0000 在物理地址 0x07e00 处的第一个扇区之后加载第二个扇区。 (0x07e0

bootload.asm

bits 16
ORG 0x7c00      ; Bootloader starts at physical address 0x07c00

    ; At start bootloader sets DL to boot drive

    ; Since we specified an ORG(offset) of 0x7c00 we should make sure that
    ; Data Segment (DS) is set accordingly. The DS:Offset that would work
    ; in this case is DS=0 . That would map to segment:offset 0x0000:0x7c00
    ; which is physical memory address (0x0000<<4)+0x7c00 . We can't rely on
    ; DS being set to what we expect upon jumping to our code so we set it
    ; explicitly
    xor ax, ax
    mov ds, ax        ; DS=0

    cli               ; Turn off interrupts for SS:SP update
                      ; to avoid a problem with buggy 8088 CPUs
    mov ss, ax        ; SS = 0x0000
    mov sp, 0x7c00    ; SP = 0x7c00
                      ; We'll set the stack starting just below
                      ; where the bootloader is at 0x0:0x7c00. The
                      ; stack can be placed anywhere in usable and
                      ; unused RAM.
    sti               ; Turn interrupts back on

reset:                ; Resets floppy drive

    xor ax,ax         ; AH = 0 = Reset floppy disk
    int 0x13
    jc reset          ; If carry flag was set, try again

    mov ax,0x07e0     ; When we read the sector, we are going to read to
                      ;    address 0x07e0:0x0000 (phys address 0x07e00)
                      ;    right after the bootloader in memory
    mov es,ax         ; Set ES with 0x07e0
    xor bx,bx         ; Offset to read sector to
floppy:
    mov ah,0x2        ; 2 = Read floppy
    mov al,0x1        ; Reading one sector
    mov ch,0x0        ; Track(Cylinder) 1
    mov cl,0x2        ; Sector 2
    mov dh,0x0        ; Head 1
    int 0x13
    jc floppy         ; If carry flag was set, try again

    jmp 0x07e0:0x0000 ; Jump to 0x7e0:0x0000 setting CS to 0x07e0
                      ;    IP to 0 which is code in second stage
                      ;    (0x07e0<<4)+0x0000 = 0x07e00 physical address

times 510 - ($ - $$) db 0   ; Fill the rest of sector with 0
dw 0xAA55                   ; This is the boot signature

第二阶段将由INT 13h/AH=2h在内存中的引导加载程序之后立即加载,从物理地址0x07e00开始。

stage2.asm

BITS 16

; ORG needs to be set to the offset of the far jump used to
; reach us. Jump was 0x7e0:0x0000 so ORG = Offset = 0x0000.
ORG 0x0000

main:
    ; Set DS = CS
    mov ax, cs
    mov ds, ax

    ; Set to graphics mode 0x13 (320x200x256)
    mov ax, 13h
    int 10h

    ; Set ES to the VGA video segment at 0xA000
    mov ax, 0xa000
    mov es, ax

vga:
    ; Draw pixel in middle of screen
    mov ax, [ycoord]
    mov bx, [xcoord]
    mov cx, 320
    mul cx
    add ax, bx
    mov di, ax
    mov dl, [color]
    mov [es:di],dl

    ; Put processor in an endless loop
    cli
.endloop:
    hlt
    jmp .endloop

; Put Data after the code
xcoord DW 160
ycoord DW 100
color  DB 0x0D    ; One of the magenta shades in VGA palette

上面的代码是您的 VGA 代码的略微修改版本,因为您的代码存在错误。您的 VGA 代码没有正确地将 ES 段设置为 0xA000,这是 VGA 图形内存的开始;它没有正确地取消引用变量(您使用了它们的地址而不是值);坐标的大小是 BYTE 而不是 WORD;在具有 ASCII 字符值的变量中定义值。我还在代码之后移动了数据。

我修改了代码,在 320x200 显示器的中间画了一个像素,然后无限循环结束程序。


在 Linux 上(或在带有 Chrysocome DD 的 Windows 上)使用 NASM 进行组装,您可以使用以下命令生成 720K 的引导盘:

dd if=/dev/zero of=disk.img bs=1024 count=720
nasm -f bin bootload.asm -o bootload.bin
dd if=bootload.bin of=disk.img conv=notrunc

nasm -f bin stage2.asm -o stage2.bin    
dd if=stage2.bin of=disk.img bs=512 seek=1 conv=notrunc

这会构建bootload.bin 并将其放入磁盘映像的第一个扇区,然后构建stage2.bin 并将其放入磁盘映像的第2 扇区。磁盘映像名为disk.img。

你应该可以用 QEMU 来测试它,比如:

qemu-system-i386 -fda disk.img

有关DD 用法的更多信息可以在我的另一个Stackoverflow answers 中找到。巧合的是,这个答案是针对您去年提出的一个问题。

【讨论】:

  • 如果你的 bootload.bin 正确填充到 512B,你也可以cat bootload.bin stage2.bin /dev/zero | head -c 720K &gt; disk.img。但是 dd 方法仍然将第二个扇区放在 offset=512 处,而不管第一个二进制文件的大小。也可以:cat bootload.bin stage2.bin &gt; disk.img &amp;&amp; truncate disk.img -s $((720*1024)) 制作稀疏文件。
  • 我认为将 dd 用于此类事情的起源是一些非常古老的琐事。在某些 Unix 系统上,您必须使用 dd 之类的东西来写入块设备文件(例如 /dev/fd0),因为它们只接受块对齐的写入并写入完整块。 Linux 在块设备之上有一个缓冲层,所以你可以做任何事情。 (或者您可以使用 O_DIRECT 打开,因此写入不会通过缓冲区缓存)。 dd 对于在二进制文件中的偏移量处读取/写入当然仍然有用,但是这些天没有什么“特别”的地方。 (不是说你提出了这个说法,只是一个有趣的事实)。
  • @PeterCordes:我会在 MS-DOS 上使用copy /b 而不是type,但随后担心的是,一些虚拟环境已知会在不知名的软盘大小上犹豫不决磁盘大小。由于 OP 没有提到环境,所以我在安全方面玩它。通常,当我给出答案时,我会包含 DD 命令以生成众所周知的软盘格式,例如 720k(或 1.44MB、360K、1.2M)。
  • @PeterCordes 您可能想在 MS-DOS(或 Windows 命令行)上使用 copy /b a.bin+b.bin disk.img,因为 type 会将文件视为文本文件并在第一个 CTRL- 后停止读取Z 字符。
  • 我的评论可以用不同的编码来避免这种情况。指的是你不介意第一阶段和第二阶段都在同一个64kb段中的场景一个接一个地。在这种情况下,在第 1 阶段和第 2 阶段中间具有引导签名并在第 2 阶段之后填充到软盘大小的单个段非常容易。
猜你喜欢
  • 2013-08-24
  • 2016-07-02
  • 2013-01-25
  • 2012-07-25
  • 2012-04-09
  • 2016-03-22
  • 1970-01-01
  • 2013-05-05
  • 1970-01-01
相关资源
最近更新 更多