X86 我如何编写一个引导扇区,从它';什么事?

X86 我如何编写一个引导扇区,从它';什么事?,x86,bootloader,bios,low-level,X86,Bootloader,Bios,Low Level,我能够编写一个简单的Hello World引导扇区/引导加载程序,并通过将其放入U盘的前512字节并引导到其中,在实际的x86硬件和BIOS上运行它。现在我想知道如何将更多数据放在U盘的其余部分,并用引导扇区代码将其加载到RAM中。如何做到这一点?首先,我应该注意,我是编写低级代码的新手,所以我不能保证我的解决方案的可移植性。批评是受欢迎的 如果您已经了解了这一点,我假设您理解BIOS中断调用的概念,例如用于在屏幕上显示字符的调用。您需要从U盘加载数据的是 理解API 与可用于在屏幕上打印字符的

我能够编写一个简单的Hello World引导扇区/引导加载程序,并通过将其放入U盘的前512字节并引导到其中,在实际的x86硬件和BIOS上运行它。现在我想知道如何将更多数据放在U盘的其余部分,并用引导扇区代码将其加载到RAM中。如何做到这一点?

首先,我应该注意,我是编写低级代码的新手,所以我不能保证我的解决方案的可移植性。批评是受欢迎的

如果您已经了解了这一点,我假设您理解BIOS中断调用的概念,例如用于在屏幕上显示字符的调用。您需要从U盘加载数据的是

理解API 与可用于在屏幕上打印字符的中断0x10一样,中断0x13执行各种磁盘操作,您必须通过向
AH
写入适当的数字来选择所需的操作。我们将使用call number
0x02
从驱动器读取数据。此调用需要以下寄存器中的以下参数:

AH
0x02

AL
:要读取的扇区数

CH
:气缸

CL
:扇区

DH
:头部

DL
:驱动器

BX
:内存地址-在RAM中写入数据的位置

大多数参数指定从驱动器上的何处读取。BIOS想要知道它应该从
DL
中读取的驱动器的ID号,并且想要知道该驱动器上数据的坐标。请注意,BIOS一次只能从驱动器读取整个扇区,因此您可以指定扇区数,而不是字节数。我相信一个扇区通常有512字节长

现在,查看此文档会立即引发两个问题:

  • 我如何找到从中加载代码的U盘的驱动器ID
  • 当我把数据放在U盘上时,我只是用
    dd
    或其他工具在U盘上放了一个二进制文件,比如
    boot.bin
    boot.img
    。我知道要加载的数据在该文件中的字节偏移量,但如何将其转换为缸盖扇区坐标
  • 我将尝试回答这两个问题,然后我们将做一个Hello World示例

    对于第一个问题:当BIOS跳转到引导扇区时,会将驱动器的ID放入
    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