Assembly 通过USB驱动器引导的自定义引导加载程序在某些计算机上产生不正确的输出
我对汇编相当陌生,但我正试图潜入低级计算的世界。我试图学习如何编写作为引导加载程序代码运行的汇编代码;因此独立于任何其他操作系统,如Linux或Windows。在阅读了x86指令集和其他一些列表之后,我想出了一些汇编代码,应该在屏幕上打印10个a,然后打印1个BAssembly 通过USB驱动器引导的自定义引导加载程序在某些计算机上产生不正确的输出,assembly,x86,bootloader,bios,usb-drive,Assembly,X86,Bootloader,Bios,Usb Drive,我对汇编相当陌生,但我正试图潜入低级计算的世界。我试图学习如何编写作为引导加载程序代码运行的汇编代码;因此独立于任何其他操作系统,如Linux或Windows。在阅读了x86指令集和其他一些列表之后,我想出了一些汇编代码,应该在屏幕上打印10个a,然后打印1个B BITS 16 start: mov ax, 07C0h ; Set up 4K stack space after this bootloader add ax, 288 ; (409
BITS 16
start:
mov ax, 07C0h ; Set up 4K stack space after this bootloader
add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
mov ss, ax
mov sp, 4096
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
mov cl, 10 ; Use this register as our loop counter
mov ah, 0Eh ; This register holds our BIOS instruction
.repeat:
mov al, 41h ; Put ASCII 'A' into this register
int 10h ; Execute our BIOS print instruction
cmp cl, 0 ; Find out if we've reached the end of our loop
dec cl ; Decrement our loop counter
jnz .repeat ; Jump back to the beginning of our loop
jmp .done ; Finish the program when our loop is done
.done:
mov al, 42h ; Put ASCII 'B' into this register
int 10h ; Execute BIOS print instruction
ret
times 510-($-$$) db 0 ; Pad remainder of boot sector with 0s
dw 0xAA55
因此,输出应如下所示:
AAAAAAAAAAB
...
mov cl, 5
mov ah, 6
...
org 0x7c00
bits 16
boot:
jmp main
TIMES 3-($-$$) DB 0x90 ; Support 2 or 3 byte encoded JMPs before BPB.
; Dos 4.0 EBPB 1.44MB floppy
OEMname: db "mkfs.fat" ; mkfs.fat is what OEMname mkdosfs uses
bytesPerSector: dw 512
sectPerCluster: db 1
reservedSectors: dw 1
numFAT: db 2
numRootDirEntries: dw 224
numSectors: dw 2880
mediaType: db 0xf0
numFATsectors: dw 9
sectorsPerTrack: dw 18
numHeads: dw 2
numHiddenSectors: dd 0
numSectorsHuge: dd 0
driveNum: db 0
reserved: db 0
signature: db 0x29
volumeID: dd 0x2d7e5a1a
volumeLabel: db "NO NAME "
fileSysType: db "FAT12 "
main:
[insert your code here]
bits 16
boot:
jmp main
TIMES 3-($-$$) DB 0x90 ; Support 2 or 3 byte encoded JMPs before BPB.
; Dos 4.0 EBPB 1.44MB floppy
OEMname: db "mkfs.fat" ; mkfs.fat is what OEMname mkdosfs uses
bytesPerSector: dw 512
sectPerCluster: db 1
reservedSectors: dw 1
numFAT: db 2
numRootDirEntries: dw 224
numSectors: dw 2880
mediaType: db 0xf0
numFATsectors: dw 9
sectorsPerTrack: dw 18
numHeads: dw 2
numHiddenSectors: dd 0
numSectorsHuge: dd 0
driveNum: db 0
reserved: db 0
signature: db 0x29
volumeID: dd 0x2d7e5a1a
volumeLabel: db "NO NAME "
fileSysType: db "FAT12 "
main:
mov ax, 07C0h ; Set up 4K stack space after this bootloader
add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
mov ss, ax
mov sp, 4096
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
mov cl, 10 ; Use this register as our loop counter
mov ah, 0Eh ; This register holds our BIOS instruction
.repeat:
mov al, 41h ; Put ASCII 'A' into this register
int 10h ; Execute our BIOS print instruction
cmp cl, 0 ; Find out if we've reached the end of our loop
dec cl ; Decrement our loop counter
jnz .repeat ; Jump back to the beginning of our loop
jmp .done ; Finish the program when our loop is done
.done:
mov al, 42h ; Put ASCII 'B' into this register
int 10h ; Execute BIOS print instruction
ret
times 510-($-$$) db 0 ; Pad remainder of boot sector with 0s
dw 0xAA55
我使用运行在Windows10UbuntuBash程序上的nasm汇编程序汇编了代码。生成.bin文件后,我使用十六进制编辑器打开了它。我使用相同的十六进制编辑器将.bin文件的内容复制到闪存驱动器的前512字节中。一旦我将程序写入闪存驱动器,我就将其断开,并将其插入一台具有Intel Core i3-7100的计算机。在启动时,我选择我的USB闪存驱动器作为启动设备,只得到以下输出:
A
在更改了程序中的各种内容后,我最终感到沮丧,并在另一台计算机上尝试了该程序。另一台电脑是一台带有i5-2520m的笔记本电脑。我遵循了我前面提到的相同过程。果然,它给了我预期的输出:
AAAAAAAAAAB
我立即用i3在我原来的电脑上试用了一下,但还是不起作用
所以我的问题是:为什么我的程序使用一个x86处理器而不是另一个?它们都支持x86指令集。有什么好处
解决方案:
好的,我已经通过一些帮助找到了真正的解决方案。如果您阅读下面Michael Petch的回答,您将找到一个解决方案,解决我的问题,以及BIOS寻找BPB的另一个问题 我的代码有一个问题:我正在将程序写入闪存驱动器的第一个字节。这些字节被加载到内存中,但一些BIOS中断本身使用这些字节。所以我的程序被BIOS覆盖了。为了防止出现这种情况,可以添加BPB说明,如下所示。如果你的BIOS和我的一样工作,它只会覆盖内存中的BPB,而不会覆盖你的程序。或者,您可以将以下代码添加到程序顶部:
jmp start
resb 0x50
start:
;enter code here
此代码(由Ross Ridge提供)将您的程序推送到内存位置0x50(从0x7c00的偏移量),以防止在执行过程中被BIOS覆盖
还要记住,无论何时调用任何子例程,所使用的寄存器的值都可能被覆盖。在调用子例程之前,请确保使用push
、pop
或将值保存到内存中。请看下面Martin Rosenau的答案,了解更多关于这一点的信息
谢谢所有回答我问题的人。我现在对这种低级的东西是如何工作的有了更好的理解
汇编代码只能在我的两个x86处理器中的一个上运行
这不是处理器,而是生物传感器:
int
指令实际上是call
指令的一个特殊变体。该指令调用一些子程序(通常用汇编语言编写)
(您甚至可以用自己的子例程替换该子例程,例如,这实际上是由MS-DOS完成的。)
在两台计算机上,您有两个不同的BIOS版本(甚至是供应商),这意味着int 10h
指令调用的子程序是由不同的程序员编写的,因此并不完全相同
仅获取以下输出
这里我怀疑的问题是,第一台计算机上由int 10h
调用的子程序没有保存寄存器值,而第二台计算机上的例程保存寄存器值
换言之:
在第一台计算机上,int 10h
调用的例程可能如下所示:
AAAAAAAAAAB
...
mov cl, 5
mov ah, 6
...
org 0x7c00
bits 16
boot:
jmp main
TIMES 3-($-$$) DB 0x90 ; Support 2 or 3 byte encoded JMPs before BPB.
; Dos 4.0 EBPB 1.44MB floppy
OEMname: db "mkfs.fat" ; mkfs.fat is what OEMname mkdosfs uses
bytesPerSector: dw 512
sectPerCluster: db 1
reservedSectors: dw 1
numFAT: db 2
numRootDirEntries: dw 224
numSectors: dw 2880
mediaType: db 0xf0
numFATsectors: dw 9
sectorsPerTrack: dw 18
numHeads: dw 2
numHiddenSectors: dd 0
numSectorsHuge: dd 0
driveNum: db 0
reserved: db 0
signature: db 0x29
volumeID: dd 0x2d7e5a1a
volumeLabel: db "NO NAME "
fileSysType: db "FAT12 "
main:
[insert your code here]
bits 16
boot:
jmp main
TIMES 3-($-$$) DB 0x90 ; Support 2 or 3 byte encoded JMPs before BPB.
; Dos 4.0 EBPB 1.44MB floppy
OEMname: db "mkfs.fat" ; mkfs.fat is what OEMname mkdosfs uses
bytesPerSector: dw 512
sectPerCluster: db 1
reservedSectors: dw 1
numFAT: db 2
numRootDirEntries: dw 224
numSectors: dw 2880
mediaType: db 0xf0
numFATsectors: dw 9
sectorsPerTrack: dw 18
numHeads: dw 2
numHiddenSectors: dd 0
numSectorsHuge: dd 0
driveNum: db 0
reserved: db 0
signature: db 0x29
volumeID: dd 0x2d7e5a1a
volumeLabel: db "NO NAME "
fileSysType: db "FAT12 "
main:
mov ax, 07C0h ; Set up 4K stack space after this bootloader
add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
mov ss, ax
mov sp, 4096
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
mov cl, 10 ; Use this register as our loop counter
mov ah, 0Eh ; This register holds our BIOS instruction
.repeat:
mov al, 41h ; Put ASCII 'A' into this register
int 10h ; Execute our BIOS print instruction
cmp cl, 0 ; Find out if we've reached the end of our loop
dec cl ; Decrement our loop counter
jnz .repeat ; Jump back to the beginning of our loop
jmp .done ; Finish the program when our loop is done
.done:
mov al, 42h ; Put ASCII 'B' into this register
int 10h ; Execute BIOS print instruction
ret
times 510-($-$$) db 0 ; Pad remainder of boot sector with 0s
dw 0xAA55
。。。因此,在调用int 10h
之后,ah
寄存器不再包含值0Eh
,甚至可能是cl
寄存器被修改的情况(这将在无休止的循环中结束)
为避免此问题,您可以使用push
保存cl
寄存器(您必须保存整个cx
寄存器),并在int
指令后将其还原。您还必须在调用int 10h
子例程之前设置ah
寄存器的值,因为您无法确定从那时起它没有被修改过:
push cx
mov ah, 0Eh
int 10h
pop cx
mov sp,…
<代码>ret
请考虑一下Peter Cordes的评论:
ret
指令是如何工作的,它与sp
和ss
寄存器有何关系
这里的ret
指令肯定不会达到您期望的效果
在软盘上,引导扇区通常包含以下代码:
mov ax, 0 ; (may be written as "xor ax, ax")
int 16h
int 19h
int 19h
完全符合您对ret
指令的期望
但是BIOS将再次引导计算机,这意味着它将从U盘加载代码并再次执行
您将得到以下结果:
AAAAAAAAAAAAAAAAAAAB
因此插入int16h
指令。这将等待用户在调用int 16h
子程序之前,当ax
寄存器的值为0时按下键盘上的键
或者,您可以简单地添加一个无止境的循环:
.endlessLoop:
jmp .endlessLoop
mov-ss,…
当这两条指令之间发生中断时:
mov ss, ax
; <--- Here
mov sp, 4096
汇编代码只能在我的两个x86处理器中的一个上运行
这不是处理器,而是生物传感器:
int
指令实际上是call
指令的一个特殊变体。该指令调用一些子程序(通常用汇编语言编写)
(你甚至可以用你自己的子程序来代替这个子程序——这实际上是由MS-DO完成的。)