我对汇编语言还很陌生,但是我正尝试进入低级计算领域。我正在尝试学习如何编写将作为引导加载程序代码运行的汇编代码。因此独立于任何其他操作系统,例如Linux或Windows。阅读此页面和其他一些x86指令集列表之后,我想出了一些汇编代码,该代码应该在屏幕上打印10 A,然后打印1B。
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
我使用在Windows 10 Ubuntu Bash程序上运行的nasm汇编器汇编了代码。生成.bin文件后,我使用十六进制编辑器将其打开。我使用相同的十六进制编辑器将该.bin文件的内容复制到闪存驱动器的前512个字节中。将程序写入闪存驱动器后,我将其断开连接并将其插入装有Intel Core i3-7100的计算机。在启动时,我选择了USB闪存驱动器作为启动设备,但仅获得以下输出:
A
在更改了程序中的各种内容之后,我最终感到沮丧,并在另一台计算机上尝试了该程序。另一台计算机是一台配备i5-2520m的笔记本电脑。我遵循了前面提到的相同过程。果然,它给了我预期的输出:
AAAAAAAAAAB
我立即使用i3在原始计算机上进行了尝试,但仍然无法正常工作。
所以我的问题是:为什么我的程序只能在一个x86处理器上工作而不能在另一个处理器上工作?它们都支持x86指令集。是什么赋予了?
解决方案:
好的,我已经能够在一些帮助下找到真正的解决方案。如果您阅读下面的Michael Petch的答案,您将找到解决我的问题的解决方案,以及另一个BIOS寻找BPB的问题。
这是我的代码的问题:我正在将程序写入闪存驱动器的第一个字节。这些字节已加载到内存中,但是某些BIOS中断正在使用这些字节本身。所以我的程序被BIOS覆盖了。为防止这种情况,可以如下所示添加BPB描述。如果您的BIOS与我的BIOS相同,它将仅覆盖内存中的BPB,但不会覆盖您的程序。或者,您可以将以下代码添加到程序顶部:
jmp start
resb 0x50
start:
;enter code here
此代码(由Ross Ridge提供)将把程序推送到内存位置0x50(偏移量为0x7c00),以防止BIOS在执行过程中被覆盖。
还要记住,每当调用任何子例程时,您正在使用的寄存器的值都可能被覆盖。在调用子例程之前push
,请确保使用pop
或将值保存到内存中。请参阅下面的Martin Rosenau的答案,以了解有关此内容的更多信息。
感谢所有回答我的问题的人。现在,我对这些低级内容的工作原理有了更好的了解。
这可能可以成为关于这个问题的规范答案。
如果您尝试使用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]
将ORG
指令调整为所需,如果只需要默认的0x0000,则将其忽略。
如果您要修改代码以使其具有Unix / Linux file
命令上方的布局,则可能能够转出它认为已在磁盘映像中构成VBR的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
如前所述-您无法ret
结束引导加载程序。你可以把它变成一个无限循环或停止与处理器cli
之后hlt
。
如果您在堆栈上分配了大量数据或开始写入引导加载程序512字节之外的数据,则应将自己的堆栈指针(SS:SP)设置到不会干扰自己代码的内存区域。此问题中的原始代码确实设置了堆栈指针。这是对阅读此问答的其他人的一般观察。在包含通用Bootloader提示的 Stackoverflow答案中,我有关于此的更多信息。
如果您想知道BIOS是否会覆盖BPB中的数据并确定它写入的值,则可以使用此引导加载程序代码来转储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 ax, 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
对于在将控制权转移到引导加载程序代码之前未更新BPB的所有BIOS的输出应如下所示:
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
如果要实施ext2文件系统,则不需要BIOS参数块(BPB),对吗?如果这是正确的,那么如何防止BIOS期望BPB?
@DanHoynoski:如果使用FDD仿真从USB引导,您仍然需要为BPB分配空间(因为BIOS会覆盖该区域的一部分),但是它可能全为零,因为EXT2不依赖BPB中的数据就像FAT一样。
.tblloop中的@MichaelPetch将si移至bx,这是错误的。print_word_hex使用ax寄存器。
@SuperKooks:在您删除几分钟前的第一条评论和您的新评论之间,我实际上已经解决了该问题并更新了答案;-)。我使用的函数的原始版本在BX中传递了该参数,有时我使用AX对其进行了标准化,但并未更新问题中的代码。感谢您发现问题。