AMF*_*ech 6 x86 assembly usb-drive bios bootloader
我对装配很新,但我正试图深入了解低级计算的世界.我正在尝试学习如何编写将作为引导加载程序代码运行的汇编代码; 所以独立于任何其他操作系统,如Linux或Windows.在阅读了本页和其他几个x86指令集列表之后,我想出了一些汇编代码,它应该在屏幕上打印10 A然后是1 B.
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
Run Code Online (Sandbox Code Playgroud)
所以输出应该如下所示:
AAAAAAAAAAB
Run Code Online (Sandbox Code Playgroud)
我使用在Windows 10 Ubuntu Bash程序上运行的nasm汇编程序汇编代码.在生成.bin文件之后,我使用十六进制编辑器打开它.我使用相同的十六进制编辑器将该.bin文件的内容复制到闪存驱动器的前512个字节中.将程序写入闪存驱动器后,我将其断开并将其插入装有Intel Core i3-7100的计算机.在启动时,我选择了我的USB闪存驱动器作为启动设备,只是为了得到以下输出:
A
Run Code Online (Sandbox Code Playgroud)
在改变程序中的各种东西之后,我终于感到沮丧,并在另一台计算机上尝试了该程序.另一台电脑是i5-2520m的笔记本电脑.我遵循了前面提到的相同过程.果然,它给了我预期的输出:
AAAAAAAAAAB
Run Code Online (Sandbox Code Playgroud)
我立即在i3的原始计算机上尝试过它,但它仍然没有用.
所以我的问题是:为什么我的程序使用一个x86处理器而不是另一个?它们都支持x86指令集.是什么赋予了?
解决方案:
好的,我已经能够在一些帮助下找到真正的解决方案.如果您在下面阅读Michael Petch的答案,您将找到解决我的问题的解决方案,以及BIOS寻找BPB的另一个问题.
这是我的代码的问题:我正在将程序写入我的闪存驱动器的第一个字节.这些字节被加载到内存中,但是一些BIOS中断正在使用这些字节.所以我的程序被BIOS覆盖了.为防止这种情况,您可以添加BPB描述,如下所示.如果你的BIOS工作方式与我的相同,它只会覆盖内存中的BPB,而不会覆盖你的程序.或者,您可以将以下代码添加到程序的顶部:
jmp start
resb 0x50
start:
;enter code here
Run Code Online (Sandbox Code Playgroud)
此代码(由Ross Ridge提供)将您的程序推送到内存位置0x50(偏离0x7c00),以防止它在执行期间被BIOS覆盖.
还要记住,每当调用任何子例程时,您使用的寄存器的值都可能被覆盖.请确保你要么使用push
,pop
或调用子程序之前,你的值保存到内存中.请看Martin Rosenau在下面的回答,了解更多相关信息.
感谢所有回复我问题的人.我现在对这种低级别的东西如何运作有了更好的理解.
汇编代码仅适用于我的两个 x86处理器之一
不是处理器而是 BIOS:
该int
指令实际上是call
。该指令调用一些子程序(通常用汇编程序编写)。
(你甚至可以用你自己的子程序替换那个子程序——例如,这实际上是由 MS-DOS 完成的。)
在两台计算机上,您有两个不同的 BIOS 版本(甚至供应商),这意味着int 10h
指令调用的子程序是由不同的程序员编写的,因此不会完全相同。
只得到以下输出
我怀疑这里的问题是int 10h
第一台计算机上调用的子例程没有保存寄存器值,而第二台计算机上的例程保存。
换句话说:
在第一台计算机上,由 调用的例程int 10h
可能如下所示:
...
mov cl, 5
mov ah, 6
...
Run Code Online (Sandbox Code Playgroud)
...所以在int 10h
调用之后ah
寄存器不再包含该值0Eh
,甚至可能是这种情况cl
寄存器被修改的情况(这将在无限循环中结束)。
为了避免这个问题,您可以使用保存cl
寄存器push
(您必须保存整个cx
寄存器)并在int
指令后恢复它。您还必须ah
在每次调用int 10h
子例程之前设置寄存器的值,因为您无法确定从那时起它没有被修改:
push cx
mov ah, 0Eh
int 10h
pop cx
Run Code Online (Sandbox Code Playgroud)
mov sp, ...
...ret
请考虑 Peter Cordes 的评论:
如何进行ret
指导工作,它是如何与sp
和ss
寄存器有什么关系?
这 ret
这里说明绝对不会达到您的预期!
在软盘上,引导扇区通常包含以下代码:
mov ax, 0 ; (may be written as "xor ax, ax")
int 16h
int 19h
Run Code Online (Sandbox Code Playgroud)
int 19h
完全符合您对ret
指令的期望。
但是 BIOS 将再次启动计算机,这意味着它将从您的 USB 记忆棒加载代码并再次执行它。
你会得到以下结果:
啊啊啊啊啊啊啊啊啊啊啊啊……
因此int 16h
插入指令。这将ax
在调用int 16h
子程序之前等待用户在寄存器值为 0时按下键盘上的某个键。
或者,您可以简单地添加一个无限循环:
.endlessLoop:
jmp .endlessLoop
Run Code Online (Sandbox Code Playgroud)
mov ss, ...
当这两条指令之间发生中断时:
mov ss, ax
; <--- Here
mov sp, 4096
Run Code Online (Sandbox Code Playgroud)
...sp
和ss
寄存器的组合不代表值的“有效”表示。
如果您不走运,中断会将数据写入您不想要的内存中的某处。它甚至可能会覆盖您的程序!
因此,您通常在修改ss
寄存器时锁定中断:
cli ; Forbid interrupts
mov ss, ax
mov sp, 4096
sti ; Allow interrupts again
Run Code Online (Sandbox Code Playgroud)
这可能会成为关于这个主题的规范答案.
如果您正试图使用USB设备启动在真实的硬件,那么你可能会遇到的,即使你得到它在工作的另一个问题BOCHS和QEMU.如果您的BIOS设置为进行USB FDD仿真(而不是USB HDD或其他),则可能需要将BIOS参数块(BPB)添加到引导加载程序的开头.你可以像这样创建一个假的:
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]
Run Code Online (Sandbox Code Playgroud)
ORG
如果您只需要默认值0x0000,请将指令调整为您需要的值或省略它.
如果您要修改代码以使Unix/Linux file
命令上方的布局可能能够转储它认为构成磁盘映像中MBR的BPB数据.运行该命令file disk.img
,您可能会得到此输出:
disk.img:DOS/MBR引导扇区,代码偏移量0x3c + 2,OEM-ID"mkfs.fat",根条目224,扇区2880(卷<= 32 MB),扇区/ FAT 9,扇区/磁道18,串行数字0x2d7e5a1a,未标记,FAT(12位)
在这个OP原始代码的情况下,它可能已被修改为如下所示:
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
Run Code Online (Sandbox Code Playgroud)
正如已经指出的那样 - 你不能ret
结束引导加载程序.您可以将其置于无限循环中或暂停处理器,cli
然后执行hlt
.
如果您在堆栈上分配大量数据或开始写入引导加载程序的512字节以外的数据,则应将自己的堆栈指针(SS:SP)设置为不会干扰您自己的代码的内存区域.此问题中的原始代码确实设置了堆栈指针.这是对阅读此Q/A的其他人的一般观察.我在Stackoverflow的答案中有更多相关信息,其中包含一般的Bootloader提示.
如果您想知道BIOS是否可能覆盖BPB中的数据并确定它写入了什么值,您可以使用此引导加载程序代码来转储BPB,因为引导程序在控制转移到它之后会看到它.在正常情况下,前3个字节EB 3C 90
后面应该跟一系列AA
.任何不可能的值AA
都可能被BIOS覆盖.此代码在NASM中,可以组装到引导加载程序中nasm -f bin boot.asm -o boot.bin
; Simple bootloader that dumps the bytes in the BIOS Parameter
; Block BPB. First 3 bytes should be EB 3C 90. The rest should be 0xAA
; unless you have a BIOS that wrote drive geometry information
; into what it thinks is a BPB.
; Macro to print a character out with char in BX
%macro print_char 1
mov al, %1
call bios_print_char
%endmacro
org 0x7c00
bits 16
boot:
jmp main
TIMES 3-($-$$) DB 0x90 ; Support 2 or 3 byte encoded JMPs before BPB.
; Fake BPB filed with 0xAA
TIMES 59 DB 0xAA
main:
xor ax, ax
mov ds, ax
mov ss, ax ; Set stack just below bootloader at 0x0000:0x7c00
mov sp, boot
cld ; Forward direction for string instructions
mov si, sp ; Print bytes from start of bootloader
mov cx, main-boot ; Number of bytes in BPB
mov dx, 8 ; Initialize column counter to 8
; So first iteration prints address
.tblloop:
cmp dx, 8 ; Every 8 hex value print CRLF/address/Colon/Space
jne .procbyte
print_char 0x0d ; Print CRLF
print_char 0x0a
mov bx, si ; Print current address
call print_word_hex
print_char ':' ; Print ': '
print_char ' '
xor dx, dx ; Reset column counter to 0
.procbyte:
lodsb ; Get byte to print in AL
call print_byte_hex ; Print the byte (in BL) in HEX
print_char ' '
inc dx ; Increment the column count
dec cx ; Decrement number of bytes to process
jnz .tblloop
cli ; Halt processor indefinitely
.end:
hlt
jmp .end
; Print the character passed in AL
bios_print_char:
push bx
xor bx, bx ; Attribute=0/Current Video Page=0
mov ah, 0x0e
int 0x10 ; Display character
pop bx
ret
; Print the 16-bit value in AX as HEX
print_word_hex:
xchg al, ah ; Print the high byte first
call print_byte_hex
xchg al, ah ; Print the low byte second
call print_byte_hex
ret
; Print lower 8 bits of AL as HEX
print_byte_hex:
push bx
push cx
push ax
lea bx, [.table] ; Get translation table address
; Translate each nibble to its ASCII equivalent
mov ah, al ; Make copy of byte to print
and al, 0x0f ; Isolate lower nibble in AL
mov cl, 4
shr ah, cl ; Isolate the upper nibble in AH
xlat ; Translate lower nibble to ASCII
xchg ah, al
xlat ; Translate upper nibble to ASCII
xor bx, bx ; Attribute=0/Current Video Page=0
mov ch, ah ; Make copy of lower nibble
mov ah, 0x0e
int 0x10 ; Print the high nibble
mov al, ch
int 0x10 ; Print the low nibble
pop ax
pop cx
pop bx
ret
.table: db "0123456789ABCDEF", 0
; boot signature
TIMES 510-($-$$) db 0
dw 0xAA55
Run Code Online (Sandbox Code Playgroud)
对于在将控制权转移到引导加载程序代码之前未更新BPB的任何BIOS,输出应如下所示:
Run Code Online (Sandbox Code Playgroud)7C00: EB 3C 90 AA AA AA AA AA 7C08: AA AA AA AA AA AA AA AA 7C10: AA AA AA AA AA AA AA AA 7C18: AA AA AA AA AA AA AA AA 7C20: AA AA AA AA AA AA AA AA 7C28: AA AA AA AA AA AA AA AA 7C30: AA AA AA AA AA AA AA AA 7C38: AA AA AA AA AA AA
归档时间: |
|
查看次数: |
758 次 |
最近记录: |