引导加载程序在真实硬件上打印垃圾
Bootloader printing garbage on real hardware
我正在尝试编写自己的引导加载程序。虽然它在 QEMU、Bochs 和 VirtualBox 中运行良好,但我似乎无法在我的笔记本电脑上运行它。
在我的笔记本电脑上,引导加载程序的行为与所有模拟器都非常不同,挂在看似随机的地方,拒绝打印,甚至跳过一些 jmp $
指令。
虽然我在 "real-hardware" 上遇到了很多麻烦,但我认为它们都是一个原因。
以下代码是一个简短的引导加载程序,它应该打印一条 "TEST" 消息 3 次,然后通过跳转到同一位置挂起:
[BITS 16]
[ORG 0x7C00]
jmp 0x0000:start_16 ; In case bootloader is at 0x07C0:0x0000
start_16:
xor ax, ax
mov ds, ax
mov es, ax
cli ; Disable interrupts
mov ss, ax
mov sp, 0x7C00
sti ; Enable interrupts
cld ; Clear Direction Flag
; Store the drive number
mov [drive_number], dl
; Print message(s)
mov si, msg
call print_string
mov si, msg
call print_string
mov si, msg
call print_string
jmp $ ; HALT
; print_string
; si = string
print_string:
pusha
mov ah, 0x0E
.repeat:
lodsb
cmp al, 0x00
je .done
int 0x10
jmp short .repeat
.done:
popa
ret
; Variables
drive_number db 0x00
msg db 'TEST', 0x0D, 0x0A, 0x00
times 510-($-$$) db 0x00
db 0x55
db 0xAA
编译并模拟代码:
$ nasm -f bin bootloader.asm
$ qemu-system-x86_64 bootloader
在模拟器上,它打印了 3 次 "TEST" 然后挂起,在我的笔记本电脑上,它打印了 "TEST" 后跟 3 个奇怪的字符:
http://wiki.osdev.org does not work either. For example, none of the code-snippets from http://wiki.osdev.org/Babystep2 中的大多数引导加载程序代码都可以在我的笔记本电脑上运行。
我的代码有什么问题?我该如何解决?
附加信息
如果我删除 2 个不必要的 mov si, msg
,"TEST" 消息将打印 两次 。
笔记本电脑:
- 华硕 Vivobook S200,
- CPU:英特尔 i3-3217U
- BIOS:美国大趋势,版本 210。
- 计算机与任何其他引导加载程序(如 Grub)一起工作得很好。
汇编写法:
$ nasm -f bin bootloader.asm
$ qemu-system-x86_64 bootloader # TEST 1
$ sudo dd if=/dev/zero of=/dev/sdd bs=1M count=1 # clean the USB
$ sudo dd if=bootloader of=/dev/sdd conv=fsync # write to USB
$ qemu-system-x86_64 /dev/sdd # TEST 2
编辑 1
Ross Ridge 在评论中注意到 Ω♣|
是引导加载程序的前 3 个字节。
编辑 2
更新打印函数和字符串:
print_string:
pusha
.repeat:
mov ah, 0x0E
xor bx, bx
cld ; Clear Direction Flag
lodsb
cmp al, 0x00
je .done
int 0x10
jmp short .repeat
.done:
popa
ret
msg db 'TEST', 0x00
输出
一个TEST
。另外两个丢失了。
编辑 3
根据 Ross Ridge 的建议,已添加 dumpregs
以便更好地调试。 int 0x10
不 修改任何寄存器。
经过一些测试后,我将 dumpregs
函数移动到 drive_number
赋值周围,并在其后移动了 jmp $
。该代码应打印 1 行寄存器转储并停止。相反,它继续:
完整代码:
https://gist.github.com/anonymous/0ddc146f73ff3a13dd35
编辑 4
当前引导加载程序的反汇编使用:
$ ndisasm -b16 bootload2 -o 0x7c00
现在看来 BIOS 可能正在修改 BIOS parameter block,它错误地认为是加载到内存中的引导扇区的一部分。它可能觉得它需要这样做,因为它在引导时提供给 USB 设备的几何形状可能不同于将引导扇区及其假定的 BPB 写入设备时使用的几何形状。由于引导扇区开头存在跳转指令是应用程序测试 BPB 是否存在的方法之一,因此您可以尝试在引导扇区开头插入一些其他指令(但不是 NOP)。例如:
[BITS 16]
[ORG 0x7C00]
xor ax,ax
jmp 0x0000:start_16 ; In case bootloader is at 0x07C0:0x0000
start_16:
请注意,远跳似乎不是 normal indicators of the presence of a BPB 之一。事实上,这是 BPB 不存在的确凿证据,因为 BPB 从偏移量 4 开始,远跳指令的长度为 5 个字节。
如果这不起作用,您可以尝试为 BPB 保留 space,如下所示:
[BITS 16]
[ORG 0x7C00]
jmp start
nop
resb 8 + 25
start:
jmp 0x0000:start_16 ; In case bootloader is at 0x07C0:0x0000
start_16:
您可以使用以下代码转储寄存器以帮助调试问题:
dumpregs:
push es
pusha
push 0xb800
pop es
mov di, [vidmem_ptr]
mov bp, sp
mov cx, 8
dump_loop:
dec bp
dec bp
mov ax, [bp + 16]
call printhex2
inc di
inc di
loop dump_loop
mov [vidmem_ptr], di
popa
pop es
ret
printhex2:
push ax
mov al, ah
call convhex1
pop ax
convhex1:
aam 16
; DB 0D4h, 16
xchg al, ah
call convhex
mov al, ah
convhex:
cmp al, 10
jb lessthan_10
add al, 'A' - '0' - 10
lessthan_10:
add al, '0'
stosb
mov al, 7
stosb
ret
vidmem_ptr dw 5 * 80 * 2 ; start at row 5
它使用直接视频写入来转储所有通用寄存器。它使用 PUSHA/POPA 寄存器顺序:AX CX DX BX SP BP SI DI
如果您将原始代码中的 INT 0x10 指令与调用此函数括起来,您应该得到如下输出:
尤其要确保调试输出的每一行左侧的 8 个数字与右侧的 8 个数字匹配。您正在使用的 BIOS 调用不应更改任何寄存器。
我以前见过类似的症状。场景大概是这样的:
a) 您正在从 USB 闪存启动笔记本电脑
b) BIOS 查看设备的第一个扇区并尝试决定是否应该将其视为软盘或硬盘驱动器。它找不到有效的分区 table,因此决定将其视为软盘。
c) BIOS 尝试 "helpful" 并修补 BPB 中的某些字段。
d) 你没有 BPB,所以你的代码 and/or 数据被破坏,导致奇怪的事情发生(代码中的微小变化导致不同的奇怪事情发生)。
遗憾的是,我自己没有执行此操作的硬件。我不知道为什么 BIOS 假设有一个 BPB,或者它假设存在多个不同 "BPB formats" 中的哪一个,或者它认为它可能正在修补什么或者为什么它认为有必要修补任何东西。我只知道有些 BIOS 会丢弃偏移量 0x1C 到 0x1F 处的字节和偏移量 0x24 处的字节。
我正在尝试编写自己的引导加载程序。虽然它在 QEMU、Bochs 和 VirtualBox 中运行良好,但我似乎无法在我的笔记本电脑上运行它。
在我的笔记本电脑上,引导加载程序的行为与所有模拟器都非常不同,挂在看似随机的地方,拒绝打印,甚至跳过一些 jmp $
指令。
虽然我在 "real-hardware" 上遇到了很多麻烦,但我认为它们都是一个原因。
以下代码是一个简短的引导加载程序,它应该打印一条 "TEST" 消息 3 次,然后通过跳转到同一位置挂起:
[BITS 16]
[ORG 0x7C00]
jmp 0x0000:start_16 ; In case bootloader is at 0x07C0:0x0000
start_16:
xor ax, ax
mov ds, ax
mov es, ax
cli ; Disable interrupts
mov ss, ax
mov sp, 0x7C00
sti ; Enable interrupts
cld ; Clear Direction Flag
; Store the drive number
mov [drive_number], dl
; Print message(s)
mov si, msg
call print_string
mov si, msg
call print_string
mov si, msg
call print_string
jmp $ ; HALT
; print_string
; si = string
print_string:
pusha
mov ah, 0x0E
.repeat:
lodsb
cmp al, 0x00
je .done
int 0x10
jmp short .repeat
.done:
popa
ret
; Variables
drive_number db 0x00
msg db 'TEST', 0x0D, 0x0A, 0x00
times 510-($-$$) db 0x00
db 0x55
db 0xAA
编译并模拟代码:
$ nasm -f bin bootloader.asm
$ qemu-system-x86_64 bootloader
在模拟器上,它打印了 3 次 "TEST" 然后挂起,在我的笔记本电脑上,它打印了 "TEST" 后跟 3 个奇怪的字符:
http://wiki.osdev.org does not work either. For example, none of the code-snippets from http://wiki.osdev.org/Babystep2 中的大多数引导加载程序代码都可以在我的笔记本电脑上运行。
我的代码有什么问题?我该如何解决?
附加信息
如果我删除 2 个不必要的 mov si, msg
,"TEST" 消息将打印 两次 。
笔记本电脑:
- 华硕 Vivobook S200,
- CPU:英特尔 i3-3217U
- BIOS:美国大趋势,版本 210。
- 计算机与任何其他引导加载程序(如 Grub)一起工作得很好。
汇编写法:
$ nasm -f bin bootloader.asm
$ qemu-system-x86_64 bootloader # TEST 1
$ sudo dd if=/dev/zero of=/dev/sdd bs=1M count=1 # clean the USB
$ sudo dd if=bootloader of=/dev/sdd conv=fsync # write to USB
$ qemu-system-x86_64 /dev/sdd # TEST 2
编辑 1
Ross Ridge 在评论中注意到 Ω♣|
是引导加载程序的前 3 个字节。
编辑 2
更新打印函数和字符串:
print_string:
pusha
.repeat:
mov ah, 0x0E
xor bx, bx
cld ; Clear Direction Flag
lodsb
cmp al, 0x00
je .done
int 0x10
jmp short .repeat
.done:
popa
ret
msg db 'TEST', 0x00
输出
一个TEST
。另外两个丢失了。
编辑 3
根据 Ross Ridge 的建议,已添加 dumpregs
以便更好地调试。 int 0x10
不 修改任何寄存器。
经过一些测试后,我将 dumpregs
函数移动到 drive_number
赋值周围,并在其后移动了 jmp $
。该代码应打印 1 行寄存器转储并停止。相反,它继续:
完整代码: https://gist.github.com/anonymous/0ddc146f73ff3a13dd35
编辑 4
当前引导加载程序的反汇编使用:
$ ndisasm -b16 bootload2 -o 0x7c00
现在看来 BIOS 可能正在修改 BIOS parameter block,它错误地认为是加载到内存中的引导扇区的一部分。它可能觉得它需要这样做,因为它在引导时提供给 USB 设备的几何形状可能不同于将引导扇区及其假定的 BPB 写入设备时使用的几何形状。由于引导扇区开头存在跳转指令是应用程序测试 BPB 是否存在的方法之一,因此您可以尝试在引导扇区开头插入一些其他指令(但不是 NOP)。例如:
[BITS 16]
[ORG 0x7C00]
xor ax,ax
jmp 0x0000:start_16 ; In case bootloader is at 0x07C0:0x0000
start_16:
请注意,远跳似乎不是 normal indicators of the presence of a BPB 之一。事实上,这是 BPB 不存在的确凿证据,因为 BPB 从偏移量 4 开始,远跳指令的长度为 5 个字节。
如果这不起作用,您可以尝试为 BPB 保留 space,如下所示:
[BITS 16]
[ORG 0x7C00]
jmp start
nop
resb 8 + 25
start:
jmp 0x0000:start_16 ; In case bootloader is at 0x07C0:0x0000
start_16:
您可以使用以下代码转储寄存器以帮助调试问题:
dumpregs:
push es
pusha
push 0xb800
pop es
mov di, [vidmem_ptr]
mov bp, sp
mov cx, 8
dump_loop:
dec bp
dec bp
mov ax, [bp + 16]
call printhex2
inc di
inc di
loop dump_loop
mov [vidmem_ptr], di
popa
pop es
ret
printhex2:
push ax
mov al, ah
call convhex1
pop ax
convhex1:
aam 16
; DB 0D4h, 16
xchg al, ah
call convhex
mov al, ah
convhex:
cmp al, 10
jb lessthan_10
add al, 'A' - '0' - 10
lessthan_10:
add al, '0'
stosb
mov al, 7
stosb
ret
vidmem_ptr dw 5 * 80 * 2 ; start at row 5
它使用直接视频写入来转储所有通用寄存器。它使用 PUSHA/POPA 寄存器顺序:AX CX DX BX SP BP SI DI
如果您将原始代码中的 INT 0x10 指令与调用此函数括起来,您应该得到如下输出:
尤其要确保调试输出的每一行左侧的 8 个数字与右侧的 8 个数字匹配。您正在使用的 BIOS 调用不应更改任何寄存器。
我以前见过类似的症状。场景大概是这样的:
a) 您正在从 USB 闪存启动笔记本电脑
b) BIOS 查看设备的第一个扇区并尝试决定是否应该将其视为软盘或硬盘驱动器。它找不到有效的分区 table,因此决定将其视为软盘。
c) BIOS 尝试 "helpful" 并修补 BPB 中的某些字段。
d) 你没有 BPB,所以你的代码 and/or 数据被破坏,导致奇怪的事情发生(代码中的微小变化导致不同的奇怪事情发生)。
遗憾的是,我自己没有执行此操作的硬件。我不知道为什么 BIOS 假设有一个 BPB,或者它假设存在多个不同 "BPB formats" 中的哪一个,或者它认为它可能正在修补什么或者为什么它认为有必要修补任何东西。我只知道有些 BIOS 会丢弃偏移量 0x1C 到 0x1F 处的字节和偏移量 0x24 处的字节。