【问题标题】:Issues with bootloader引导加载程序的问题
【发布时间】:2015-12-20 16:32:31
【问题描述】:

我正在学习汇编,并尝试编写自己的引导加载程序。它在 VirtualBox 上运行良好,但在实际 PC 上无法运行。 在电脑上“Hello World!”没有打印出来。

这是使用的代码:

BITS 16
ORG 0x7C00

jmp boot_sector

;------------------------------

OEMLabel            db "FLOPPYDR"
BytesPerSector      dw 512
SectorsPerCluster   db 1
ReservedForBoot     dw 1
NumberOfFats        db 2
RootDirEntries      dw 224
LogicalSectors      dw 2880
MediumByte          db 0xF0
SectorsPerFat       dw 9
SectorsPerTrack     dw 18
Sides               dw 2
HiddenSectors       dd 0
LargeSectors        dd 0
DriveNo             dw 0
Signature           db 41
VolumeID            dd 0x00
VolumeLabel         db "FLOPPYDRIVE"
FileSystem          db "FAT12"

;##############################
          boot_sector:
;##############################

  mov ax, 0x0000            ; Set up the stack
  mov ss, ax                ; Is this done correctly?
  mov sp, 0x7C00            ; (I dont quite understand)

  int 0x10                  ; Set video mode

  int 0x13                  ; Reset the drive

  mov ah, 0x02              ; Read more sectors
  mov al, 2                 ; Read two extra sectors,
  mov bx, main_sector       ; starting from the second.
  mov ch, 0                 ;
  mov cl, 2                 ; dl has been set already (?)
  mov dh, 0                 ;
  int 0x13                  ;

  mov [bootdev], dl         ; Store original dl in bootdev

  jmp main_sector           ; Go to the main sector (0x200 I think)

  times 510 - ($ - $$) db 0 ; Fill in the rest of the sector with 0s
  dw 0xAA55                 ; and 0xAA55 at the end for signature

;##############################
          main_sector:
;##############################

jmp Start

;------------------------------

bootdev db 0
msg db 'Hello World!', 10, 13, 0

;------------------------------

print_string:
  mov ah, 0x0E
  mov bh, 0
  cmp al, 0
  jne .loop
  mov bl, 0x0F
  .loop:
    lodsb
    cmp al, 0
    je .end
    int 0x10
    jmp .loop
  .end:
    ret

;------------------------------

  Start:
    mov si, msg
    call print_string
    hlt

    times 512 - ($ - main_sector) db 0

我还评论了一些问题,但这些不是我的主要问题(好吧,也许我不知道答案会导致问题)。为什么这不能在真正的 PC 上运行?

编译我使用nasm -f bin boot.asm -o boot.bin,创建一个虚拟软盘文件我使用mkfile 1474560 floppy.flp

然后我使用 HexEdit 打开 floppy.flp 并将前 64 行 (0x00 - 0x3F) 替换为 boot.bin 文件的内容(使用 HexEdit 打开)。

E9 38 00 46 4C 4F 50 50 59 44 52 00 02 01 01 00
02 E0 00 40 0B F0 09 00 12 00 02 00 00 00 00 00
00 00 00 00 00 00 29 00 00 00 00 46 4C 4F 50 50
59 44 52 49 56 45 46 41 54 31 32 B8 00 00 8E D0
BC 00 7C CD 10 CD 13 B4 02 B0 02 BB 00 7E B5 00
B1 02 B6 00 CD 13 88 16 03 7E E9 A3 01 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA <-- End of first sector
E9 24 00 00 48 65 6C 6C 6F 20 57 6F 72 6C 64 21
0A 0D 00 B4 0E B7 00 3C 00 75 02 B3 0F AC 3C 00
74 04 CD 10 EB F7 C3 BE 04 7E E8 E6 FF F4 00 00 (The rest is just 0's).

这是我将floppy.flp刻录到U盘时的终端:

Last login: Wed Sep 23 12:10:48 on ttys000
MacBook-Air:~ sasha$ cd ~/Desktop
MacBook-Air:Desktop sasha$ diskutil list
/dev/disk0
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *121.3 GB   disk0
   1:                  Apple_HFS                         209.7 MB   disk0s1
   2:          Apple_CoreStorage                         120.5 GB   disk0s2
   3:                 Apple_Boot Recovery HD             650.0 MB   disk0s3
/dev/disk1
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS Macintosh HD           *120.1 GB   disk1
                                 Logical Volume on disk0s2
                                 8CD6A846-395D-4C97-A5DE-0A7ABA9F1C99
                                 Unencrypted
/dev/disk2
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     Apple_partition_scheme                        *17.1 MB    disk2
   1:        Apple_partition_map                         32.3 KB    disk2s1
   2:                  Apple_HFS Flash Player            17.1 MB    disk2s2
/dev/disk3
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                            FLOPPYDRIVE            *1.0 GB     disk3
MacBook-Air:Desktop sasha$ diskutil unmountdisk /dev/disk3
Unmount of all volumes on disk3 was successful
MacBook-Air:Desktop sasha$ sudo dd bs=512 if=floppy.flp of=/dev/disk3
Password:
2880+0 records in
2880+0 records out
1474560 bytes transferred in 0.843982 secs (1747146 bytes/sec)
MacBook-Air:Desktop sasha$ 

【问题讨论】:

  • 我最近用general tips on developing a bootloaderhere写了一个答案。第 1、2、3 点及其解决方案可能对您有所帮助。
  • 在您的情况下,您可能还需要通过将NOP 指令放在右侧之后 jmp boot_sector 来对齐磁盘数据结构
  • 特定于您的情况 ESDS 将是一个重要因素。 lodsb 需要 DS,int 0x13 需要正确的 ES,因为它需要 ES:BX 中的完整地址磁盘缓冲区。由于您使用lodsb,因此您应该在设置堆栈后使用cld 清除方向标志,以便保证lodsb 在每次执行后递增SI
  • @MichaelPetch 它肯定有效,再次感谢您!
  • 那我将把这个花絮添加到我的答案中。遇到这个问题的其他人可能会从中受益。

标签: macos assembly nasm x86-16 bootloader


【解决方案1】:

我最近在 Stackoverflow 上向written 详细介绍了引导加载程序。大多数涉及它在一个仿真器或 VM 上工作但在另一个(或物理硬件)上不工作的情况通常归结为当 BIOS 跳转到您的代码时对段寄存器的状态做出错误假设。在某些仿真器下,段寄存器中的值可能更合理,但通常情况并非如此。根据我之前的回答,我有这两个提示似乎适用于此:

  1. 当 BIOS 跳转到您的代码时,您不能依赖 DSESSSSP 具有有效或预期值的寄存器。当您的引导加载程序启动时,它们应该被正确设置。
  2. lodsbmovsb 等使用的方向标志可以设置或清除。如果方向标志设置不当SI/DI 寄存器可能会在错误的方向上调整。使用STD/CLD 将其设置为您希望的方向(CLD=forward/STD=backwards)。在这种情况下,代码假定向前移动,因此应该使用CLD。更多信息请参见instruction set reference

您的汇编代码设置为编译和链接,假设原点为 0x7C00(通过ORG 0x7C00)。您的代码访问变量如 msgbootdev 将假设它们的内存地址在段内是绝对的 (DS)。这意味着如果您有一个无效的 DS 段,那么您可能在错误的位置处理变量、数据和标签。举个例子:

mov [bootdev], dl 

DS 有一个隐式引用,等效于使用显式 DS 段对其进行寻址:

mov [ds:bootdev], dl

如果 DS 中有一些随机值,那么您很可能会在您不期望的地方访问内存。对于某些环境,DS 可能只是零,因此您的代码可以正常工作。

您如何知道要使用哪个细分市场?引导加载程序由 BIOS 在物理内存 0x0000:0x7C00(segment:offset) 处加载。您的原点(使用ORG 指令设置)与偏移量匹配,这意味着在您的情况下 DS 应设置为零。

在您的代码中,ES 也应设置为零。原因是INT 0x13 AH=0x02(磁盘读取)说:

ES:BX 缓冲区地址指针

想象一下,如果 ES 设置为随机垃圾,那么磁盘读取可能会读入您不希望的内存。所以就像 DS 一样,ES 也必须被设置。您已将引导加载程序和内核写入同一个文件中,原点为 0x7C00,因此您只需要再次使用设置为零的 ES 段。

设置堆栈时,您可以适当地设置 ESDS

mov ax, 0x0000            ; Set up the stack
mov ss, ax                ; Is this done correctly?
mov sp, 0x7C00            ; (I dont quite understand)
mov ds, ax                ; Set DS to 0 because that is what your code needs
mov es, ax                ;     ES the same as DS.
cld                       ; Read my tip #2

您确实问过您是否正确设置了堆栈。没有什么问题。您的指令有效地设置了一个从0x0000:0x7C00 向下增长的堆栈,就在您的引导加载程序占用的区域下方。剩下大约 27kb (0x7C00-0x1000) 的堆栈空间。 4k 对于 BIOS 调用和您当前的代码来说已经足够了。内存的第一个0x1000一般用于中断表/BIOS数据区等。

我在您的代码中注意到的另一个错误是当您尝试重置磁盘驱动器时:

int 0x10                  ; Set video mode
int 0x13                  ; Reset the drive

您在这两行上方将 AX 设置为零。 INT 0x10 AH=0x00(设置视频模式)有在AX中返回信息的副作用。由于 AX 可能会被破坏,因此您对 INT 0x13 AH=0x00 的调用可能会出错。在调用int 0x13 重置驱动器之前,您需要清除AH(或所有AX)。代码应如下所示:

int 0x10                  ; Set video mode
xor ax,ax                 ; clear AX (AH=0)
int 0x13                  ; Reset the drive

您的程序顶部有一个小问题,如果您将此引导加载程序放在正确格式化的 FAT12 磁盘映像上并尝试将其挂载到您的操作系统中,则可能只会出现问题。你有:

jmp boot_sector
;------------------------------    
OEMLabel            db "FLOPPYDR"

引导加载程序中的磁盘结构应该从第 4 个字节开始有 OEMLabeljmp boot_sector 可以被 NASM 编码为 2 或 3 字节指令。使用 short 强制执行 2 字节编码,后跟 NOP(1 字节指令)。这会将OEMLabel 放在文件的第4 个字节。它可能看起来像这样:

jmp short boot_sector
nop
;------------------------------    
OEMLabel            db "FLOPPYDR"

或者,您可以对JMP 进行编码,编码时可能为 2 或 3 个字节,并在必要时使用 NASM 的 TIMES 指令填充 NOP,以便 OEMLabel 始终从第 4 个字节开始:

jmp boot_sector
times 3-($-$$) nop
;------------------------------    
OEMLabel            db "FLOPPYDR"

避免使用hexedit 在磁盘映像开头手动插入引导加载程序代码的技巧是使用dd。您可以使用 dd 覆盖前 1024 个字节并保持其余字节不变。试试dd if=boot.bin of=floppy.flp bs=512 count=2 conv=notrunc。这应该会打开 floppy.flp 写入 2 512 字节扇区,其中包含 boot.bin 中的 1024 字节,而不会截断文件 (conv=notrunc)

【讨论】:

  • 在 NASM 语法中,ds: 覆盖属于括号内,就像在 mov [ds:bootdev], dl 中一样。但是,这作为等价物并不完全正确,因为指定此覆盖将使 NASM 实际上发出不需要的覆盖前缀字节。
  • jmp boot_sector 是一个短跳转(尽管将其明确指定为jmp short boot_sector 更好),所以它是一个 2 字节指令,而不是你写的 3 字节。要在第 4 字节开始 OEM ID,必须使用第 1 和第 2 (jmp short) 和第 3 (nop) 字节。
  • @ecm DS:在括号内是正确的(我已修复),但是我不打算让指令编码相同,而是指出有一个隐含的 DS 和很多人一样不知道 16/32 位代码中的每个内存地址都有一个隐式段。
  • 也许您可以澄清[var] 地址等同于寻址 [ds:var],但没有“可能已写入”。顺便说一句,您上次的编辑将 DS:[ 更改为 [fd:
  • @ecm:凌晨 4 点 30 分,半睁着眼睛在黑暗中打字。当您的手指向左移动 1 并且您没有注意到时,FD 就是 DS。
【解决方案2】:

启动引导加载程序时,几乎所有寄存器(包括段寄存器)的内容都是“未定义的”。唯一真正具有已知值的寄存器是 DL(其中包含磁盘的 BIOS 驱动器号)。

所有引用内存的指令都使用隐式或显式段寄存器。例如,mov [bootdev], dllodsb 都依赖于(隐含的)DS 段寄存器,该寄存器从未设置过并且仍然未定义。

像所有未定义的值一样,它们有可能(由于纯粹的运气)成为一个使事情意外运行的值。如果 BIOS 碰巧将值 0x0000 留在 DS 中,您的代码将起作用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-05-07
    • 2013-08-14
    • 2011-12-23
    • 1970-01-01
    • 1970-01-01
    • 2011-05-25
    • 1970-01-01
    • 2012-07-21
    相关资源
    最近更新 更多