assembly - ¿Por qué no se carga mi directorio raíz?(FAT12)
x86 filesystems (2)
Análisis del problema
El problema con su código es que no está leyendo desde el punto en el disco que está esperando. Aunque la lectura del disco es exitosa, ha cargado los sectores incorrectos en la memoria.
Si miramos la lista de interrupciones de Ralph Brown para Int 13h / AH = 2 , veremos que las entradas se ven así:
DISCO - LEER SECTOR (S) EN LA MEMORIA
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
Si revisamos sus registros antes de hacer
int 13h
en
.load_root
, vemos estos registros con el siguiente contenido:
eax 0x20e ecx 0x101 edx 0x0 ebx 0x2600 es 0x7c0
Entonces ES: BX es 0x7c0: 0x2600, que es la dirección física 0xA200. Eso es correcto. AH (0x02) es lectura de disco y el número de sectores a leer en AL es 14 (0x0e). Esto parece razonable. El problema surge en ECX y EDX . Si revisamos su código, parece que está intentando encontrar el sector (Dirección de bloque lógico) en el disco donde comienza el directorio raíz:
mov al, [FATcount] ; calculate how many sectors into the disk must be loaded
mul word [SectorsPerFAT]
add al, [ReservedSectors]
En su bloque de parámetros de BIOS tiene
SectorsPerFat
= 9,
ReservedSectors
= 1 y
FATCount
= 2. Si revisamos un
documento de diseño FAT12
que muestra esta configuración, se vería así:
Tu cálculo es correcto. 2 * 9 + 1 = 19. Los primeros 19 bloques lógicos se ejecutan de LBA 0 a LBA 18. LBA 19 es donde comienza su directorio raíz. Necesitamos convertir esto a Cilindros / Cabezas / Sectores (CHS). Dirección del bloque lógico para el cálculo de CHS :
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
En su código SPT = 18, HPC = 2. Si usamos un LBA de 19 calculamos un CHS de C = 0, H = 1, S = 2.
Si observamos los valores que pasó a los registros (
CL
,
CH
,
DH
) arriba, descubrimos que utilizó un CHS de C = 1, H = 0, S = 1.
Esto pasa a ser LBA 36, no 19. El problema es que sus cálculos son incorrectos.
En particular
.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
Desafortunadamente, esta no es una forma correcta de calcular CHS a partir de un LBA.
Tiene un problema similar con
.load_fat
pero tiene la suerte de calcular el valor correcto.
Está leyendo los sectores incorrectos en el disco y eso está causando que los datos se carguen a 0xA200 que no esperaba.
Traducción de LBA a CHS
Lo que necesita es una rutina de conversión de LBA a CHS adecuada.
Como necesitará dicha función para diferentes aspectos de la navegación de las estructuras de archivos FAT12, es mejor crear una función.
Lo llamaremos
lba_to_chs
.
Antes de escribir dicho código, deberíamos revisar la ecuación anteriormente:
C = LBA ÷ (HPC × SPT) H = (LBA ÷ SPT) mod HPC S = (LBA mod SPT) + 1
Podríamos implementar esto tal como está, pero si reelaboramos la ecuación para cilindros podemos reducir la cantidad de trabajo que tenemos que hacer.
C = LBA ÷ (HPC × SPT)
se puede reescribir como:
C = LBA ÷ (HPC × SPT)
C = LBA ÷ (SPT × HPC)
C = (LBA ÷ SPT) × (1 ÷ HPC)
C = (LBA ÷ SPT) ÷ HPC
Si ahora miramos la fórmula revisada tenemos:
C = (LBA ÷ SPT) ÷ HPC
H = (LBA ÷ SPT) mod HPC
S = (LBA mod SPT) + 1
Ahora deberíamos notar que
(LBA ÷ SPT)
está duplicado en dos lugares.
Solo tenemos que hacer esa ecuación una vez.
Además, dado que la instrucción
DIV
x86 calcula el resto y el cociente al mismo tiempo, también terminamos calculando
LBA mod SPT
de forma gratuita cuando lo hacemos
(LBA ÷ SPT)
.
El código seguiría esta estructura:
-
Calcule LBA
DIV
SPT.
Esto produce:
-
(LBA ÷ SPT)
en el cociente -
(LBA mod SPT)
en el resto
-
- Tome el resto del paso (1) y póngalo en un registro temporal
-
Agregue 1 al temporal en el paso (2).
Ese registro ahora contiene el sector calculado por
S = (LBA mod SPT) + 1
-
Tome el cociente del paso (1) y divida por HPC.
- El número del cilindro será el cociente
- La cabeza será el resto.
Hemos reducido la ecuación a un par de instrucciones DIV y un incremento / suma. Podemos simplificar las cosas más. Si suponemos que estamos utilizando formatos de disco compatibles de IBM bien conocidos, también podemos decir que los sectores por pista (SPT), cabezas (HPC), cilindro, cabeza y sector siempre serán inferiores a 256. Cuando el LBA máximo en cualquier pozo el formato de disquete conocido se divide por SPT, el resultado siempre será inferior a 256. Saber esto nos permite evitar que los bits superiores tomen los dos bits superiores del cilindro y los coloquen en los dos bits superiores de CL . También podemos usar instrucciones DIV que hacen una división sin signo de 16 bits por 8 bits.
Código de traducción
Si tomamos el pseudocódigo anterior, podemos crear una función
lba_to_chs
bastante pequeña que toma un LBA y lo convierte a CHS y solo funciona para formatos de disquete compatibles con 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
Si necesita una versión que pueda manejar todas las geometrías de disco válidas admitidas por FAT12, entonces el código tendría que usar instrucciones DIV de 32 bits / 16 bits y uno debe tratar con cilindros de 10 bits en lugar de 8. El código de muestra podría verse me gusta:
; 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
Puede usar cualquiera de estas funciones
lba_to_chs
la misma manera e integrarlas en su código
.load_fat
y
.load_root
.
Su código podría verse así:
;;;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 $
Estoy escribiendo un gestor de arranque de la etapa 1 en ensamblado con el que intento cargar el sistema de archivos FAT12 en la memoria para poder cargar mi gestor de arranque de la etapa 2. He logrado cargar las FAT en la memoria, sin embargo, estoy luchando por cargar el directorio raíz en la memoria.
Actualmente estoy usando this como referencia y he producido lo siguiente:
.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 $
Sin embargo, si inspecciono la memoria en
0xA200
con gdb, solo veo 0s.
Mi directorio raíz contiene un archivo: he puesto un archivo llamado OS.BIN en el directorio raíz para probar.
El uso de
info registers
en gdb después de la operación de lectura da el siguiente resultado:
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
El estado de la operación es 0, el número de sectores leídos es 14 y
es:bx
apunta a 0xA200, pero
x/32b 0xa200
muestra 32 0s, cuando esperaba ver los datos de OS.BIN.
EDITAR
Hice
info registers
antes de la interrupción y la salida es la siguiente:
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
Que es lo mismo que después, excepto que el número de solicitud de función ha sido reemplazado por el código de estado.
¿A dónde voy mal? ¿Estoy leyendo desde la dirección incorrecta de CHS? ¿O algún otro simple error? ¿Y cómo puedo corregir esto?
Estoy usando
fat_imgen
para hacer mi imagen de disco.
El comando para crear la imagen de disco es
fat_imgen -c -f floppy.flp -F -s bootloader.bin
y el comando para agregar
OS.BIN
a la imagen es
fat_imgen -m -f floppy.flp -i OS.BIN
Tengo un bloque de parámetros de BIOS (BPB) que representa un disquete de 1,44 MB con FAT12:
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 "
Tengo otra función que parece funcionar que carga la tabla FAT12 en la dirección de memoria 0x7c0: 0x0200 (dirección física 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
Terminé descartando la carga del directorio raíz después de cargar las FAT. Al final, modifiqué mi rutina .load_fat para cargar las FAT y el directorio raíz al mismo tiempo (esencialmente leyendo 32 sectores después del sector de arranque, pero de una manera que todavía me permite modificar fácilmente la geometría del disco).
El código para esto está abajo:
.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
Aunque no es la forma en que tenía la intención de resolver el problema, hace el trabajo y puedo pasar de esto para continuar el desarrollo.
EDITAR
Creo que resolví dónde andaba mal el viejo código, de todos modos. Estaba incrementando el cilindro después del sector 18, cuando debería haber estado incrementando la cabeza. ¡Es CHS, no HCS, por una razón!