为什么我的根目录没有被加载? (FAT12)

时间:2017-08-01 10:06:24

标签: assembly x86 filesystems nasm bootloader

我正在编写程序集中的第1阶段引导加载程序,我试图将FAT12文件系统加载到内存中,以便我可以加载我的第2阶段引导加载程序。我已经设法将FAT加载到内存中,但是我很难将根目录加载到内存中。

我目前正在使用this作为参考,并产生了以下内容:

.load_root:
    ;es is 0x7c0
    xor dx, dx              ; blank dx for division
    mov si, fat_loaded      ; inform user that FAT is loaded
    call print
    mov al, [FATcount]      ; calculate how many sectors into the disk must be loaded
    mul word [SectorsPerFAT]
    add al, [ReservedSectors]
    div byte [SectorsPerTrack]
    mov ch, ah              ; Store quotient in ch for cylinder number
    mov cl, al              ; Store remainder in cl for sector number

    xor dx, dx
    xor ax, ax
    mov al, ch              ; get back to "absolute" sector number
    mul byte [SectorsPerTrack]
    add al, cl
    mul word [BytesPerSector]
    mov bx,ax               ; Memory offset to load to data into memory after BOTH FATs (should be 0x2600, physical address should be 0xA200)

    xor dx, dx              ; blank dx for division
    mov ax, 32
    mul word [MaxDirEntries]
    div word [BytesPerSector] ; number of sectors root directory takes up (should be 14)

    xor dh, dh              ; head 0
    mov dl, [boot_device]   ; boot device

    mov ah, 0x02            ; select read mode

    int 13h
    cmp ah, 0
    je .load_OS
    mov si, error_text
    call print
    jmp $

但是,如果我使用gdb检查0xA200处的内存,我只看到0。我的根目录 包含一个文件 - 我已将一个名为OS.BIN的文件放在根目录中进行测试。

在读取操作之后在gdb中使用info registers会给出以下输出:

eax            0xe      14
ecx            0x101    257
edx            0x0      0
ebx            0x2600   9728
esp            0x76d0   0x76d0
ebp            0x0      0x0
esi            0x16d    365
edi            0x0      0
eip            0x7cdd   0x7cdd
eflags         0x246    [ PF ZF IF ]
cs             0x0      0
ss             0x53     83
ds             0x7c0    1984
es             0x7c0    1984
fs             0x0      0
gs             0x0      0

操作的状态为0,读取的扇区数为14,es:bx指向0xA200,但x/32b 0xa200显示32 0,当我希望看到OS的数据时。 BIN。

修改 我在中断之前做了info registers,输出如下:

eax            0x20e    526
ecx            0x101    257
edx            0x0      0
ebx            0x2600   9728
esp            0x76d0   0x76d0
ebp            0x0      0x0
esi            0x161    353
edi            0x0      0
eip            0x7cc8   0x7cc8
eflags         0x246    [ PF ZF IF ]
cs             0x0      0
ss             0x53     83
ds             0x7c0    1984
es             0x7c0    1984
fs             0x0      0
gs             0x0      0

除了功能请求号已被状态码替换外,其余与之后相同。

我哪里错了?我是从错误的CHS地址读的吗?还是其他一些简单的错误?我怎么能纠正这个?

我正在使用fat_imgen制作我的磁盘映像。用于创建磁盘映像的命令是fat_imgen -c -f floppy.flp -F -s bootloader.bin,用于将OS.BIN添加到映像的命令是fat_imgen -m -f floppy.flp -i OS.BIN

我有BIOS Parameter Block(BPB)代表使用FAT12的1.44MB软盘:

jmp short loader
times 9 db 0

BytesPerSector: dw 512
SectorsPerCluster: db 1
ReservedSectors: dw 1
FATcount: db 2
MaxDirEntries: dw 224
TotalSectors: dw 2880
db 0
SectorsPerFAT: dw 9
SectorsPerTrack: dw 18
NumberOfHeads: dw 2
dd 0
dd 0
dw 0
BootSignature: db 0x29
VolumeID: dd 77
VolumeLabel: db "Bum'dOS   ",0
FSType: db "FAT12   "

我有另一个看似有效的函数将FAT12表加载到内存地址0x7c0:0x0200(物理地址0x07e00):

;;;Start loading File Allocation Table (FAT)
.load_fat:
    mov ax, 0x07c0          ; address from start of programs
    mov es, ax
    mov ah, 0x02            ; set to read
    mov al, [SectorsPerFAT]   ; how many sectors to load
    xor ch, ch              ; cylinder 0
    mov cl, [ReservedSectors]  ; Load FAT1
    add cl, byte 1
    xor dh, dh              ; head 0
    mov bx, 0x0200          ; read data to 512B after start of code
    int 13h
    cmp ah, 0
    je .load_root
    mov si, error_text
    call print
    hlt

2 个答案:

答案 0 :(得分:4)

问题分析

您的代码存在的问题是您无法从磁盘上的某个位置读取您期望的内容。虽然您的磁盘读取成功,但它已将错误的扇区加载到内存中。

如果我们查看Ralph Brown的Int 13h/AH=2中断列表,我们会看到输入看起来像这样:

  

磁盘 - 将扇区读入存储器

AH = 02h
AL = number of sectors to read (must be nonzero)
CH = low eight bits of cylinder number
CL = sector number 1-63 (bits 0-5)
high two bits of cylinder (bits 6-7, hard disk only)
DH = head number
DL = drive number (bit 7 set for hard disk)
ES:BX -> data buffer

如果我们在int 13h .load_root之前审核您的注册表,我们会看到这些寄存器包含以下内容:

eax            0x20e   
ecx            0x101 
edx            0x0
ebx            0x2600 
es             0x7c0

所以 ES:BX 是0x7c0:0x2600,它是物理地址0xA200。那是正确的。 AH (0x02)是磁盘读取, AL 中要读取的扇区数是14(0x0e)。这看似合理。问题出现在 ECX EDX 中。如果我们检查您的代码,您似乎正在尝试在根目录开始的磁盘上找到扇区(逻辑块地址):

mov al, [FATcount]      ; calculate how many sectors into the disk must be loaded
mul word [SectorsPerFAT]
add al, [ReservedSectors]

在BIOS参数块中,您SectorsPerFat = 9,ReservedSectors = 1,FATCount = 2.如果我们查看显示此配置的FAT12 design document,看起来像:

enter image description here

您的计算是正确的。 2 * 9 + 1 = 19.前19个逻辑块从LBA 0运行到LBA 18. LBA 19是根目录的起始位置。我们需要将其转换为Cylinders / Heads / Sectors(CHS)。 Logical Block Address to CHS calculation

CHS tuples can be mapped to LBA address with the following formula:

LBA = (C × HPC + H) × SPT + (S - 1)

where C, H and S are the cylinder number, the head number, and the sector number

LBA is the logical block address
HPC is the maximum number of heads per cylinder (reported by 
    disk drive, typically 16 for 28-bit LBA)
SPT is the maximum number of sectors per track (reported by
    disk drive, typically 63 for 28-bit LBA)
LBA addresses can be mapped to CHS tuples with the following formula 
    ("mod" is the modulo operation, i.e. the remainder, and "÷" is 
    integer division, i.e. the quotient of the division where any 
    fractional part is discarded):

    C = LBA ÷ (HPC × SPT)
    H = (LBA ÷ SPT) mod HPC
    S = (LBA mod SPT) + 1

在您的代码SPT = 18中,HPC = 2.如果我们使用LBA为19,我们计算C = 0,H = 1,S = 2的CHS。如果我们查看您传入寄存器的值( CL CH DH ),我们发现您使用了CHS C = 1,H = 0,S = 1。这恰好是LBA 36而不是19.问题是您的计算错误。特别是.load_root

div byte [SectorsPerTrack]
mov ch, ah              ; Store quotient in ch for cylinder number
mov cl, al              ; Store remainder in cl for sector number
[snip]
xor dh, dh              ; head 0
mov dl, [boot_device]   ; boot device
mov ah, 0x02            ; select read mode
int 13h

不幸的是,这不是从LBA计算CHS的正确方法。您与.load_fat存在类似问题,但您很幸运能够计算出正确的值。您正在读取磁盘上的错误扇区,这导致数据加载到您不期望的0xA200。

LBA到CHS的翻译

您需要的是适当的LBA到CHS转换例程。由于在导航FAT12文件结构的不同方面需要这样的功能,因此最好创建一个函数。我们称之为lba_to_chs

在我们编写这样的代码之前,我们应该重新审视这个等式:

C = LBA ÷ (HPC × SPT)
H = (LBA ÷ SPT) mod HPC
S = (LBA mod SPT) + 1

我们可以按原样实现,但如果我们重新设计气缸方程式,我们可以减少我们必须做的工作量。 C = LBA ÷ (HPC × SPT)可以改写为:

C = LBA ÷ (HPC × SPT)
C = LBA ÷ (SPT × HPC)
C = (LBA ÷ SPT) × (1 ÷ HPC)
C = (LBA ÷ SPT) ÷ HPC

如果我们现在看看修订后的公式:

C = (LBA ÷ SPT) ÷ HPC
H = (LBA ÷ SPT) mod HPC
S = (LBA mod SPT) + 1

现在我们应该注意到(LBA ÷ SPT)在两个地方重复了。我们只需要做一次这个等式。同样,因为x86 DIV 指令同时计算余数和商,所以当我们LBA mod SPT时,我们也会免费计算(LBA ÷ SPT)。代码将遵循以下结构:

  1. 计算LBA DIV SPT。这会产生:
    • (LBA ÷ SPT)在商
    • 中 剩下的
    • (LBA mod SPT)
  2. 从步骤(1)中取出剩余部分并进入临时注册
  3. 在步骤(2)中向临时添加1。该寄存器现在包含由S = (LBA mod SPT) + 1
  4. 计算的扇区
  5. 取步骤(1)中的商并除以HPC。
    • 圆柱编号将为商
    • 头将是剩下的。
  6. 我们将等式缩减为几个 DIV 指令和增量/加法。我们可以更简化一些事情。如果我们假设我们使用众所周知的IBM兼容磁盘格式,那么我们也可以说每个磁道扇区(SPT),磁头(HPC),圆柱,磁头和扇区将始终小于256.当任何磁场上的最大LBA时已知的软盘格式除以SPT,结果总是小于256.知道这一点,我们可以避免位于圆柱顶部的两位,并将它们放在 CL 的前两位。我们也可以使用16位×8位无符号除法的 DIV 指令。

    翻译代码

    如果我们采用上面的伪代码,我们可以创建一个相当小的lba_to_chs函数,它接受LBA并将其转换为CHS,并且仅适用于众所周知的IBM兼容软盘格式。

    ;    Function: lba_to_chs
    ; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
    ;              Works ONLY for well known IBM PC compatible floppy disk formats.
    ;
    ;   Resources: http://www.ctyme.com/intr/rb-0607.htm
    ;              https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
    ;              https://stackoverflow.com/q/45434899/3857942
    ;              Sector    = (LBA mod SPT) + 1
    ;              Head      = (LBA / SPT) mod HEADS
    ;              Cylinder  = (LBA / SPT) / HEADS
    ;
    ;      Inputs: SI = LBA
    ;     Outputs: DL = Boot Drive Number
    ;              DH = Head
    ;              CH = Cylinder
    ;              CL = Sector
    ;
    ;       Notes: Output registers match expectation of Int 13h/AH=2 inputs
    ;
    lba_to_chs:
        push ax                    ; Preserve AX
        mov ax, si                 ; Copy 16-bit LBA to AX
        div byte [SectorsPerTrack] ; 16-bit by 8-bit DIV : LBA / SPT
        mov cl, ah                 ; CL = S = LBA mod SPT
        inc cl                     ; CL = S = (LBA mod SPT) + 1
        xor ah, ah                 ; Upper 8-bit of 16-bit value set to 0 for DIV
        div byte [NumberOfHeads]   ; 16-bit by 8-bit DIV : (LBA / SPT) / HEADS
        mov ch, al                 ; CH = C = (LBA / SPT) / HEADS
        mov dh, ah                 ; DH = H = (LBA / SPT) mod HEADS
        mov dl, [boot_device]      ; boot device, not necessary to set but convenient
        pop ax                     ; Restore scratch register
        ret
    

    如果您需要的版本可以处理FAT12支持的所有有效磁盘几何,那么代码必须使用32位/ 16位 DIV 指令,并且必须处理正在处理的柱面10位代替8.示例代码可能如下所示:

    ;    Function: lba_to_chs
    ; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
    ;              Works for all valid FAT12 compatible disk geometries.
    ;
    ;   Resources: http://www.ctyme.com/intr/rb-0607.htm
    ;              https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
    ;              https://stackoverflow.com/q/45434899/3857942
    ;              Sector    = (LBA mod SPT) + 1
    ;              Head      = (LBA / SPT) mod HEADS
    ;              Cylinder  = (LBA / SPT) / HEADS
    ;
    ;      Inputs: SI = LBA
    ;     Outputs: DL = Boot Drive Number
    ;              DH = Head
    ;              CH = Cylinder (lower 8 bits of 10-bit cylinder)
    ;              CL = Sector/Cylinder
    ;                   Upper 2 bits of 10-bit Cylinders in upper 2 bits of CL
    ;                   Sector in lower 6 bits of CL
    ;
    ;       Notes: Output registers match expectation of Int 13h/AH=2 inputs
    ;
    lba_to_chs:
        push ax                    ; Preserve AX
        mov ax, si                 ; Copy LBA to AX
        xor dx, dx                 ; Upper 16-bit of 32-bit value set to 0 for DIV
        div word [SectorsPerTrack] ; 32-bit by 16-bit DIV : LBA / SPT
        mov cl, dl                 ; CL = S = LBA mod SPT
        inc cl                     ; CL = S = (LBA mod SPT) + 1
        xor dx, dx                 ; Upper 16-bit of 32-bit value set to 0 for DIV
        div word [NumberOfHeads]   ; 32-bit by 16-bit DIV : (LBA / SPT) / HEADS
        mov dh, dl                 ; DH = H = (LBA / SPT) mod HEADS
        mov dl, [boot_device]      ; boot device, not necessary to set but convenient
        mov ch, al                 ; CH = C(lower 8 bits) = (LBA / SPT) / HEADS
        shl ah, 6                  ; Store upper 2 bits of 10-bit Cylinder into
        or  cl, ah                 ;     upper 2 bits of Sector (CL)
        pop ax                     ; Restore scratch registers
        ret
    

    您可以使用相同的方式使用这些lba_to_chs功能中的任何一种,并将它们集成到您的.load_fat.load_root代码中。您的代码可能如下所示:

    ;;;Start loading File Allocation Table (FAT)
    .load_fat:
        mov ax, 0x07c0             ; address from start of programs
        mov es, ax
        mov ah, 0x02               ; set to read
        mov al, [SectorsPerFAT]    ; how many sectors to load
    
        mov si, [ReservedSectors]  ; Load FAT1 into SI for input to lba_to_chs
        call lba_to_chs            ; Retrieve CHS parameters and boot drive for LBA
    
        mov bx, 0x0200             ; read data to 512B after start of code
        int 13h
        cmp ah, 0
        je .load_root
        mov si, error_text
        call print
        hlt
    
    ;;;Start loading root directory
    .load_root:
        mov si, fat_loaded
        call print
        xor ax, ax
        mov al, [FATcount]
        mul word [SectorsPerFAT]
        add ax, [ReservedSectors]  ; Compute LBA of oot directory entries
        mov si, ax                 ; Copy LBA to SI for later call to lba_to_chs
    
        mul word [BytesPerSector]
        mov bx,ax                  ; Load to after BOTH FATs in memory
    
        xor dx, dx                 ; blank dx for division
        mov ax, 32
        mul word [MaxDirEntries]
        div word [BytesPerSector]  ; number of sectors to read
    
        call lba_to_chs            ; Retrieve CHS values and load boot drive
        mov ah, 0x02
        int 13h
        cmp ah, 0
        je .load_OS
        mov si, error_text
        call print
        jmp $
    

答案 1 :(得分:3)

我在加载FAT后最终报废了加载根目录。最后,我修改了我的.load_fat例程,同时加载FATs 根目录(基本上在引导扇区后读取32个扇区,但仍然允许我轻松修改磁盘几何)。

以下代码如下:

.load_fat:
    mov ax, 0x07c0          ; address from start of programs
    mov es, ax
    mov al, [SectorsPerFAT] ; how many sectors to load
    mul byte [FATcount]     ; load both FATs
    mov dx, ax
    push dx
    xor dx, dx              ; blank dx for division
    mov ax, 32
    mul word [MaxDirEntries]
    div word [BytesPerSector] ; number of sectors for root directory
    pop dx
    add ax, dx              ; add root directory length and FATs length -- load all three at once
    xor dh,dh
    mov dl, [boot_device]

    xor ch, ch              ; cylinder 0
    mov cl, [ReservedSectors]  ; Load from after boot sector
    add cl, byte 1
    xor dh, dh              ; head 0
    mov bx, 0x0200          ; read data to 512B after start of code
    mov ah, 0x02            ; set to read
    int 13h
    cmp ah, 0
    je .load_root
    mov si, error_text
    call print
    hlt

虽然不是我打算解决问题的方式,但它可以完成这项工作,我可以从此继续开发。

修改

无论如何,我认为我找到了旧代码出错的地方。我在第18区之后增加了圆柱体,当时我应该增加头部。这是CHS,而不是HCS,原因是什么!