引导加载程序的问题
Issues with bootloader
我正在学习汇编,我尝试编写自己的引导加载程序。它在 VirtualBox 上运行良好,但在实际 PC 上不起作用。
在 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
刻录到 USB 驱动器时的终端:
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$
当引导加载程序启动时,几乎所有寄存器(包括段寄存器)的内容都是"undefined"。唯一实际具有已知值的寄存器是 DL(它包含 BIOS 的磁盘驱动器号)。
所有引用内存的指令都使用隐式或显式段寄存器。例如,mov [bootdev], dl
和 lodsb
都依赖于(隐含的)DS 段寄存器,该寄存器从未设置且仍未定义。
像所有未定义的值一样,它们有可能(由于纯粹的运气)成为使事情偶然发生的值。如果 BIOS 碰巧在 DS 中保留值 0x0000,您的代码将起作用。
我最近在 Whosebug 上 详细介绍了引导加载程序。大多数涉及它在一个模拟器或 VM 上工作但在另一个(或物理硬件)上工作的情况的问题通常归结为当 BIOS 跳转到您的代码时对段寄存器的状态做出错误的假设。在某些仿真器下,段寄存器中可能有更合理的值,但通常情况并非如此。从我之前的回答中,我得到了这两个似乎适用于此的提示:
- 当BIOS跳转到你的代码时你不能依赖DS,ES,SS,SP 寄存器具有有效值或预期值。当您的引导加载程序启动时,它们应该被适当地设置。
lodsb
、movsb
等使用的方向标志可以设置或清除。如果方向标志设置不当,SI/DI 寄存器可能会调整到错误的方向。使用 STD
/CLD
将其设置为您希望的方向 (CLD=forward/STD=backwards)。在这种情况下,代码假定向前移动,因此应该使用 CLD
。有关更多信息,请参阅 instruction set reference
你的汇编代码被设置为编译并且link假定原点为0x7C00(通过ORG 0x7C00
)。您访问 msg 和 bootdev 等变量的代码将假设它们的内存地址在段内是绝对的(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 Buffer Address Pointer
想象一下,如果 ES 设置为随机垃圾,磁盘读取可能会读入您不想要的内存。所以就像 DS 一样,ES 也必须设置。您已经将引导加载程序和内核编写在原点为 0x7C00 的同一文件中,因此您再次只需要将 ES 段设置为零即可。
设置堆栈时,您可以适当地设置 ES 和 DS。
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
) 的堆栈 space。 4k 足以用于 BIOS 调用和您当前的代码。前0x1000
内存一般为中断table/BIOS数据区等
我在您的代码中注意到的另一个错误是当您尝试重置磁盘驱动器时:
int 0x10 ; Set video mode
int 0x13 ; Reset the drive
您在这两行上方将 AX 设置为零。 INT 0x10 AH=0x00 (set video mode) has the side effect of returning information in AX. Since AX can be clobbered your call to 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 磁盘映像上并尝试将其装载到您的 OS 中,则可能只会出现问题。你有:
jmp boot_sector
;------------------------------
OEMLabel db "FLOPPYDR"
您在引导加载程序中的磁盘结构应该 OEMLabel
从第 4 个字节开始。 jmp 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)
我正在学习汇编,我尝试编写自己的引导加载程序。它在 VirtualBox 上运行良好,但在实际 PC 上不起作用。 在 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
刻录到 USB 驱动器时的终端:
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$
当引导加载程序启动时,几乎所有寄存器(包括段寄存器)的内容都是"undefined"。唯一实际具有已知值的寄存器是 DL(它包含 BIOS 的磁盘驱动器号)。
所有引用内存的指令都使用隐式或显式段寄存器。例如,mov [bootdev], dl
和 lodsb
都依赖于(隐含的)DS 段寄存器,该寄存器从未设置且仍未定义。
像所有未定义的值一样,它们有可能(由于纯粹的运气)成为使事情偶然发生的值。如果 BIOS 碰巧在 DS 中保留值 0x0000,您的代码将起作用。
我最近在 Whosebug 上
- 当BIOS跳转到你的代码时你不能依赖DS,ES,SS,SP 寄存器具有有效值或预期值。当您的引导加载程序启动时,它们应该被适当地设置。
lodsb
、movsb
等使用的方向标志可以设置或清除。如果方向标志设置不当,SI/DI 寄存器可能会调整到错误的方向。使用STD
/CLD
将其设置为您希望的方向 (CLD=forward/STD=backwards)。在这种情况下,代码假定向前移动,因此应该使用CLD
。有关更多信息,请参阅 instruction set reference
你的汇编代码被设置为编译并且link假定原点为0x7C00(通过ORG 0x7C00
)。您访问 msg 和 bootdev 等变量的代码将假设它们的内存地址在段内是绝对的(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 Buffer Address Pointer
想象一下,如果 ES 设置为随机垃圾,磁盘读取可能会读入您不想要的内存。所以就像 DS 一样,ES 也必须设置。您已经将引导加载程序和内核编写在原点为 0x7C00 的同一文件中,因此您再次只需要将 ES 段设置为零即可。
设置堆栈时,您可以适当地设置 ES 和 DS。
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
) 的堆栈 space。 4k 足以用于 BIOS 调用和您当前的代码。前0x1000
内存一般为中断table/BIOS数据区等
我在您的代码中注意到的另一个错误是当您尝试重置磁盘驱动器时:
int 0x10 ; Set video mode
int 0x13 ; Reset the drive
您在这两行上方将 AX 设置为零。 INT 0x10 AH=0x00 (set video mode) has the side effect of returning information in AX. Since AX can be clobbered your call to 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 磁盘映像上并尝试将其装载到您的 OS 中,则可能只会出现问题。你有:
jmp boot_sector
;------------------------------
OEMLabel db "FLOPPYDR"
您在引导加载程序中的磁盘结构应该 OEMLabel
从第 4 个字节开始。 jmp 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)