assembly x86 filesystems nasm bootloader

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:

  1. Calcule LBA DIV SPT. Esto produce:
    • (LBA ÷ SPT) en el cociente
    • (LBA mod SPT) en el resto
  2. Tome el resto del paso (1) y póngalo en un registro temporal
  3. Agregue 1 al temporal en el paso (2). Ese registro ahora contiene el sector calculado por S = (LBA mod SPT) + 1
  4. 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!