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

Why isn't my root directory being loaded? (FAT12)

我正在用程序集编写第 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 table 加载到内存地址 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

加载 FAT 后,我最终放弃了加载根目录。最后,我修改了 .load_fat 例程以同时加载 FAT 根目录(本质上是在引导扇区之后读取 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 之后递增 cylinder,而我本应递增磁头。这是 CHS,而不是 HCS,这是有原因的!

问题分析

您的代码存在的问题是您没有按照预期从磁盘上的位置读取数据。尽管您的磁盘读取成功,但它已将错误的扇区加载到内存中。

如果我们查看 Int 13h/AH=2 的 Ralph Brown 中断列表,我们会看到输入如下所示:

DISK - READ SECTOR(S) INTO MEMORY

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

如果我们在 .load_root 中执行 int 13h 之前检查您的寄存器,我们会看到这些寄存器包含以下内容:

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

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

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它看起来像:

你的计算是正确的。 2*9+1 = 19。从 LBA 0 到 LBA 18 的前 19 个逻辑块 运行。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。如果我们使用 19 的 LBA,我们计算的 CHS 为 C=0,H=1,S=2。如果我们查看您传递到寄存器 (CLCHDH) 的值,我们会发现您使用了 C=1、H=0、S=1 的 CHS。这恰好是 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 ÷ SPT) 时也最终免费计算 LBA mod SPT。代码将遵循以下结构:

  1. 计算 LBA DIV SPT。这产生:
    • (LBA ÷ SPT)中商
    • (LBA mod SPT) 余数
  2. 取步骤(1)的余数存入暂存器
  3. 将步骤(2)中的临时值加1。该寄存器现在包含由 S = (LBA mod SPT) + 1
  4. 计算的扇区
  5. 从步骤 (1) 中取商并除以 HPC。
    • 柱数取商
    • 头将是余数。

我们已将等式简化为一对 DIV 指令和 increment/add。我们可以进一步简化事情。如果我们假设我们使用的是众所周知的 IBM 兼容磁盘格式,那么我们也可以说每个磁道的扇区数 (SPT)、磁头数 (HPC)、柱面数、磁头数和扇区数将始终小于 256。当任何孔上的最大 LBA已知的软盘格式除以 SPT 结果总是小于 256。知道这一点可以让我们避免位旋转柱面的前两位并将它们放在 CL[=113 的前两位=].我们还可以使用 DIV 指令执行 16 位乘 8 位无符号除法。


翻译代码

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

;    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
;              
;              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

    mov ax, 32
    cwd                        ; Zero dx for division
                               ;     (works since AX(32) < 0x8000)
    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 $