X86 我如何编写一个引导扇区,从它';什么事?
我能够编写一个简单的Hello World引导扇区/引导加载程序,并通过将其放入U盘的前512字节并引导到其中,在实际的x86硬件和BIOS上运行它。现在我想知道如何将更多数据放在U盘的其余部分,并用引导扇区代码将其加载到RAM中。如何做到这一点?首先,我应该注意,我是编写低级代码的新手,所以我不能保证我的解决方案的可移植性。批评是受欢迎的 如果您已经了解了这一点,我假设您理解BIOS中断调用的概念,例如用于在屏幕上显示字符的调用。您需要从U盘加载数据的是 理解API 与可用于在屏幕上打印字符的中断0x10一样,中断0x13执行各种磁盘操作,您必须通过向X86 我如何编写一个引导扇区,从它';什么事?,x86,bootloader,bios,low-level,X86,Bootloader,Bios,Low Level,我能够编写一个简单的Hello World引导扇区/引导加载程序,并通过将其放入U盘的前512字节并引导到其中,在实际的x86硬件和BIOS上运行它。现在我想知道如何将更多数据放在U盘的其余部分,并用引导扇区代码将其加载到RAM中。如何做到这一点?首先,我应该注意,我是编写低级代码的新手,所以我不能保证我的解决方案的可移植性。批评是受欢迎的 如果您已经了解了这一点,我假设您理解BIOS中断调用的概念,例如用于在屏幕上显示字符的调用。您需要从U盘加载数据的是 理解API 与可用于在屏幕上打印字符的
AH
写入适当的数字来选择所需的操作。我们将使用call number0x02
从驱动器读取数据。此调用需要以下寄存器中的以下参数:
AH
:0x02
AL
:要读取的扇区数
CH
:气缸
CL
:扇区
DH
:头部
DL
:驱动器
BX
:内存地址-在RAM中写入数据的位置
大多数参数指定从驱动器上的何处读取。BIOS想要知道它应该从DL
中读取的驱动器的ID号,并且想要知道该驱动器上数据的坐标。请注意,BIOS一次只能从驱动器读取整个扇区,因此您可以指定扇区数,而不是字节数。我相信一个扇区通常有512字节长
现在,查看此文档会立即引发两个问题:
dd
或其他工具在U盘上放了一个二进制文件,比如boot.bin
或boot.img
。我知道要加载的数据在该文件中的字节偏移量,但如何将其转换为缸盖扇区坐标DL
。这至少在我的硬件(在两台计算机上测试)和qemu中是正确的。在我所有的测试设备上,这最初是0x80
,根据(见表“驱动器表”)对应于“第一个硬盘驱动器”
对于第二个问题,答案似乎是,至少在我的机器上,使用可用的CHS到LBA转换表。我的理解是,你可以把烧录到U盘上的任何二进制文件想象成分成512字节的“扇区”数组。第一个扇区有CHS坐标(0,0,1),然后是(0,0,2),依此类推到(0,0,63),然后是(0,1,0),依此类推。换句话说,您可以将CHS坐标(c、h、s)解释为一个三位数的base-64数字,它只提供文件512字节扇区的索引。至少我的硬件似乎就是这样做的
示例代码
计划是这样的。从USB记忆棒中的字节513开始(因此,在引导扇区之后),我们将放置一个字符串。这应与驱动器上的CHS坐标(0,0,2)相对应,默认情况下,驱动器ID仅位于DL
中。我们将进行BIOS调用以读取这些坐标处的一个扇区。我将把它们读入内存偏移量0x7E00,因为根据,它对应于一大块可用的空闲内存。然后我们将在屏幕上打印字符串
代码如下:
ORG 0x7C00
;
; Main code
;
; Clear segment registers, always necessary
MOV AX, 0
MOV DS, AX
MOV ES, AX
; Read sector 2 of this drive into memory
MOV AH, 2 ; Code to read data
MOV BX, 0x0000_7E00 ; Destination
MOV AL, 1 ; Number of sectors to read
MOV CH, 0 ; Cylinder
MOV DH, 0 ; Head
MOV CL, 2 ; Sector
INT 0x13 ; Fire in the hole!
MOV BX, 0x0000_7E00
CALL print
;
; CPU trap
;
JMP $
;
; Functions
;
print:
; Prints to the screen the zero-terminated string starting at [BX].
PUSHA
MOV AH, 0x0E
loop:
MOV AL, [BX]
CMP AL, 0
JE break
INT 0x10
ADD BX, 1
JMP loop
break:
POPA
RET
;
; Padding and magic number
;
TIMES 510-($-$$) DB 0
DW 0xAA55
;
; This is after the boot sector and so not initially loaded by the BIOS
;
DB 'Hello, world - from disc sector 2!'
DB 0
为了完整起见,我将记录我用来编译它的命令(在Ubuntu上)。我使用NASM组装二进制文件:
nasm -f bin -o boot.bin main.s
其中main.s
是上面的程序集文件,然后用
dd if=boot.bin of=[YOUR USB STICK'S FILE HANDLE HERE]
其中,例如,我的U盘的文件句柄是/dev/sda
,但不要只是复制粘贴,以防在您的机器上不同,并且您覆盖了其他设备上的引导扇区
便携性注意事项
上述代码在qemu、32位ThinkPad和旧的64位三星笔记本电脑上都能正常工作。然而,我看到了一些示例代码,这些代码在其他设置中可能需要做一些其他的事情,所以我将在这里提到它们
第一个是在读取驱动器之前,进行另一个中断0x13
调用以“重置”驱动器。我不确定这到底是做什么的,也不确定是否有必要,但下面是代码:
MOV AH, 0
INT 0x13
你应该在阅读之前这样做。我的代码可以使用或不使用此步骤。同样,BIOS假定DL
包含驱动器ID
我看到的另一件事是从引导扇区开始,使用如下表:
OperatingSystemName db "PrettyOS" ; 8 byte
BytesPerSec dw 512
SecPerClus db 1
ReservedSec dw 1
NumFATs db 2
RootEntries dw 224
TotSec dw 2880
MediaType db 0xF0
FATSize dw 9
SecPerTrack dw 18
NumHeads dw 2
HiddenSec dd 0
TotSec32 dd 0
DriveNum db 0
Reserved db 0
BootSig db 0x29
VolumeSerialNum dd 0xD00FC0DE
VolumeLabel db "PRETTY OS " ; 11 byte
FileSys db "FAT12 " ; 8 byte
()
这将先于程序集文件顶部的任何代码,然后是一条跳转指令,让您越过它进入主代码。我没有发现在我的设置中需要这样的东西,但是这种东西叫做a,所以如果我的示例代码不适用于您,可能需要研究一下。我的假设是BIOS在跳转到您的代码之前读取此表,并使用它设置从驱动器读取时可能需要的一些内部状态,例如每个扇区的字节数。看起来合理,但我不是引导加载程序专家。您忘记定义
堆
。我认为这应该是引导签名之后的标签,在字符串前面,如果您计划加载第二个扇区与内存中的第一个扇区相邻,匹配磁盘上的布局。i、 e.您可以省略mov bx,heap
,因为int 0x13
不会改变B