【问题标题】:Why isn't my root directory being loaded? (FAT12)为什么我的根目录没有被加载? (FAT12)
【发布时间】:2018-01-08 03:33:13
【问题描述】:

我正在编写一个第 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

【问题讨论】:

  • 请提供minimal reproducible example。此外,使用调试器验证磁盘读取的输入参数。
  • @Jester 我认为我不能进一步减少代码——这部分没有按预期工作,但我不知道为什么。如果您需要任何具体的附加信息,我可以提供,但目前我不知道要添加(或删除)什么。
  • 这不完整。我无法运行它。它缺少测试它所需的所有其他代码,缺少用于创建磁盘映像或映像本身的命令。喝完咖啡会看看你的注册更新:)
  • @Jester 啊,好的。我会在几个滴答声中将它的当前状态推送到一个新的 git 分支并给你链接。
  • @WORD_559 不要只给我们链接,在问题中发布任何需要的内容。

标签: assembly x86 nasm bootloader fat


【解决方案1】:

问题分析

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

如果我们查看 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

如果我们在您在.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。前 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。如果我们使用 19 的 LBA,我们计算的 CHS 为 C=0、H=1、S=2。如果我们查看您传递到上述寄存器(CLCHDH)的值,我们会发现您使用了 C 的 CHS =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 ÷ SPT) 时,我们也最终免费计算 LBA mod SPT。代码将遵循以下结构:

  1. 计算 LBA DIV SPT 。这产生:
    • (LBA ÷ SPT)在商中
    • 余下的(LBA mod SPT)
  2. 将步骤 (1) 中的剩余部分放入临时寄存器中
  3. 将 1 添加到步骤 (2) 中的临时值。该寄存器现在包含由S = (LBA mod SPT) + 1 计算的扇区
  4. 将步骤 (1) 中的商除以 HPC。
    • 气缸数将是商
    • 头部将是其余部分。

我们已将等式简化为一对 DIV 指令和一个增量/加法。我们可以进一步简化事情。如果我们假设我们使用的是众所周知的 IBM 兼容磁盘格式,那么我们也可以说 Sectors per Track (SPT)、Heads(HPC)、Cylinder、Head 和 Sector 将始终小于 256。当任何井上的最大 LBA已知的软盘格式除以 SPT 的结果总是小于 256。知道了这一点,我们就可以避免位旋转柱面的顶部两位并将它们放在 CL 的顶部两位中。我们还可以使用 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
;              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

    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 $

【讨论】:

    【解决方案2】:

    我最终在加载 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 扇区之后增加了 圆柱体,而我应该增加磁头。这是 CHS,而不是 HCS,这是有原因的!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-28
      • 2017-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-12
      • 2012-05-17
      相关资源
      最近更新 更多