======================================================= Introducción a la programación en lenguaje ensamblador para procesadores Intel serie x86 y compatibles (V) ======================================================= Por nmt numit_or@cantv.net =========================================================== PRIMEROS CONTACTOS CON EL SISTEMA: OPERACIONES CON EL DISCO =========================================================== -------------------------------------------------------- CONTENIDO - Primera utilidad - El Disco Duro · LBA - Logical Block Address: Dirección Lógica del Bloques · Áreas del disco - El sector de arranque · Análisis del código · NOTA: MBR para NASM - Programación de E/S de disco - Ejemplo de código INT13: MBRREST · Análisis de MBRREST · Operaciones con archivos en DOS · NOTA: la instrucción DIV - Código del MBR (Master Boot Record: Sector Maestro de Arranque) - Unas palabras sobre las particiones - Escribir un sector de arranque - APÉNDICES · Apéndice 1: Desensamblaje de un MBR · Apéndice 2: Anidamiento de la partición · Apéndice 3: Enlace de la tabla de partición · Apéndice 4: Indicadores de tipo de partición ================= Primera utilidad ================= Hay muchas rutinas y programas que resulta mejor escribirlas en ensamblador. Es en este tipo de rutinas en las que vamos a concentrarnos. Entre estas rutinas, están las que requieren una interacción directa con dispositivos de hardware, como el controlador de vídeo o el disco duro, ahí donde se requiere un control estricto. Como en el futuro mediato vamos a revisar el uso de los registros de sistema del procesador, y estos encuentran su campo más propicio en el diseño de sistemas operativos, nos concentraremos ahora en una rutina que nos abrirá el camino hacia ello. Vamos a escribir ahora un programa sencillo pero muy útil, que salva el sector de arranque, el primero de un disquete en un archivo de respaldo, y luego, si se le pide, escribe un archivo en los primeros sectores del disquete. Esto nos permite obtener el sector de arranque de los sistemas puestos en disquetes para su estudio o necesaria restauración en momentos "amargos", y también copiar en el disquete bootstraps, rutinas de arranque que vayamos escribiendo. Estudiaremos la manera cómo arranca el sistema y diseñar nuestra propia rutina de arranque. Es que en este estudio es donde cabe investigar los modos de operación del procesador, ahí donde el sistema operativo no representa una traba. Escribir esta utilidad no requieren mucho conocimiento: necesitamos saber qué es el sector de arranque de un disco, cómo lo obtenemos y cómo lo guardamos en un archivo, como leemos un archivo y ponemos su contenido en un sector del disco. Para hacer el trabajo completo, escribiremos nuestro propio sector de arranque y lo probaremos. NOTA: En esta lección vamos a tratar con rutinas que escriben sobre disquetes en unidades de floppy. Hay que seguirla con detenimiento, pues no me puedo responsabilizar con el mal uso que se haga con las rutinas aquí expuestas. ------------- El Disco Duro ------------- La sigla CHS significa Cilinder-Head-Sector --Cilindro-Cabezal-Sector--, que son los elementos que constituyen el formato de un disco duro. Un disco duro consiste en uno o más platos rotativos. Cada plato tiene varias pistas concéntricas numeradas empezando por cero desde la pista más externa. Si se trata de un disco formateado siguiendo la especificación ATA, cada pista estará formateada en sectores (S) de 512 bytes donde se almacena la información. Los datos son escritos o leídos a través de un cabezal, cuya posición determina la pista leída. El cilindro (C) es el conjunto vertical de todas las pistas con el mismo número en cada superficie de un disco duro. Así como se identifica el número de pista con el de cilindro, a la superficie o lado del disco se le identifica con el cabezal (H). Para el manejo de discos, el BIOS ofrece varios servicios que pueden solicitarse a través de la interrrupción 13h. LBA - Logical Block Address: Dirección Lógica del Bloques --------------------------------------------------------- El formato CHS no es la manera lógica de direccionar sectores del disco. Los sistemas operativos, que trabajan en alto nivel, tratan el disco como un stream (un continuum) de sectores, usando un esquema de numeración simple comenzando con cero, que sería la dirección del primer sector del dispositivo, y a partir de este sector se direccionan el resto. A este esquema de direcionamiento se le llama LBA (Logical Block Addressing: Direccionamiento Lógico por Bloques). El estándar ATA exige que la dirección cilíndro 0, cabeza 0, sea el mismo sector que el direccionado por LBA 0. LBA es un esquema más atractivo que CHS porque presenta las direccciones como deberían ser vistas en un sistema de archivos. Pero CHS ya existía mucho antes que el LBA, así que hubo que diseñar un método de traducción. Luego veremos los problemas y algoritmos concernientes a los métodos de traducción. Áreas del disco --------------- El disco duro se divide en dos áreas pincipales: un área de sistema y una de arranque. · Área de sistema La primera área del disco, donde se mantiene información que usa el sistema operativo para el arranque y el manejo de archivos. Se subdivide en: · Registro de arranque: sector de 512 bytes con código y datos necesarios para cargar los archivos de sistema desde el disco hasta la memoria. Para DOS, también contiene una tabla conocida como bloque de parámetros del BIOS (BIOS Parameter Block: BPB) que contiene información sobre la geometría y el formato del disco, etc. Incluye además otra tabla con datos sobre las particiones del disco. Esta última tabla no está presente en los disquetes de unidad de floppys. · Tabla de asignación e archivos (FAT: File Allocation Table): Tabla con información para asignar espacio en disco duro a los archivos. Contiene una entrada para cada grupo en disco. Un grupo es un conjunto de sectores que el sistema operativo trata como una unidad de almacenamiento. Su tamaño es una potencia de 2. Un grupo es la unidad más pequeña para localización de espacio que maneja DOS: el espacio usado en un disco por cada archivo es medido en cantidad de grupos. Así que si un arhivo tiene una cantidad de datos menor a un grupo, DOS reservará todo el espacio equivalente a un grupo para el archivo. El espacio restante quedará sin uso. La FAT comienza en el sector 2, inmediatamente después del sector de arranque. La cantidad de sectores que se le asigna está en el BPB, en el desplazamiento 16h del sector de arranque. Generalmente existe una copia adicional de ella por razones de seguridad y que se encuentra en los sectores después de la primera FAT. El desplazamiento 10h del sector de arranque, dentro del BPB, indica el número de copias. La FAT es una base de datos sobre archivos que asocia algún número de grupo con un archivo de datos. El tamaño de cada entrada de la FAT puede variar entre 12 y 16 bits para unidades de floppy y 32 para unidades de disco duro. El tamaño de las entradas de la FAT influyen en la posibilidad de direccionar en discos de tamaño considerable. Los directorios de las FAT12 y FAT16 están pensados para sistemas de a rchivos que soportan hasta 65,525 grupos y limitadas a 2 GB. Trabajan mejor sobre pequeñas unidades de hasta 500 MB debido al tamaño de los grupos. Para unidades mayores se usa FAT32. FAT32 no reconocerá volúmenes FAT ni NTFS de otros sistemas operativos, pero soporta unidades de hasta 2 terabytes. Usa grupos más pequeños (por ejemplo, 4k grupos hasta 8 gigs). FAT32X es una formna de FAT32 creada por FDISK de Windows para particiones que superan los 8 GB y los 1024 cilindros del disco son sobrepasados. La tabla de localización de archivos es movida la final del disco. VFAT (Virtual File Allocation Table) es una versión de modo protegido del sistema de archivos de la FAT, usada por W95. Es compatible con el sistema FAT, la principal diferencia es el soporte de nombres largos. Bajo FAT32, el PBP aumenta su número de entradas y algunos de sus campos originales quedan inutilizados. · Directorio: Para cada archivo, el sistema operativo crea una entrada de directorio de 32 bytes con información sobre los archivos en el área de datos. Esta información incluye el nombre del archivo, extensión, tamaño, número relativo del primer grupo, etc. Cada entrada incluye una indicación del grupo donde comienza el archivo; este mismo número indica una entrada de la FAT donde se ha de encontrar el número del siguiente grupo de datos del archivo. Los grupos se cuentan a partir del final del directorio; así que el grupo 1 será el primero después del directorio. Bajo FAT32, el sistema puede usar cualquiera de las copias de la FAT, no sólo una. El directorio ahora es localizado libremente, ya no hay límite en el número de entradas de directorios en el directorio raiz, condición necesaria para permitir entradas de directorio para nombres de archivo --la FAT12 y FAT16 sólo permiten nombres de hasta 8 caracteres. El directorio raíz ya no está almacenado en un lugar fijo: hay una indicación del número del grupo dónde comienza en el BPB extendido de la FAT32. Las entradas del directorio del disco no cambian excepto por los dos bytes previamente reservados para atributos extendidos, que ahora contienen la palabra de orden más alto del número del cluster inicial. Para ubicar el directorio en FAT32, hay que revisar el campo 2Ch del PBP extendido, que indica el número del grupo donde comienza. *NOTA 1* El sistema de manejo de archivos aquí presentado corresponde a los sistemas Microsoft. Otros sistemas han sido propuestos para superar algunas ineficiencias del sistema FAT, que pone todos los punteros a los archivos en disco en una misma tabla de forma aleatoria. Por esto, para encontrar cualquier archivo siempre se necesita toda la FAT, incluso si se trabaja con sólo uno. El método alternativo propuesto por UNIX fue asociar una tabla a cada archivo, llamada nodo-i. Dicha tabla se halla en el mismo disco y contiene información sobre atributos de protección y de conteo, como 10 números de bloques de disco y 3 de bloque indirectos. Si un archivo crece por encima de los 10 bloques, se adquiere uno nuevo desocupado y se coloca un puntero indirecto a él. En este nuevo bloque se colocan punteros para los nuevos bloques de disco. Si hay un archivo que requiere más bloques de los que puede direccionar el nuevo bloque de punteros, entonces este bloque se usa para apuntar a un nuevo bloque, también con punteros a bloques del archivo; ya el segundo bloque no apunta a bloques de datos sino a bloques de punteros. Se tiene así un sistema de punteros a bloques y de punteros a bloques con punteros. *NOTA 2* El boot record en unidades FAT32 requiere 2 sectores (debido a la expansión de los campos en el BPB). Así que el número de sectores reservados en unidades FAT32 es mayor que en FAT16, generalmente 32. Esta area expandida permite almacenar dos copias completas del sector de arranque y un sector donde es puede almacenar la cantidad de espacio libre, y algún otro tipo de información sobre el sistema de archivos. · Área de datos Espacio del disco donde se almacenan los archivos de datos de sistema y de usuario. Al inicio se encuentran los archivos del sistema, como IOSYS.SYS de DOS. A continuación siguen los archivos de usuario, a no ser que no hayan archivos de sistema y entonces los archivos de dato s del usuario iniciarán el área de datos el disco. El sector de arranque --------------------- El sector de arranque en un disco siempre es el primer sector de la primera pista sobre el primer cabezal. Cuando la computadora es escendida (o reiniciada), el BIOS ejecuta el POST (Power On Self Test: Auto-Prueba de Energía); inicializa todos los datos, luego busca un sector de arranque válido. Primero revisa el floppy A, si así está especificado, luego revisa C. Si no encuentra un sector de arranque, se ejecuta la int18h, que inicia el ROM BASIC. Para identificar un sector de arranque válido, el BIOS revisa en el deplazamiento 510 del sector de arranque y comprueba si aparece el valor 0AA55h. Si el BIOS encuentra el sector de arranque, lo lee (512 bytes) y lo guarda en la RAM en 0:7C00h o 07C0h:0. Luego pasa el control a esa dirección y se ejecuta el código en el sector de arranque. En este punto, todo lo que ha sido inicializado es el área de datos del BIOS (en 0:400h o 40h:0) y las interrupciones del BIOS (10h-1Ah). La memoria está casi totalmente sin uso, pero no necesariamente limpia. Generalmente, en la dirección 0 del sector de arranque, hay un salto (jmp) al código de arranque, que en el sistema operativo DOS está en el desplazamiento 1Eh o 3Eh, dentro del mismo sector de arranque, inmediatamente después del BPB, con información sobre el formato del disco. Abajo un ejemplo que se puede usar como plantilla para escribir el código de un sector de arranque: ; --------------------------------------------------------------------------------- TITLE MBR.ASM: Plantilla para la escritura de MBRs ; --------------------------------------------------------------------------------- ; ¡Code+Data debe tener un tamaño menor que 510 bytes! ; --------------------------------------------------------------------------------- .model tiny .code org 0 EntryPoint: db 0EAh ;jmp far SEG:OFS ; En este momento estamos en 0:7C00 dw OFFSET AfterData, 7C0h ; Esto hace que estemos en 7C0:0 ; Poner cualquier dato aquí AfterData: push CS pop DS ; actualiza DS para que sea 7C0h en vez de 0 ; Poner aquí el código! jmp $ ; Terminar aquí org 510 ; Hacer el fichero de 512 bytes de tamaño dw 0AA55h ; Agregar la signatura de arranque END EntryPoint ; --------------------------------------------------------------------------------- ; Para ensamblar con TASM: ; tasm mbr ; tlink mbr ; Esto producirá un archivo mbr.com ; --------------------------------------------------------------------------------- - Análisis del código: directiva ORG - Aquí hay varios detalles que son dignos de analizar. Primero una directiva nueva: ORG. Para llevar una cuenta de las posiciones relativas a un segmento de datos o de código, el ensamblador utiliza un contador de localidades. Al inicio, el contador es puesto en 0. Con cada instrucción, el contador se incrementa en la cantidad correspondiente al tamaño de la instrucción. Si se desea iniciar o cambiar el contenido del contador de localidades, se puede usar la directiva ORG, que de esta manera cambiará la localidad de los elementos siguientes. El formato es: ORG expresión Esta directiva es muy útil especialmente cuando deseamos que inicien, por ejemplo, en la localidad 0, por ejemplo: org 0 Luego unas instrucciones curiosas: EntryPoint: db 0EAh ;jmp far SEG:OFS ; En este momento estamos en ; 0:7C00 dw OFFSET AfterData, 7C0h ; Esto hace que estemos en 7C0:0 la primera define un byte inicializado con 0EAh, la segunda define dos datos con tamaño word, el primero es la dirección de "AfterData", y el segundo inicializado con 07C0h. El valor 0EAh es el código de operación de "jmp far": cuando escribimos en lenguaje ensamblador, el código escrito debe ser ensamblado: debe traducirse su contenido a código binario y puesto en otro archivo, generalmente con extensión .OBJ. El ensamblador se encarga de este trabajo. Cada instrucción en ensamblador tiene su correspondiente código binario de operación con el que realmente trabaja la máquina. El valor 0EAh será interpretado por el procesador como un salto lejano. Un salto lejano, como ya vimos tiene el siguiente formato: SEGMENTO:DESPLAZAMIENTO se requieren dos valores tamaño word, uno para indicar el segmento y otro para un desplazamiento dentro del segmento. La instrucción: dw OFFSET AfterData, 7C0h indica dónde debe pasar el control la instrucción "jmp far": jmp far 07C0h:offset AfterData Quiere decir que la primera instrucción después de este salto debe estar en 07C0:0 Las instrucciones: push CS pop DS ; actualiza DS para que sea 7C0h en vez de 0 pasan el contenido de CS, con la base del archivo en memoria (07C0h) a DS, usando la pila. Recuérdese que no se pueden mover datos inmediatos o desde la memoria directamente a los registros de segmento. En la instrucción: jmp $ ; Terminado el símbolo "$" se puede traducir como "aquí"; la instrucción lo que hace es detener la ejecución del programa, ya que la instrucción es un salto incondicional a su propia dirección. El símbolo "$", como puede verse, es un marcador genérico que es interpretado como la dirección del inicio de la línea donde se encuentra. Las instrucciones: org 510 ; Hacer el fichero de 512 bytes de tamaño dw 0AA55h ; Agregar la signatura de arranque escriben en el desplazamiento 5 10 del sector la signatura de sector de arranque válido 0AA55h. Otro hecho notable es que este programa no declara una pila (stack) ni un segmento de datos: sólo se declara un segmento de código para todo. Se usa el modelo de memoria TINY. Esta configiguración corresponde al formato original de los ficheros ejecutables de Microsoft DOS: COM. Pero nuestro archivo no tiene ese formato, ya que los .COM inician en la dirección 100h (o 256), ya que cuando se monta un programa en memoria para correrlo, DOS agrega al programa un area de datos de 256 bytes conocida como Prefijo de Segmento del Programa (PSP); por eso los programas .COM ponen al comienzo la instrucción "org 100h", para que el conteo de instrucciones inicie en 100h. Pero el nuestro inicia en 0, por lo que no podrá compilarse como archivo .COM sino como .EXE, como lo hemos venido haciendo. Luego debemos usar un editor hexadecimal para extraer del .EXE generado los últimos 512 (200h) bytes y guardarlo como extensión .BIN o .IMG. Luego escribimos este fichero en el sector 1 de un disquete usando alguna herramienta como rawrite, para DOS o Windows, o dd de Linux. Escribiremos aquí nosotros mismos una utilidad para hacer esto, que no es difícil. Luego hay que colocar el disquete en la unidad de floppy y reiniciar el sistema. *NOTA: MBR para NASM* Para la escritura de sectores de arranque lo más conveniente es usar NASM, que además ofrece una versión para Linux: http://sourceforge.net/projects/nasm/ A continuación una plantilla para escribir MBRs con NASM: ; ---------------------------------------------------------------- ; MBR.ASM ; Salta y luego cuelga el sistema ; ---------------------------------------------------------------- ; Hay que decirle al ensamblador que este es el desplazamiento 0. ; Si no es el desplazamiento 0, lo será después del salto. ORG 0 jmp 07C0h:start ; Ir al segmento 07C0 start: ; Actualizar los registros del segmento mov ax, cs mov ds, ax mov es, ax hang: ; Colgarse jmp hang times 510-($-$$) db 0 dw 0AA55h ; para ensamblar con NASM: ; nasm mbr.asm -o mbr.bin ; autor: Daniel Marjamäki (daniel.marjamaki@home.se) ; ---------------------------------------------------------------- Son pocas las diferencias respecto a la plantilla anterior: Se escribe [ORG 0] en vez de ORG 100 Se escribe jmp 07C0h:start en vez de: db 0EAh dw OFFSET AfterData, 7C0h ; Esto hace que estemos en 7C0:0 Se escribe times 510-($-$$) db 0 dw 0AA55h en vez de: org 510 dw 0AA55h La instrucción "times 510-($-$$) db 0" equivale a: db 510-($-$$) dup (0) pone ceros desde su ubicación hasta el desplazamiento 510. Luego se coloca en ese desplazamiento la signatura AA55. La instrucción "times" de NASM equivale a "DUP" de TASM: dice al ensamblador que ensamble una instrucción tantas veces como lo indique; pero se puede aplicar a intrucciones de código: DUP sólo es aplicable a intrucciones de declaración de datos. El argumento para TIMES no es una constante numérica sino una "expresión" numérica, así que pueden emplearse instrucciones como: buffer: db 'hello, world' times 64-$+buffer db ' ' El símbolo "$" designa "aquí": indica el comienzo de la línea que contiene la expresión y permite el cálculo de instrucciones que involucran la dirección actual de una instrucción. "$$" indica el comienzo de la sección donde se encuentra la instrucción actual: se emplea ($-$$) para indicar la distancia entre el comienzo de la instrucción actual y el comienzo de la sección. Otro hecho notable es que NASM no necesita el uso de un enlazador para generar ficheros binarios o ficheros con formato .COM. Antes de probar el código de arriba, escribamos un programa para leer y escribir archivos al comienzo de un disquete. Esto nos permitirá revisar los servicios de la interface int13 del BIOS. Programación de E/S de disco ---------------------------- IBM ha programó una interface para el manejo de discos que respetan las especificaciones ATA: la interrupción INT 13h. Para especificar las direcciones se emplean los siguientes registros: CH: 8 bits bajos de los 10 bits empleados para especificar el número de cilindro o pista. CL: 2 bits altos de los 10 bits del número de cilindro o pìsta: se almacenan en los bits 6 y 7. Los bits 0-5 son los 6 bits del número de sector. DH: Número de cabezal o lado. Sólo se usan los bits 0-3. Para indicar error, la int13 pone en cero la bandera CF y regresa el código del error en el registro AH. A continuación algunos servicios de la INT 13h: · RESTABLECER EL SISTEMA DE DISCO AH = 00h (servicio o número de función). DL = No. de unidad (80h-FFh para discos duros, 128-255 decimal; 0-7Fh para u. de floppy). Este servicio reinicializa el controlador del disco: cuando se acceda de nuevo a la unidad, se coloca el cilindro en cero. Se emplea después de un error grave. Una operación válida pone en cero la bandera CF; un error activa CF y regresa el código de error en AH. · LEER ESTADO DEL DISCO AH = 01h (servicio o número de función). DL = No. de unidad (80h-FFh para discos duros, 128-255 decimal; 0-7Fh para u. de floppy). La operación se usa para examinar el estado de las operaciones de disco más recientes. Regresa en AL el código de estado, que la última operación puso seguramente en AH. · LEER SECTOR(ES) AH = 02h (servicio o número de función). AL = número de sectores a leer (Base 1: 1..255). CH = bits 0-7, No. de cilindro o pista (LSB del No. de cilindro, 10-bits, 0-1023). CL = bits 6-7, No. de cilindro o pista. CL = bits 0-5, No. de sector inicial (Base 1: 1-63) DH = No. de cabezal o lado (Base 0: 0-15, con traducción puede ser 0-255). DL = No. de unidad (80h-FFh para discos duros, 128-255 decimal; 0-7Fh para u. de floppy). ES:BX = dirección del buffer E/S. Desde una unidad de disquete hay que asegurarse de que un pedido de lectura no cruce el límite de una página DMA (dirección alineada 64K). Es altamente indeseable que el BIOS se encargue del problema por tí. La manera más fácil de cuidarse de esto es simplemente asegurarse de que la dirección del buffer que usas esté siempre alineada sobre un límite igual al tamaño pedido. Si lees cuatro sectores de 512 bytes, hay que alinear el buffer sobre el límite de 2KB. Ejemplo: _sector db 512 dup (?) ; buffer ... mov ah, 2 ; petición de lectura mov al, 1 ; un sector lea bx, 5 ; puntero al buffer mov ch, 4 ; pista 4 mov cl, 3 ; sector 3 mov dh, 0 ; cabeza o lado 0 mov dl, 0 ; unidad 0 (C) int 13h ; llamada al BIOS · ESCRIBIR SECTOR(ES) AH = 03h (número de la función de escritura). El resto de los parámetros son exactamente los mismos que los usados para leer sectores. Recuerda que se debe llenar el buffer apuntado por ES:BX con los datos que se quieren escribir. · OBTENER LOS PARÁMETROS DEL DISCO AH = 08h (número de la función para obener los parámetros de la unidad). DL = número de unidad (igual que para leer/esribir sectores). Esta función regresará con CF=0 si la unidad es válida. Al parecer, no todos los BIOS activan o desactivan correctamente el indicador CF. Cuando se intenta detectar el disco instalado, se debe chequear el número de unidades regresadas en DL, cuando se ha chequeado el primer disco con DL=80h. Esta función regresa el máximo de parámetros CHS en los mismos registros en que ellos se pasaron a INT13, con las funciones o servicios 02h y 03h: BL regresa el tipo de disco CH[0-7] y CL[6-7] regresa el valor máximo del cilindro (menor o igual a 1023). CL[0-5] el máximo número de sectores, ya que los números de sectores son con base uno, este valor es también una cuenta de los sectores. DH regresa el máximo número de cabezales (0-255, una vez más, el número de cabezales-1). DL regresa el No. de unidades conectadas al controlador del disco. ES:DI regresa, para disquetes, la dirección de una tabla de 11 bytes con parámetros de la unidad de floppy: dbp struct dpbCONTROL_TIMERS DW ? dpbMOTOR_OFF_DELAY DB ? dpbBYTES_PER_SECTOR DB ? dpbSECTORS_PER_TRACK DB ? dpbGAP_LENGTH DB ? dpbDATA_LENGTH DB ? dpbFORMAT_GAP_LENGTH DB ? dpbFORMAT_FILLER_BYTE DB ? dpbHEAD_SETTLE_TIME DB ? dpbMOTOR_START_TIME DB ? dbb ends (dbp: disk parameter block - bloque de parámetros del disco) Notas: Es común excluir el último cilindro de la información regresada por estas funciones. En los compatibles IBM PCs, este cilindro se reserva para diagnósticos del fabricante. Si se escribe un sistema operativo o una utilidad de partición, se podría dejar como una opción al usuario incluir o no este cilindro en una partición. Recordar siempre que la utilidad debería hacer una lectura/escritura/verificación del cilindro antes de intentar usarlo. · CHEQUEO DE INSTALACIÓN DE EXTENSIONES INT13 AH = 41h DL = número de unidad (igual que para leer/esribir sectores). BX = 055AAh Regresa: BX = 0AA55h (en orden invertido) y CF=0 si las extensones son soportadas por la unidad. Esta llamada también regresa en CX información sobre las subfunciones soportadas. · LECTURA EXTENDIDA DE SECTOR(ES) AH = 42h DL = número de unidad (igual que para leer/esribir sectores). DS:SI = puntero a la estructura del paquete pedido al disco (disk request packet structure.) Formato del paquete requerido: BYTE Tamaño del paquete (10h) BYTE Reservado (00h) WORD Conteo de sectores WORD Desplazamiento (offset) del buffer WORD Segmento del buffer QWORD 64-bits con la dirección del bloque lógico · ESCRITURA EXTENDIDA DE SECTOR(ES) AH = 43h DL = número de unidad (igual que para leer/esribir sectores). DS:SI = puntero a la estructura del paquete pedido al disco (disk request packet structure.) Ee lo mismo que el servicio anterior. · OBTENCIÓN DE PARÁMETROS EXTENDIDOS DE LA UNIDAD (EXTENDED GET DRIVE PARAMETERS) AH = 48h DL = número de unidad (igual para lectura/escritura de sectores). DS:SI = buffer para la estructura de parámetros de la unidad. (Estructura): WORD Tamaño del buffer, 1Ah, 1Eh or 42h, depende de la versión de las extensiones. WORD Indicadores (flags) de información DWORD # de cilindros físicos DWORD # de cabezales físicos DWORD # de sectores físicos QWORD 64-bits con el número total de sectores de la unidad. Notas: La tabla de arriba describe los valores regresados por una llamada en la versión 1.0. Mayor información sobre esta materia, puede encontrarase en la lista de interrupciones de Ralf Browns: http://www-2.cs.cmu.edu/afs/cs/user/ralf/pub/WWW/files.html ------------------------------------ · Ejemplo de código INT13: MBRREST ------------------------------------ Con esta información ya podemos escribir nuestra rutina para salvar el sector de arranque de un disco. El algoritmo es simple: · Creamos un archivo · Leemos el sector de arranque del disco en un buffer de 512 bytes (512 bytes es el tamaño de un sector) · Escribimos el contenido del buffer en el archivo · Cerramos el archivo Para crear, leer y escribir un archivo en disco, emplearemos la interface suministradas por los servicios 3Ch-40h de la interrupción 21h de DOS. Quiere decir que este programa supone DOS. Luego de la rutina para hacer un respaldo del sector de arranque del disco seguiremos con otra que escriba en el inicio del disco un archivo que especifiquemos: · Abrimos un archivo para su lectura · Leemos de él un bloque de 512 bytes y lo ponemos en un buffer. · Copiamos el contenido del buffer en el primer sector del disco. · Revisamos si ya hemos copiado todo el contenido del archivo. Si ya lo hicimos, cerramos el archiv o y salimos; sino copiamos el siguiente bloque del archivo hasta que hallamos copiado todo el contenido del archivo e el disco Agregaremos también el código para obtener argumentos desde la línea de órdenes y crear un respaldo del sector de arranque original. El programa sólo hará el respaldo si no encuentra el fichero binario que se escribirá en el primer sector del disquete. ; ----------------------------------------------------------------------- TITLE MBRREST.ASM: Utilidad para escribir el MBR en una unidad de floppy ; --------------------------------------------------------------------------------- ; Para ensamblar con TASM: ; tasm mbrrest ; tlink /t mbrrest ; Esto producirá un archivo mbrrest.com ; --------------------------------------------------------------------------------- .model tiny sectors_per_track equ 18 tracks_per_side equ 80 .code org 100h entry: lea si, msg1 ; desplegar el texto de copyright :-) call _szDisplay ; ------------------------------------------------------------ ; Obtener el nombre del archivo pasado en la línea de órdenes ; ------------------------------------------------------------ mov si, 80h lodsb ; obtener número de caracteres test al, al ; ¿el número es cero? je no_file ; si es cero saltar ; saltarse los espacios is_space: lodsb ; obtener el caracter cmp al, 20h ; ¿es un espacio? je is_space ; saltar si es un espacio cmp al, 13h ; se alcanzó el final de la línea je no_file ; saltar si se llegó al final dec si ; ajustar el puntero push si ; salvar la dirección de la cadena ; buscar el final de la cadena next: lodsb cmp al, 13 ; ¿se alcanzó el final de línea? jne next ; revisar siguiente si no se alcanzó? ; poner cero al final de la cadena dec si ; apuntar al final mov byte ptr [si], 0 ; poner cero al final pop si ; restablecer la direción de la cadena jmp _with_file ; proceder a hacer el respaldo no_file: dec si mov byte ptr [si], 0 ; SI apunta a cero _with_file: ; --------------------------------- ; Hacer un respaldo del MBR actual ; --------------------------------- mov ax, 0 ; reestablecer sistema de disco mov dl, al ; 0 para floppy, cambiar a 80h para int 13h ; discos duros ; Verificar si se trabaja sobre una unidad de floppy: se lee en ; el sector 18 de la primera pista; si da error, no es una unidad ; de floppy de 1.44MB mov ah, 2 ; leer mov al, 1 ; un sector mov dh, 0 ; cabezal o lado 0 mov dl, 0 ; disquete: unidad A mov ch, 0 ; pista 0 mov cl, 18 ; sector 18 lea bx, buffer int 13h or ah, ah ; Si ah=0, se pudo leer el sector 18 je is_1_44 ; y el disquete es de 1.44MB lea si, msg3 ; Si no es un disquete de 1.44MB call _szDisplay ; desplegar el mensaje 3 jmp close_file ; cerrar el archivo y salir is_1_44: ; Lectura CHS del MBR mov bp, 4 ; leer el sector de arranque 4 veces ... read: lea di, buffer ; buffer para la transferencia de datos mov cx, 512 ; tamaño del buffer = bytes por sector call _ZeroMemory ; Limpiar el buffer mov ah, 2 ; leer mov al, 1 ; un sector mov dh, 0 ; cabezal o lado 0 mov dl, 0 ; disquete: unidad A (poner 80h para disco duro) mov ch, 0 ; pista 0 mov cl, 1 ; sector 1 lea bx, buffer int 13h dec bp jnz read ; Creación del fichero de respaldo mov ax, 3C00h ; crear el fichero de respaldo mov cx, 0 ; atributo normal lea dx, bu_file int 21h mov bx, ax ; mover el handle del fichero a BX ; Escritura del buffer en el fichero de respaldo mov ah, 40h ; escribir el mbr en boot.bak mov cx, 512 ; un sector = 512 bytes lea dx, buffer int 21h jc error_writing ; Cierre del fichero de respaldo mov ah, 3Eh ; cerrar el archivo creado para respaldo int 21h jmp restore error_writing: lea si, error_w no_name: call _szDisplay int 20h ; ------------------------------------------------------------------------------- ; ---------------------------- ; Escribir el archivo en disco ; ---------------------------- restore: ; ¿Se pasó nombre de archivo como parámetro? lodsb test al, al jne @00 ; Si no se pasó nombre, avisar y salir lea si, warning jmp no_name ; Abrir fichero para lectura @00: push si lea si, msg2 ; desplegar el mensaje de aviso call _szDisplay pop si dec si mov ax, 3D00h ; abrir fichero indicado para su lectura mov dx, si int 21h jc error ; error abriendo el fichero? mov bx, ax ; mover el handle del fichero a BX ; Obtener el tamaño del archivo mov ax, 4202h mov cx, 0 mov dx, cx int 21h jc error ; error abriendo el fichero? ; Verificar si cabe en una unidad de floppy cmp dx, 016h jl good_for_floppy1 bad_for_floppy: lea si, msg3 call _szDisplay jmp close_file ; cerrar el fichero error: lea si, error_r ; desplegar mensaje de error call _szDisplay int 20h ; regresar a DOS ... good_for_floppy1: cmp dx, 015h jl good_for_floppy2 cmp ax, 0F900h jg bad_for_floppy ; Obtener el No. de sectores del archivo good_for_floppy2: mov cx, 200h div cx mov uSector, ax or dx, dx je rounded inc word ptr [uSector] rounded: mov ax, 4200h mov cx, 0 mov dx, cx int 21h jc error ; error abriendo el fichero? ; Leer el fichero mov track, al mov head, al inc ax mov sector, al get_next_sector: lea di, buffer mov cx, 512 call _ZeroMemory mov ah, 3Fh ; leer el fichero en el buffer mov cx, 512 lea dx, buffer int 21h pushf or ax, ax je write_sector popf jnc write_sector ; ¿hubo error? lea si, error_r ; si hubo error, call _szDisplay ; avisar y jmp close_file ; cerrar el fichero write_sector: popf push ax ; Escritura del fichero en el disco mov ax, 0 ; restablecer el sistema de disco mov dl, 0 ; para disquete: mov dl, 0 int 13h push bx mov bp, 4 ; escribir el MBR cuatro veces ... write: mov ax, 0301h ; AH: 3, escribir, AL: 1 sector mov dh, head mov dl, 0 mov ch, track mov cl, sector lea bx, buffer ; dirección del buffer a escribir int 13h dec bp jnz write pop bx ; Si se llega al sector 18 (sectors_per_track), se escribe ; en el siguiente track y se comienzade nuevo el conteo de ; sectores inc sector cmp sector, sectors_per_track jne count_sector mov sector, 1 inc track ; Si se llega a la pista 80 (tracks_per_side), se lee en el ; otro lado del disquete cmp track, tracks_per_side jne count_sector inc side ; Si hay que leer el lado 3, se termina porque el disquete ; sólo tiene 2 lados cmp side, 3 je close_file count_sector: dec uSector ; Disminuir conteo de sectores a copiar jne get_next_sector ; Si no se llegó a cero, copiar el siguiente ; Cerrar el fichero close_file: mov ah, 3Eh ; cerrar el fichero int 21h int 20h ; regresar a DOS ... ; ---------------------------------------------------------- _szDisplay: push si _print_string: lodsb test al, al jz _exit_szDisplay mov bx, 000Fh mov ah, 0Eh int 10h jmp _print_string _exit_szDisplay: pop si ret _ZeroMemory: push di mov al, 0 rep stosb pop di ret ; ----------------------------------------------------------------- file db 'BOOT.BIN',0 bu_file db 'BOOT.BAK',0 msg1 db 'MBRREST Version 2.0 (c) 2002', 13, 10 db '----------------------------', 13, 10 db 'Haciendo respaldo del sector de arranque...', 13, 10, 0 msg2 db 'Escribiendo archivo en el inicio del disco...', 13, 10, 0 msg3 db 'No se trabaja con un disquete de 1.44 MB',13, 10, 0 warning db 'No se introdujo el nombre del archivo a escribir. ' db 'No se escribir', 0A0h, 20h, 'en el disco', 13, 10, 0 error_r db 'Error leyendo el binario.', 13, 10, 0 error_w db 'ERROR: No se pudo escribir el sector de arranque a ' db 'boot.bak', 13, 10, 0 head db 0 track db 0 sector db 0 side db 0 uSector dw 0 buffer db 512 dup (0) ; ----------------------------------------------------------------- END entry ; ----------------------------------------------------------------- *NOTA* Nota que el archivo de respaldo simpre lleva el mismo nombre: "BOOT.BAK" y que es copiado en el mismo directorio del archivo copiado al disquete. Esto quiere decir que, si se va a restaurar este archivo, *debe cambiársele el nombre*, sino cuando haga el respaldo nuevo, sobreescribirá el "BOOT.BAK" anterior y el archivo que se copiará no será el anteriormente salvado. ; ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Para usar MBRREST, hay que usar la siguiente orden: MBRREST image.bin donde " image.bin" es el nombre de la imagen a copiar en los primeros sectores del disquete. Si no se le pasa nombre, MBRREST sólo hará un respaldo del primer sector del disquete. Esta herramienta no te funcionará correctamente desde Windows 2000, ya que protege contra escritura en discos. - Análisis de MBRREST - La primera observación que hay que hacer es que debemos obtener un fichero .COM, no .EXE. Un fichero .COM usa siempre modelo de memoria TINY: un único segmento para datos y código. Tampoco es necesario definir una pila: los ejecutables con formato COM generan automáticamente la pila. Como estos archivos usan un segmento para código y datos, su tamaño está restringido a 64KB. Cuando DOS monta el programa, pone en DS y CS la dirección del PSP (Prefijo de Segmento el Programa). El PSP es una pequeña base de datos de 256 (100h) bytes que construye DOS en el límite de un párrafo (16 bytes) en la memoria interna disponible cuando carga un programa en memoria, inmediatamente después se cargará el programa mismo. El PSP también forma parte del segmento único de 64 KB. Como el programa comienza a ejecutarse inmediatamente después del PSP, debe indicársele al ensamblador que genere el código a partir del desplazamiento 100h (256). Esto se le indica con la directiva ORG, inmediatamente después de iniciar el segmento de código con .code o con la directiva SEGMENT: .code ORG 100h Los registros CS y DS ya tienen la dirección apropiada, así que no hay que iniciarlos como se hacía con los archivos .EXE. Nuestro programa, después de mostrar un mensaje con el copyright, obtiene un puntero a los argumentos que se la pasan al programa cuando se ejecuta desde DOS. El PSP mantiene en 80h:FFh, el bufer por omisión del DTA (Data Tranfer Disk: área de transferencia a disco); ahí DOS pone el texto completo que escribe el usuario después de la petición del programa por su nombre: se trataría de los parámetros que se le pasan al programa. La instrucción: ; ------------------------------------------------------------ ; Obtener el nombre del archivo pasado en la línea de órdenes ; ------------------------------------------------------------ mov si, 80h pone en SI la dirección del buffer DTA. El primer byte de ese buffer contiene el número de caracteres que tecleó el usuario después del nombre del programa. En el programa obtenemos es número con la instrucción: lodsb ; obtener número de caracteres Luego verificamos si se escribió algo después del nombre: test al, al ; ¿el número es cero? je no_file ; si es cero saltar si no se escribió nada, CL tendrá cero y se pasará el control dónde indica la etiqueta "no_file". Si se escribió algo, se procede saltarse los espacios. Después del nombre se debe escribir uno o más espacios para separarlo de los parámetros, y deben saltarse los bytes con esos espacios: ; saltarse los espacios is_space: lodsb ; obtener el caracter cmp al, 20h ; ¿es un espacio? je is_space ; saltar si es un espacio cmp al, 13h ; se alcanzó el final de la línea je no_file ; saltar si se llegó al final Esta rutina va colocando en AL el caracter a que apunta SI, lo cual es hecho por la instrucción LODSB; luego se revisa si ese caracter es 20H, el valor ASCII del espacio, y si lo es vuelve a ejecutarse LODSB y se repite el ciclo hasta encontrar un caracter distinto a 20h, el espacio. Después hay que revisar si sólo se pasaron espacios. El último caracter que debe haber en el buffer es 13 o 0Dh, que es el equivalente ASCII para el salto de linea generado al pulsar la tecla ENTER. Así que se este es el último caracter después de la serie de espacios, entonces no se introdujo parámetros y se pasa el control a la rutina en "no_file". Luego decrementamos SI en uno para que apunte al primer caracter. Salvamos en la pila la dirección que da inicio a los argumentos pasados. dec si ; ajustar el puntero push si ; salvar la dirección de la cadena A continuación buscamos el útimo caracter, que debe ser 13 o 0Dh: ; buscar el final de la cadena next: lodsb cmp al, 13 ; ¿se alcanzó el final de línea? jne next ; revisar siguiente si no se alcanzó? Ya localizado, lo reemplazamos por cero, para tener una cadena terminada en cero, y recuperamos en SI el puntero al inicio de la cadena: ; poner cero al final de la cadena dec si ; apuntar al final mov byte ptr [si], 0 ; poner cero al final pop si ; restablecer la direción de la cadena jmp _with_file ; proceder a hacer el respaldo Obsérvese la 2da. instrucción. Incluye un operador que indica el tipo de puntero, en este caso un puntero a byte: "ptr" es un operador que se usa para indicar el tamaño de un dato inmediato a poner en una dirección de memoria. Su formato es: tipo ptr dirección_de_memoria "tipo" es una indicación del tipo de dato, que puede ser: byte, word, dword, qword. Si no se pasó parámetros, se pone cero donde apunta SI, lo que se usará luego para revisar si el usuario llevó a cabo esta acción: no_file: dec si mov byte ptr [si], 0 ; SI apunta a cero Luego comenzamos las rutinas que usan la int 13h para hacer un respaldo del sector de arranque actual, por si acaso. Primero preparamos el sistema de disco con el servicio 0 e la int13: _with_file: mov ax, 0 ; reestablecer sistema de disco mov dl, 0 ; 0 para floppy, cambiar a 80h para int 13h ; discos duros Ahora verificamos si se trabaja sobre una unidad de floppy: se lee en el sector 18 de la primera pista; si da error, no es una unidad de floppy de 1.44MB: mov ah, 2 ; leer mov al, 1 ; un sector mov dh, 0 ; cabezal o lado 0 mov dl, 0 ; disquete: unidad A mov ch, 0 ; pista 0 mov cl, 18 ; sector 18 lea bx, buffer int 13h or ah, ah ; Si ah=0, se pudo leer el sector 18 je is_1_44 ; y el disquete es de 1.44MB lea si, msg3 call _szDisplay jmp close_file El número de sectores por pista, de pistas por lados, y de lados, son valores bien conocidos para los disquetes con capacidad de 1.44 MB: sectores por pista: 18 pistas por lado: 80 lados: 2 De inmediato leemos el primer sector del disco en el buffer usando el servicio 2 ede la int13. Esto lo hacemos cuatro veces. Los comentarios son bien descriptivos: is_1_44: ; Lectura CHS del MBR mov bp, 4 ; leer el sector de arranque 4 veces ... read: lea di, buffer ; buffer para la transferencia de datos mov cx, 512 ; tamaño del buffer = bytes por sector call _ZeroMemory ; Limpiar el buffer mov ah, 2 ; leer mov al, 1 ; un sector mov dh, 0 ; cabezal o lado 0 mov dl, 0 ; disquete: unidad A (80h: disco duro) mov ch, 0 ; pista 0 mov cl, 1 ; sector 1 lea bx, buffer int 13h dec bp jnz read Aquí hemos agregado una rutina que limpia el buffer: _ZeroMemory: _ZeroMemory: push di mov al, 0 rep stosb pop di ret La función recibe en CX el tamaño del buffer a limpiar y en DI la dirección del buffer. Lo que hace la subrutina es escribir 'n' cantidad de ceros a partir de la dirección señalada por DI. - Operaciones con archivos en DOS (I) - Ya con el buffer lleno, procedemos a escribir estos datos en un disco. Antes hay que crear el archivo donde pondremos los datos del sector de arranque, que ahora deben estar en el buffer. Para crear un fichero nuevo llamamos al servicio 3Ch de la int21 de DOS: mov ax, 3C00h ; crear el fichero de respaldo mov cx, 0 ; atributo del archivo 0 = normal lea dx, bu_file ; nombre del archivo int 21h jc error_writing ; saltar si hubo error mov bx, ax ; mover el handle del fichero a BX El atributo del archivo a crear se pasa en CX: 00: normal 01: sólo lectura 02: oculto 04: sistema DOS Si hay en error al crear, se activa la bandera de carro CF. La instrucción "JC" revisa esa bandera y si está activa pasa el control a la dirección indicada por la etiqueta que le sigue. Si la operación tiene exito devuelve en AX el handle del fichero creado, el cual se debe usar para operaciones de lectura/escritura sobre el fichero y para cerrarlo luego. A continuación procedemos a escribir los datos en el fichero creado, usando el servicio 40h de la int21 de DOS: ; Escritura del buffer en el fichero de respaldo mov ah, 40h ; escribir el mbr en boot.bak mov cx, 512 ; un sector = 512 bytes lea dx, buffer int 21h jc error_writing Como puede observarse, BX tiene el handle del fichero, CX la cantidad de datos a mover, y DX la dirección donde están los datos. Nuevamente, si hay error se activa la bandera de carro CF. Si no hubo problemas, se cierra el handle del fichero usando el servicio 3Eh de la int21; no se olvide que aún BX tiene el handle: ; Cierre del fichero de respaldo mov ah, 3Eh ; cerrar el archivo creado para respaldo int 21h jmp restore Si hubo error en la creación o lectura del archivo, desplegamos un aviso y salimos a DOS usando la int20 de DOS: error_writing: lea si, error_w no_name: call _szDisplay int 20h Pero si todo va bien, revisamos si se ha pasado el nombre de un archivo como parámetro; si no se hizo, se avisará que no se pasó el nombre de un archivo para copiar en el 1er. sector del disquete, no se copiará nada en él pero sí se conservará el que había ya en el disquete (recuérdese que al comienzo se hace un respaldo): restore: ; ¿Se pasó nombre de archivo como parámetro? lodsb test al, al jne @00 ; Si no se pasó nombre, avisar y salir lea si, warning jmp no_name Para la revisión, basta revisar si SI apunta a cero, lo que indicaría que no se ha pasado ningún nombre. Si se pasó un nombre, se procede a abrirlo en modo de sólo lectura usando el servicio 3Dh de la int21 de DOS: @00: push si lea si, msg2 ; desplegar el mensaje de aviso call _szDisplay pop si dec si mov ax, 3D00h ; abrir fichero indicado para lectura mov dx, si ; nombre del archivo int 21h jc error ; error abriendo el fichero? mov bx, ax ; mover el handle del fichero a BX Verificamos si el archivo cabe en un disquete de 1.44MB. Para obtener el tamaño del archivo, usamos el servicio 42h de la int 21h, que mueve el puntero del archivo al sitio indicado: mov ax, 4202h mov cx, 0 mov dx, cx int 21h jc error ; error abriendo el fichero? La dirección para colocar el puntero se pone en CX:DX. En AL debe colocarse desde donde debe tomarse la dirección: 0 contar desde el inicio del fichero 1 contar desde la dirección actual del puntero 2 contar desde el final del fichero El servicio regresa en DX:AX la nueva dirección. Por eso, si colocamos el puntero al final del fichero, obtendremos en DX:AX la nueva dirección del puntero, que en este cso coincidirá con el tamaño del fichero. Luego verificamos si el fichero cabe en una unidad de floppy. Primero comprobamos si es menor que 160000h = 1.441.792 bytes cmp dx, 016h jl good_for_floppy1 Si lo es, puede caber, sino se cierra el fichero y salimos del programa: bad_for_floppy: lea si, msg3 call _szDisplay jmp close_file ; cerrar el fichero Luego vemos si es menor que 150000h = 1.441.792; si no lo es, revisamos si la parte baja es mayor F900h = 63744 bytes (1.44MB = 0015F900h) good_for_floppy1: cmp dx, 015h jl good_for_floppy2 cmp ax, 0F900h jg bad_for_floppy Si todo ha ido bien, obtenemos el número de sectores que vamos a copiar: good_for_floppy2: mov cx, 200h div cx mov uSector, ax or dx, dx je rounded inc word ptr [uSector] Simplemente dividimos el valor DX:AX entre 200h, el númerode bytes en un sector. Si hay resto, es colocado en DX y agregamos 1 al valor contenido en la variable donde guardamos el número de sectores: uSector. *NOTA: la instrucción DIV* La división la hacemos usando la instrucción DIV. El fomato de esta instrucción es: DIV reg/memoria Acepta un operando, que puede ser un registro o un operador de memoria. Ese operando será el divisor. La instrucción se usa para dividir enteros sin signo. Divide entre un valor tipo word y uno tipo byte; uno tipo dword y otro word; uno qword y otro dword. El operando a la derecha de la instrucción designa eldivisor; para el dividendo se usa AX, DX:AX o EDX:EAX, dependiendo del tamaño del divisor. Es decir: · para dividir word entre byte: el dividendo en AX · para dividir dword entre word: el dividendo en DX:AX · para dividir qword entre dword: el dividendo en EDX:EAX Dependiendo del tamaño de los operandos, la operación devuelve en · AL el cociente y el residuo en AH para word/byte; · AX el cociente y el residuo en DX para dword/word; · EAX el cociente y el residuo en EDX para qword/dword; Ejemplos: b_dat DB ? w_dat DW ? d_dat DD ? operación divisor dividendo cociente residuo DIV CL byte AX AL AH DIV BX word DX:AX AX DX DIV EBX dword EDX:EAX EAX EDX DIV b_dat byte AX AL AH DIV w_dat word DX:AX AX DX DIV d_DAT dword EDX:EAX EAX EDX Una vez que tenemos el número de sectores, los copiamos en el disco. Primero debemos reponer el puntero del fichero al comienzo del mismo. rounded: mov ax, 4200h ; AL = 0: desde el comienzo mov cx, 0 ; poner en mov dx, cx ; 0000:0000 int 21h jc error ; error abriendo el fichero? El modo de apertura del archivo debe ser indicado en AL. Si se abrió el archivo sin problemas, entonces AX = 0. Como vamos a comenzar copiando el sector 1, de la pista o cilindro 0, del cabezal o lado 0, iniciamos las siguientes variables con estos valores: mov track, al mov head, al inc ax mov sector, al Luego leemos el archivo sector por sector, iniciando la lectura en el sector mencionado. Antes de leer cada sector, limpiamos el buffer: get_next_sector: lea di, buffer mov cx, 512 call _ZeroMemory La función 3Fh de la int 21h lee una cantidad indicada de bytes de un archivo iniciando en el lugar donde está el puntero de archivo y coloca lo leído en el buffer cuya dirección está en DX: mov ah, 3Fh ; leer el fichero en el buffer mov cx, 512 lea dx, buffer int 21h jnc write_sector ; ¿hubo error? lea si, error_r ; si hubo error, call _szDisplay ; avisar y jmp close_file ; cerrar el fichero Si hay error, se activa la bandera CF, lo que hará que se cierre el archivo y se salga del programa. Si todo va bien, podremos escribir el sector. Primero restablecemos la unidad: write_sector: push ax ; Escritura del fichero en el disco mov ax, ax ; restablecer el sistema de disco mov dl, 0 ; para disquete: mov dl, 0 int 13h Luego procedemos a escribir el sector, salvando el registro BX, donde se halla el handle del fichero: push bx mov bp, 4 ; escribir el MBR cuatro veces ... write: mov ax, 0301h ; AH: 3, escribir, AL: 1 sector mov dh, head mov dl, 0 mov ch, track mov cl, sector lea bx, buffer ; dirección del buffer a escribir int 13h dec bp jnz write Usamos el servicio 3 de la int 13h para escribir los datos al disco. Recuperamos el handle del archivo, que necesitaremos para cerrarlo: pop bx Si se llega al sector 18 (sectors_per_track), se escribe en el siguiente track y se comienza de nuevo el conteo de sectores: inc sector cmp sector, sectors_per_track jne count_sector mov sector, 1 inc track Si se llega a la pista 80 (tracks_per_side), se lee en el otro lado del disquete: cmp track, tracks_per_side jne count_sector inc side Si hay que leer el lado 3, se termina porque el disquete sólo tiene 2 lados: cmp side, 3 je close_file Se revisa si ya se leyeron todos los sectores, en cuyo caso el valor en uSector sería igual a cero. Si no es este el caso, se copia el siguiente sector del archivo. count_sector: dec uSector ; Disminuir conteo de sectores a copiar jne get_next_sector ; Si no se llegó a cero, copiar el siguiente Si ya leímos todo el archivo y lo hemos copiado a los primeros sectores del disco, cerramos el fichero y salimos close_file: mov ah, 3Eh ; cerrar el fichero int 21h int 20h ; regresar a DOS ... mov ah, 3Fh ; leer el fichero en el buffer mov cx, 512 ; leer 512 bytes y lea dx, Buffer ; ponerlos en esta dirección int 21h Ahora que hemos diseñado nuestra herramienta, procedemos a diseñar un pequeño sector de arranque. Antes un poco de información. Código del MBR (Master Boot Record: Sector Maestro de Arranque) =============================================================== Como ya hemos dicho, el sector de arranque de los discos duros, conocido como el MBR - Master Boot Record, es el sector CHS sector 0/0/1 o LBA 0. Este sector es cargado y ejecutado por el BIOS cuando se arranca desde el dico duro. Contiene un p equeño programa de arranque maestro que realizará varias tareas. El MBR es equivalente al sector de arranque de los diskettes, pero hay diferencias entre ambos, ya que el disquete no soporta particiones. Un MBR tiene tres partes esenciales: · El código que carga un sector de arranque desde una de las cuatro perticiones primarias. · La tabla de particiones en el desplazamiento 446, que tiene cuatro entradas de 16 bytes, una paracada partición. · Valores mágicos: 55h en el desplazamiento 510, 0AAh en el 511. El sector de arranque de un disquete no tiene tabla de partición. A continuación las direcciones donde se ubican las partes principales de un MBR: Desplazamiento Descripción Tamaño 000h Código ejecutable (arranque) 446 Bytes 1BEh 1ra entrada de la tabla de partición 16 Bytes 1CEh 2da entrada de la tabla de partición 16 Bytes 1DEh 3ra entrada de la tabla de partición 16 Bytes 1EEh 4ta entrada de la tabla de partición 16 Bytes 1FEh Marcador "AAh 55h" 2 Bytes El proceso de arranque es más o menos este: 1. El BIOS pone en el registro DL del procesador el número de la unidad de arranque: 0 para una unidad de floppy A, u 80h para el disco duro C. 2. El BIOS lee el sector CHS 0/0/1 del primer dispositivo de arranque disponible, en este caso sería el disco duro. 3. El sector es leído y colocado en una dirección fija de la memoria - 0000:7C00, a la cual se le transfiere el control. 4. El BIOS chequea el sector de arranque en busca de la signatura: 55h en el desplazamiento 510 y 0AAh en el 511. 5. El programa de arranque se reubicará a sí mismo en la memoria baja, para permitir que el sector de arranque de la partición activa sea cargado también en 0000:7C00h. 6. El programa maestro de arranque salta a la siguiente instrucción en la nueva dirección - generalmente 0000:06nn o 0060:nnnn. 7. El programa maestro de arranque chequeará las cuatro entradas de la "Tabla de Partición" en busca de una partición marcada como "activa" buscando un valor 80h en la entrada propicia de la tabla. 8. Si ninguna partición está marcada como "activa", el programa de arranque (dependiendo de quien lo escribió) informará al usuario sobre esto o ejecutará el ROM BASIC - llamando a la INT18, que generalmente reinicia desde otra unidad. El master boot record instalado por FDISK de DOS requerirá que el campo "activo" sea 00 u 80h para unidades de disco, cualquier otro valor ocacionará un mensaje "Invalid partitioning table". 9. El primer sector CHS de la partición "activa" será cargado en 0000:7C00, como el MBR. Luego se le transfiere ahí el control con un JMP a 0000:7C00. La misión del MBR es encontrar y cargar en la memoria el núcleo del sistema, o un segundo estadio del cargador; estas operaciones pueden realizarse de alguna de las tres siguientes maneras: · Asumir que el núcleo del sistema o el segundo estadio del cargador están cargados en espacios contiguos (el disco no tiene sistema de archivos) · Encontrar cuales sectores del disco contienen el núcleo del sistema o el segundo estadio del cargador, y escribir esta lista de sectores en el código del primer estadio (el disco puede o no o tener sistema de archivos) · Usar el sistema de archivos presente en el disco El programa de arranque debe obtener el valor de sectores por pista del disco. Las particiones FAT pueden obtenerla de una tabla que DOS tiene en el sector de arrranque: el BPB (BIOS parameter block). Otros sistemas de archivos, asumen un valor, como 18 sectores por pista, o usan prueba directa: se cargan sectores usando la int13 y se practica una búsqueda binaria hasta encontrar el valor sectores/track. Unas palabras sobre las particiones ----------------------------------- Como algunas personas a veces quieren tener en su máquina más de un sistema operativo, IBM decidió implementar particionamiento del disco. Para estos fines, reservó una pequeña area en el primer sector del disco: el sector CHS 0/0/1 o sector LBA 0, para una Tabla de Partición. Esa tabla tiene cuatro entradas; cada una especifica las direcciones del comienzo y del final de una partición: uno de los dos campos espécifica el comienzo, mientras que el otro especifica el número de sectores consecutivos en las particiones. Por ejemplo, para una unidad de 1 GB: 520 Cilindros 64 Cabezales 63 Sectores Por compatibilidad con la INT13, muchas unidades ATA-2 tendrán una traducción CHS a LBA donde los sectores por pista (SPT: Sectors Per Track), no excederán los 63, incluso si todas las unidades permiten que el máximo sea 255. Los 16 bytes de una entrada de la tabla de partición se usan así: +--- El bit 7 es la bandera de partición activa, los bits 6-0 están | en cero. | +--- Comienzo de CHS en el formato de una llamada a INT13. | | | | +--- Byte de tipo de partición. | | | | | | +--- Fin de CHS en el formato de una llamada a INT13. | | | | | | | | +-- Comienzo de LBA. | | | | | | | | | | +-- Tamaño en sectores. | | | | | | v <--+---> v <--+--> v v 0 1 2 3 4 5 6 7 8 9 A B C D E F DH DL CH CL TB DL CH CL LBA..... SIZE.... 80 01 01 00 06 0e be 94 3e000000 0c610900 1ra entrada 00 00 81 95 05 0e fe 7d 4a610900 724e0300 2da entrada 00 00 00 00 00 00 00 00 00000000 00000000 3ra entrada 00 00 00 00 00 00 00 00 00000000 00000000 4ta entrada Los bytes 0-3 son usados por el pequeño programa en el MBR para ubicar el primer sector de una partición activa en la memoria. El byte cero indica si la partición es inicializable: su valor es devuelto en DH. El byte 1 indica el cabezal o lado (base 0): su valor es devuelto en DL. El byte 2 contiene 8 bits bajos de que indican el número de cilindro o pista (base 0). El byte 4 se divide en dos partes: sus 6 bits bajos indican el número de sector donde inicia la partición (base 1) y los 2 bits altos corresponden a los dos bits altos de del número de cilindro (el número de cilindro se indica por un dato de 10 bits, 8 bajos los pone CH, y dos altos los pone los dos bits altos de CL; así que el número de pistas puede ir de 0 a 2^10 = XXXX) El byte 4 indica el tipo de partición. En este caso es 6: DOS FAT. Lista de algunos tipos de particiones ----------------------------------------------------------- | Valor | Descripción ----------------------------------------------------------- | 00h | Desconocida o nada ----------------------------------------------------------- | 01h | FAT de 12-bit ----------------------------------------------------------- | 04h | FAT de 16-bit (Partición menor que 32MB) ----------------------------------------------------------- | 05h | Partición Extendida MS-DOS ----------------------------------------------------------- | 06h | FAT de 16-bit (Partición mayor que 332MB) ----------------------------------------------------------- | 0Bh | FAT de 32-bit (Partición de hasta 2048GB) ----------------------------------------------------------- | 0Ch | Lo mismo que 0BH, pero usa extensiones LBA1 13h ----------------------------------------------------------- | 0Eh | Lo mismo que 06H, pero usa extensiones LBA1 13h ----------------------------------------------------------- | 0Fh | Lo mismo que 05H, pero usa extensiones LBA1 13h ----------------------------------------------------------- | 80h | Linux ----------------------------------------------------------- | 81h | Linux -Minix ----------------------------------------------------------- Como puede verse, la partición comienza en CHS 0H, 1H, 1H (LBA 3EH) y termina en CHS 294H, EH, 3EH con un tamaño de 9610CH sectores. El valor en este campo de 32 bits se calcula de la manera estándar: Inicio = ((InicioC * HeadsPerCylinder) + InicioH) * SectorsPerTrack) + InicioS - 1 La segunda partición es del tipo 5: partición extendida; comienza en CHS 295H, 0H, 1H (LBA 9614AH) y termina en CHS 37DH, EH, 3EH con un tamaño de 34E72H sectores. Este campo también es de 32-bits, para decir cuántos sectores usa la partición. Puede ser usado calculando dos algorritmos mayores: Cuenta = (((FinC - InicioC) * HeadsPerCylinder) + (FinH - InicioH)) * + SectorsPerTrack + (FinS - InicioS) C = Cilindro H = Cabezal [Head] Track = Pista HeadsPerCylinder = Cabezal por Cilindro SectorsPerTrack = Sectores por Pista En el campo de arriba no se resta 1 al final, porque queremos el "contador de sectores", y no la dirección LBA con base cero del último sector. Hay otro método también, la idea es la misma, pero calcula el "último sector LBA" y substrae el después el campo "Inicio" [start]: Cuenta = (((FinC * HeadsPerCylinder) + FinH) * SectorsPerTrack + FinS - 1) - Inicio Las entradas de la tercera y de la cuarta tabla no se están usando. A continuación una tabla con los detalles de una tabla de partición: ------------------------------------------------- |desplazamiento Descripción Tamaño ------------------------------------------------- | 00h Estado (00h=Inactive, 1 byte | 80h=Active) ------------------------------------------------- | 01h Comienzo de la | Partición - Cabezal 1 byte ------------------------------------------------- | 02h Comienzo de la | Partición - 1 Word | Cilindro/Sector (2 bytes) ------------------------------------------------- | 04h Tipo de | Partición 1 Byte ------------------------------------------------- | 05h Fin de la | Partición - Cabezal 1 byte ------------------------------------------------- | 06h Fin de la | Partición - Cabezal 1 Word | Cilindro/Sector (2 bytes) ------------------------------------------------- | 08h Número de sectores | entre el MBR y el 1 DWord | Primer Sector en la (4 bytes) | Partición ------------------------------------------------- | 0Ch Número de sectores 1 DWord | en la Partición ------------------------------------------------- Escribir un sector de arranque ------------------------------ Para terminar, hagamos el ejercicio de escribir un pequeño sector de arranque para la unidad de floppy. Por supuesto, no tendrá tabla de particiones lo que lo hará más sencillo. Usaremos la plantilla que expusimos arriba que emplea NASM: ; --------------------------------------------------------------------------- ; ----------------------------------------------------------------- ; boots.ASM ; Pequeño bootstrap ; Carga un programa del disco y le pasa el control ; Versión: n u MiT_o r ; ----------------------------------------------------------------- ; ---------------------------------------------------------------- ; Hay que decirle al ensamblador que este es el desplazamiento 0. ; Si no es el desplazamiento 0, lo será después del salto. ; ---------------------------------------------------------------- ORG 0 jmp 07C0h:start ; Ir al segmento 07C0 start: ; ------------------------------------- ; Actualizar los registros de segmento ; ------------------------------------- cli ; Deshabilitar las interrupciones mov ax, 07C0h ; Ajustar los registros del mov ds, ax ; segmento de datos mov es, ax ; ------------------- ; Establecer la pila ; ------------------- mov ax, 9000h ; Poner la pila en 90000h mov ss, ax ; SS = 9000h mov sp, 0FFF0h ; SP = 0FFF0h => Stack = 90000h sti ; habilitar interrupciones ; -------------------------------- ; Desplegar mensaje de bienvenida ; -------------------------------- lea si, [__msg] call _szDisplay_ ; -------------------------------- ; Restablecer la unidad de floppy ; -------------------------------- reset: mov cx, 0 mov ax, cx ; reestablecer sistema de disco mov dl, al ; 0 para floppy, cambiar a 80h para discos duros int 13h ; ---------------------------------- ; Subir el 2do. sector a la memoria ; ---------------------------------- read: mov bx, 200h ; desplazamiento de la segunda etapa mov ah, 2 ; Cargar los datos del disco a ES:BX mov al, 5 ; Cargar 3 sectores mov ch, 0 ; Cilindros=0 mov cl, 2 ; Sector=2 mov dh, 0 ; Cabezal=0 mov dl, 0 ; Unidad=0 int 13h ; Leer! jnc ok_load_setup ; si no hubo problemas, se continúa lea si, [_err_] ; despliega mensaje de error call _szDisplay_ mov ah, 16 int 16h jmp read ok_load_setup: lea si, [__msg_] ; despliega mensaje de aviso call _szDisplay_ mov ah, 16 int 16h DB 0xEA ; jump to sector 02 DW 0x7E00 ; offset DW 0x0000 ; segment ; ==================================================================== _szDisplay_: push si _print_string: lodsb test al, al jz _exit_szDisplay_ mov bx, 000Fh mov ah, 0Eh int 10h jmp _print_string _exit_szDisplay_: pop si ret ; -------------------------------------------------------------------- __msg db " --------------------------------------", 13, 10 db "Prueba de dise", 0A4h, "o de bootstrap", 13, 10 db "Por n u MiT_o r", 13, 10 db "----------------------------------------", 0 __msg_ db 13, 10, "Sector 2 copiado en 07C0h:0200h", 13, 10 db "Pulsa una tecla para continuar....", 13, 10, 0 _err_ db 13, 10, "Error al leer desde el disco", 13, 10, 0 times 510-($-$$) db 0 dw 0AA55h ; --------------------------------------------------------------------------- Este es el programa que será cargado: ; --------------------------------------------------------------------------- ; ------------------------------------- ; console.asm ; Pequeño intérprete de órdenes ; (C) n u MiT_o r 2002 ; ------------------------------------- ; ------------------------------------- ; Acepta tres órdenes: ; help: imprime lista de órdenes ; clean: limpia la pantalla ; exit: reinicia del sistema ; ------------------------------------- _init: ; Despliega de bienvenida lea si, [msg1] call _szDisplay _loop: ; Despliega puntero lea si, [puntero] call _szDisplay ; Obtiene la orden lea di, [command] call _get_command _is_clean: ; ¿Es la orden clean? lea si, [_clean_] call _compare test ax, ax je _is_exit ; Limpiar el monitor call clean_ jmp _loop _is_exit: ; ¿Es la orden "exit" lea si, [_exit_] call _compare test ax, ax je _is_help ; reiniciar el sistema _reboot: db 0EAh ; Código de operación para JMP dw 0000h ; Saltar a FFFF:0000h, que reinicia dw 0FFFFh ; el sistema _is_help: ; ¿Es la orden "help" lea si, [_help_] call _compare test ax, ax je _no_order _help: lea si, [hmsg] call _szDisplay jmp _loop _no_order: lea si, [msg2] call _szDisplay jmp _loop ; ==================================================================== _szDisplay: push si __print_string: lodsb test al, al jz __exit_szDisplay call __display_char jmp __print_string __exit_szDisplay: pop si ret __display_char: mov bx, 000Fh mov ah, 0Eh int 10h ret ; -------------------------------------------------------------------- _get_command: push di mov cx, 12 _loop_: mov ah, 10h int 16h filt_extended_function_keys: test al, al je _loop_ cmp al, 0E0h je _loop_ is_enter_key: cmp al, 0Dh je _exit_get_command dec cx test cx, cx je _excess get_character: stosb call __display_char jmp _loop_ _exit_get_command: xor al, al stosb pop di ret _excess: lea si, [msg3] call _szDisplay mov cx, 12 pop di push di jmp _loop_ ; -------------------------------------------------------------------- _compare: push si push di get_string_lenght: xchg di, si call _strlen xchg si, di _compare_strings: repe cmpsb test cx, cx jne _not_equals mov ax, 1 jmp _exit_compare _not_equals: mov ax, 0 _exit_compare: pop di pop si ret _strlen: push di mov al, 0 mov cx, -1 ; cx = -1 = FFFFh = 65535 = infinito repne scasb neg cx dec cx dec cx pop di ret ; -------------------------------------------------------------------- clean_: mov dx, 0 call set_cursor mov cx, 80*25 _print: mov al, 32 ; 32 = 20h = espacio call __display_char loop _print mov dx, 0 call set_cursor ret set_cursor: mov ah, 2 int 10h ret ; ---------------------------------------------------------------------------------- msg1 db "Int",82h,"rprete de ",0A2h,"rdenes", 10, 13 db "Escribe 'help' para ver las opciones", 10, 13, 0 msg2 db 7, 10, 13, "No es una orden del programa!", 10, 13, 0 msg3 db 7, 10, 13, "Introdujo m",0A0h,"s de 12 caracteres", 10, 13 puntero db 10, 13, "> ", 0 hmsg db 10, 13, "Ordenes disponibles en este shell:", 10, 13 db " ", 0FEh," help: despliega este mensaje", 10, 13 db " ", 0FEh," clear: limpia el monitor", 10, 13 db " ", 0FEh," exit: reinicia el sistema", 10, 13, 0 _help_ db "help", 0 _clean_ db "clean", 0 _exit_ db "exit", 0 command: times 12 db 0 ; ---------------------------------------------------------------------------------- El siguiente programa crea un archivo imagen que contiene el bootstrap y el pequeño programa: ; --------------------------------------------------------------------------- ; --------------------------------------------------- ; IMAGE.ASM ; Disk image ; --------------------------------------------------- %include 'boot.asm' %include 'console.asm' ; --------------------------------- ; Todo se compila con ; nasm image.asm -o image.img ; ---------------------------------- ; --------------------------------------------------------------------------- Al compilar el programa, se genera un archivo llamado image.img. Para ejecutarlo, hay que copiarlo en un disquete. Se puede emplear para ello la herramienta que escribimos: MBRREST; pero si lo deseas, puedes usar RAWRITE: http://uranus.it.swin.edu.au/~jn/linux/rawwrite.htm. Después de copiada la imagen, hay que arrancar el sistema con el disquete en la unidad de floppy. Al final, tendrás el pequeño shell que hemos escrito; éste te permitirá obtener información sobre las órdenes disponibles (help), limpiar el monitor (clear) y reiniciar el sistema (exit). Para copiar la imagen en el disquete con MBRREST, copiamos este programa en el mismo subdirectorio donde se encuentra image.img; introducimos un disquete en la unidad de flopppy A y ejecutamos la siguiente orden: mbrrest image.img El siguiente guión .BAT compilará todo y copiará la imagen en el disquete, siéste está ya en la unidad de floppy: ---------------------- MAKE.BAT --------------------------- del image.img nasm image.asm -o image.img if errorlevel 1 goto ERROR cls mbrrest image.img @echo off goto no_err :ERROR @echo ERROR! pause :no_err ------------------------------------------------------------- ................ El programa sólo funcionará en unidades de floppy, porque no incluye tabla de partición. Debido a lo delicada que es la tarea de escribir sobre el primer sector de un disco duro, tan sólo haremos comentarios al respecto y dejamos al juicio del lector el realizar la práctica o no de un MBR propio. Con la info expuesta aquí se tiene ya una base mínima para crear hasta un sencillo editor de particiones (en el Apéndice 1 encontrarás un análisis de un sector de arranque de Windows). Nuestro programa consta de dos partes, una pensada para ocupar el primer sector del disco, que contiene el código de arranque. La otra parte ocupará el segundo sector del disco y será caragada en memoria por el código del sector de arranque. El código de la segunda parte es el pequeño intérprete de órdenes que habíamos escrito. Para probar el programa, lo escribiremos en los dos primeros sectores de un disquete, y arrancamos el sistema con este disquete en la unidad de floppy, con la opción de arrancar primero por esta unidad. El código del arranque está debidamente comentado. El código de la cónsola ya lo revisamos en el capítulo anterior. En el de cónsola hemos hecho un pequeño cambio: hemos reemplazado la orden "quit", que permitía salir a DOS, por la orden "help", que despliega una lista de las órdenes disponibles. Como estamos corriendo sin sistema operativo, no tiene sentido salir a DOS. Un estudio completo del disco incluiría el análisis del sistema de archivos del sistema. Cómo ahora estamos ocupados en la escritura de un bootstrap, y nos interesa revisar los registros de sistema del procesador, dejaremos esto de lado pasajeramente y nos concentraremos en aprender a cambiar el modo de ejecución del procesador. De todas maneras incluyo algunos apéndices con código e información importante sobre el código y los datos en el MBR de un disco duro: aquí nos hemos limitado a revisar el sector de arranque de un diaquete. * APÉNDICES * ------------------------------------ Apéndice 1: Desensamblaje de un MBR ------------------------------------- A continuación, el desensamblaje debidamente comentado que hice del MBR de de un disco duro. Para obtenerlo usé siguiente programa, que salva a un archivo el primer sector de un disco duro: ; ********************************************************************************* TITLE BSFSAVE.ASM: Utilidad para recuperar el sector de arranque de un disquete ; ********************************************************************************* ;by Evil-E [CodeBreakers] .MODEL TINY .CODE ORG 100h START: mov ah,9 ; displegar el texto de copyright :-) mov dx,offset msg int 21h xor ax,ax ; reestablecer sistema de disco mov dl,80h int 13h mov bp,4 ; leer el sector de arranque bootsector 4 veces ... read: mov ax,201h ; leer el sector mov dx,0 ; cabezal 0. Para disco duro: mov dx, 0080h mov cx,1 ; pista 0, sector 1 mov bx, offset buffer int 13h dec bp jnz read mov ax,3c00h ; crear fichero xor cx,cx mov dx,offset file int 21h xchg bx,ax ; mover filehandle a BX mov ah,40h ; escribir bootsector al archivo boot.bin mov cx,512 mov dx,offset buffer int 21h jc error_writing mov ah,3eh ; cerrar archivo int 21h int 20h ; regresar a DOS ... error_writing: mov ah,9 ; displegar un mensaje de error mov dx,offset error int 21h int 20h ; regresar a DOS ... file db 'BOOT.BIN',0 msg db 'SAVE Version 1.0 by Evil-E [CB] (c) 1998',13,10 db '----------------------------------------',13,10 db 'Escribiendo el mbr a boot.bin ...$' error db 'ERROR: No se puede escrbir el mbr a boot.bin$',13,10 buffer db 512 dup (0) ; para salvar el mbr END START ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Compilar com: tasm mbrsave tlink /t mbrsave ; ---------------------------------------------------------- El trabajo de desensamblaje lo realicé usando: BUBBLE CHAMBER: un buen desensamblador i8086 interactivo, excelente para pequeños ficheros en binario. ;********* File: boot.bin ************* ; code SEGMENT ASSUME CS:code, DS:code ORG 00h start: ; ------------------------------------------------------------------------ ; La rutina primero crea una pila (stack) que tiene un tamaño de 0:7C00h ; ------------------------------------------------------------------------ XOR AX, AX ; AX = 0 MOV SS, AX ; SS = 0 MOV SP, 7C00h ; SP = 007Ch = ; --------------------------------------------------------------------- ; Inicializar los registros de segmento de datos y el extendido a cero ; --------------------------------------------------------------------- STI ; deshabilitar las interrupciones PUSH AX POP ES PUSH AX POP DS ; ---------------------------------------------------------------------------------- ; Se hace que el registro indice de origen (source index: si), apunte al ; final de la pila + 1Bh (27). ; ; El sector se copia a sí mismo en la dirección 0:061Bh de la memoria y pasa el control ; a esa dirección, o a la dirección 1Bh de la rutina del sector de arranque, que ; debería ser la siguiente instrución de la misma rutina ; ---------------------------------------------------------------------------------- CLD MOV SI, 7C1Bh ; Copiar desde 7C1Bh, desde el desplazamiento 27 MOV DI, 061Bh ; Copiar a 061Bh PUSH AX ; 0000: PUSH DI ; 061B (new_address) en la pila MOV CX, 01E5h ; Copiar 485 bytes = 512 - 27 REPZ MOVSB RETF ; Salta a new_address (0000:061Bh) ; ------------------------------------------------------------------------------------- ; Si todo va bien, el fragmento siguiente habrá sido copiado en 061Bh y tomará el ; control de la ejecución. ; ; Inmediatamente se chequea la tabla de partición en 0:07BEh y confirma si se trata de ; un disco duro (valor 080h) que puede arrancar o no. Si no lo es, ejecuta la int 18h ; La tabla de particiones tiene cuatro entradas, así que se hace un bucle de ; cuatro vueltas. ; ------------------------------------------------------------------------------------- new_address: MOV SI, 07BEh ; 0600+1BEh (446)= dirección de la tab de particiones: ; 512 - 2 - 16*4 MOV CL, 4 ; cuatro entradas CMP [SI], CH ; CH debe valer 0 JL J0002D ; Es menor que cero? (los números negativos tienen ; activo el bit más alto. El número 80h = 1000 0000 ; también tiene activo el bit más alto, activando ; la bandera de signo si este es el caso, provocando ; un salto. JNE J0003B ; Tabla no válida ADD SI, 10h ; Entrada siguiente: cada entrada tiene 16 (10h) bytes. J00020: CMP [SI],CH ; ¿SI apunta a 80h (1000 0000)? JL J0002D ; Si el bit 7 del byte a que apunta SI es 1, se activa ; SF y se pasa el control a J0002D JNZ J0003B ; Si no es ni 0 ni 80h, no es una tabla válida y se sale ADD SI, 10h ; Siguiente entrada LOOP J00020 ; Revisar las siguientes dos entradas INT 18 ; Si se llegó aquí, ninguna partición está activa y se ; ejecuta int18 ; --------------------------------------------------------------------- ; Se ha localizado una partición activa. Se guarda en CX:DX su dir. CHS ; -------------------------------------------------------------------- J0002D: MOV DX, [SI] ; En DH 80h y en DL el número de cabezal o lado. MOV BP, SI ; BP apunta al inicio de la entrada de la ; partición activa. ; ------------------------------------------------------------------- ; Se revisa si otras particiones tienen la bandera = 0. Si no lo ; tienen, entonces la tabla no es válida ; ------------------------------------------------------------------- J00031: ADD SI, 10h ; Apuntar a la siguiente entrada de la tabla DEC CX ; Se verifica si ya no quedan más entradas JZ J0004D ; Saltar si no hay otras entradas CMP [SI], CH ; ¿Es correcta esta entrada? JZ J00031 ; Si lo es se revisa la siguiente o seguir si hay error ; ------------------------------------------------------------------- ; Rutina que esribe una cadena terminada en cero usando sólo el BIOS ; Si llegamos aquí, es porque la tabla de partición no es válida. ; ------------------------------------------------------------------- J0003B: MOV SI, offset msg0 J0003E: DEC SI J0003F: LODSB CMP AL, 0 JZ J0003E MOV BX, 0007h MOV AH, 0Eh INT 10h J0004B: JMP J0003F ; ------------------------------------------------------------- ; Si llegamos aquí, es porque todas las particiones son válidas ; y una es de arranque ; ------------------------------------------------------------- J0004D: MOV [BP+25h], AX ; Mover 0 a un desplazamiento de 37 (25h) ; bytes por encima de la entrada activa ; de la tabla de particiones. XCHG AX, SI ; SI=0 y AX->final de la tabla de particiones. ; ---------------------------------------------------------------- ; Revisar la bandera de sistema operativo. Nos dice cuál es el SO ; ---------------------------------------------------------------- MOV AL, [BP+04h] ; Mover a AL el tipo de partición MOV AH, 06h CMP AL, 0Eh ; 0Eh = FAT16 con extensiones LBA1 13h? JZ J0006B MOV AH, 0Bh CMP AL, 0Ch ; 0Ch = FAT32 con extensiones? JZ J00065 CMP AL, AH ; 06 o 0Bh = FAT16 o FAT32? JNZ J0008F INC AX ; ---------------------------------------------------------------------- ; Si es una partición tipo 0Eh o 0Bh, se pone 6 en [BP+25h] ; ---------------------------------------------------------------------- J00065: MOV BYTE PTR [BP+25h], 06h JNZ J0008F ; si no es ninguno de estos valores ; se salta el siguiente código ; --------------------------------------------------------------------------- ; Chequeo de la instalación. El servicio 41h de la int13 chequea si se trata ; de una partición activa, con 55AAh (la signatura) en el desplazamiento 510 ; del MBR ; --------------------------------------------------------------------------- J0006B: MOV BX, 55AAh PUSH AX MOV AH, 41h INT 13h ; -------------------------------------------- ; Si CF es 1 hay error ; -------------------------------------------- POP AX ; SP-2 = 7C00h-2 JB J0008C ; Salta si hay error ; ¿Está instalado el disco? CMP BX, AA55h JNZ J0008C ; Salta si no se ha instalado disco ; El subconjunto API soporta bitmap? TEST CL, 01h JZ J0008C ; ----------------------------------------------------------- ; Lee el sector de arranque del disco y lo escribe en 7C00h ; ----------------------------------------------------------- MOV AH, AL MOV [BP+24h], DL ; indicador de unidad MOV WORD PTR [06A1h], 1EEBh ; en el desplazamiento A1h se escribe ; un salto a A3h+1Eh = 06C1h = J000C1 J0008C: MOV [BP+04h], AH J0008F: MOV DI, 000Ah J00092: MOV AX, 0201h ; leer un sector MOV BX, SP ; BX apunta al tope de la pila: ahí se escribirá el código ; siguiente. XOR CX, CX ; CX = 0 CMP DI, 05h ; ¿DI = 5? JG J000A1 ; MOV CX, [BP+25h] ; CX = 0 o 6 (se había puesto 0 o 6 en BP + 25h) J000A1: ADD CX, [BP+02h] ; Cilindro/sector de inicio de la partición INT 13h J000A6: JB J000D1 ; Error de lectura MOV SI, offset buff ; 0764h CMP BootSig, 0AA55h ; [7DFE] JZ J0010D ; Si la signatura está en su lugar, saltar SUB DI,05h JG J00092 J000B8: TEST SI,SI JNZ J0003F MOV SI, offset str1 ; 072Ch: Error de carga JMP J0004B J000C1: CBW XCHG CX,AX PUSH DX CWD ADD AX,[BP+08] ADC DX,[BP+0A] CALL 06E0 POP DX JMP 06A6 J000D1: DEC DI JZ J000B8 ; ---------------------- ; Restablecer la unidad ; ---------------------- XOR AX,AX ; Servicio 0 INT 13 ; de la int13 JMP J00092 ADD [BX+SI], AL ADC BYTE PTR [SI], 34h ADC DX, [BP+33h] NOT BYTE PTR [BP+56h] ; ----------------------------------------------------------------------- ; Realizar una lectura extendida del sector. SI -> dirección del paquete ; en el disco ; ----------------------------------------------------------------------- J006E0: PUSH SI XOR SI,SI PUSH SI PUSH SI PUSH DX PUSH AX PUSH ES PUSH BX PUSH CX MOV SI ,0010h PUSH SI MOV SI, SP PUSH AX PUSH DX MOV AX, 4200h ; Servicio 42h MOV DL, [BP+24h] ; INT 13 ; De la int13 POP DX POP AX LEA SP, [SI+10h] ; Dir de la bandera de 64 bits del buffer JB J0010B ; Saltar si hubo error J00101: INC AX JNZ J00105 INC DX J00105: ADD BH,02h LOOP J00101 CLC J0010B: POP SI RETN ; salto cercano J0010D: JMP SHORT J00183 msg0 DB "Tabla de partici", F3h, "n no v", 0E1h, "lida", 0 msg1 DB "Se ha producido un error al cargar el sistema operativo.", 0 ;00159 buff DB 30 DUP (00h) ;00164 ; ---------------------------------------------------------------------------------- ; Pasar el control a DS:DI = 0000:7C00, donde debe encontrarse el código que sigue ; a éste. ; ---------------------------------------------------------------------------------- J00183: MOV DI, SP PUSH DS PUSH DI MOV SI, BP RETF ; saltar al sistema operativo ; El código del sector de arranque está en ; 0000:7C00 y continúa el proceso de arranque DB "artition" ;0018A DB 0Ah ;00192 DB 0Dh ;00193 DB 28 DUP (00h) ;00194 DB "PQ" ;001B0 DB 02h ;001B2 DB 03h ;001B3 DB 04h ;001B4 DB 05h ;001B5 DB 00h ;001B6 DB 03h ;001B7 DB "I" ;001B8 DB D8h ;001B9 DB A9h ;001BA DB A4h ;001BB DB 2 DUP (00h) ;001BC ; Tabla de particiones ; -------------------------------------------- ; Primera entrada ; -------------------------------------------- boot DB 00h ;001BE iniHe DB 01h ;001BF iniSs DB 00h ;001C0 iniCi DB 00h ;001C1 ; -------------------------------------------- OSflg DB 0Bh ;001C2 ; FAT de 32-bit (Partición de hasta 2048GB) endHe DB 3Fh ;001C3 endSe DB BFh ;001C4 endCi DB 3Eh ;001C5 ; -------------------------------------------- DD 3F000000h ;001C6 ; -------------------------------------------- lenght DD 01602300h ;001CA ; -------------------------------------------- ; -------------------------------------------- ; Segunda entrada ; -------------------------------------------- DB 80h ;001CE <- Esta es la partición activa DB 00h ;001CF DB 81h ;001D0 DB 3Fh ;001D1 ; -------------------------------------------- DB 81h ;001D2 <- MINIX o Linux DB 0Fh ;001D3 DB BFh ;001D4 DB 6Ch ; -------------------------------------------- DB 40h, 60h, 23h ;001D5 DB 00h ;001D9 ; -------------------------------------------- DB B0h ;001DA DB C8h ;001DB DB 02h ;001DC DB 00h ;001DD ; -------------------------------------------- ; -------------------------------------------- ; Otras dos entradas ; -------------------------------------------- DB 32 DUP (00h) ;001DE ; -------------------------------------------- ; -------------------------------------------- ; Signatura del sector de arranque ; -------------------------------------------- BootSig DW 55AAh ;001FE ; -------------------------------------------- code ENDS END start ; -------------------------------------------- Resumamos qué hace esta rutina. 1. Reserva un espacio en la memoria (pila) con un tamaño 07Ch=124 bytes XOR AX, AX ; AX = 0 MOV SS, AX ; SS = 0 MOV SP, 7C00h ; SP = 007Ch = 124 bytes 2. Inicializa los registros de segmento de datos y el extendido a cero. Hace que el registro indice de origen (source index: si), apunte al final de la pila + 1Bh (27). CLD MOV SI, 7C1Bh ; Copiar desde 7C1Bh, desde el ; desplazamiento 27 El sector se copia a sí mismo en la dirección 0:061Bh-01E5h de la memoria y pasa el control a esa dirección, o a la dirección 1Bh de la rutina del sector de arranque, que debería ser la instrución que sigue la última de esta rutina CLD ; Copiar desde el final MOV DI, 061Bh ; Copiar desde 0:061Bh PUSH AX ; 0000: PUSH DI ; 061Bh en la pila MOV CX, 01E5h ; Copiar 485 bytes = 512 - 27 REPZ MOVSB RETF ; Salta a 0000:061Bh El control se pasa a 0000:061Bh de una manera peculiar: se emplea la instrucción RETF, que pasa el control a la dirección indicada en el tope de la pila, y que está indicado por el registro puntero de pila SP. A diferencia de RET, la instrucción RETF usa direccionamiento de memoria, base:desplazamiento. En este caso sería 0000:061Bh, puesto primero con "PUSH AX" (AX=0) y "MOV DI, 061Bh - PUSH DI". Antes de pasar el control a esta dirección, se movió el resto del MBR a esta dirección. Luego revisa la tabla de particiones en busca de alguna activa. El primer byte en la tabla debe contener 0, válida pero inactiva, u 80h, válida y activa: new_address: MOV SI, 07BEh ; 0600+1BEh (446)= dirección de la tab de particiones: ; 512 - 2 - 16*4 MOV CL, 4 ; cuatro entradas CMP [SI], CH ; CH debe valer 0 JL J0002D ; Es menor que cero? (los números negativos tienen ; activo el bit más alto. El número 80h = 1000 0000 ; también tiene activo el bit más alto, activando ; la bandera de signo si este es el caso, provocando ; un salto. JNE J0003B ; Tabla no válida ADD SI, 10h ; Entrada siguiente: cada entrada tiene 16 (10h) bytes. J00020: CMP [SI],CH ; ¿SI apunta a 80h (1000 0000)? JL J0002D ; Si el bit 7 del byte a que apunta SI es 1, se activa ; SF y se pasa el control a J0002D JNZ J0003B ; Si no es ni 0 ni 80h, no es una tabla válida y se sale ADD SI, 10h ; Siguiente entrada LOOP J00020 ; Revisar las siguientes dos entradas Si no se encontró ninguna partición activa se ejecuta la int18: INT 18h ; Si se llegó aquí, ninguna partición está activa y se ; ejecuta int18 Al final se copia el código relevante en la partición activa en 7C00h y se le pasa el control: ; ----------------------------------------------------------- ; Lee el sector de arranque del disco y lo escribe en 7C00h ; ----------------------------------------------------------- MOV AH, AL MOV [BP+24h], DL ; indicador de unidad MOV WORD PTR [06A1h], 1EEBh ; en el desplazamiento A1h se escribe ; un salto a A3h+1Eh = 06C1h = J000C1 J0008C: MOV [BP+04h], AH J0008F: MOV DI, 000Ah J00092: MOV AX, 0201h ; leer un sector MOV BX, SP ; BX apunta al tope de la pila: ahí ; se escribirá el código siguiente. XOR CX, CX ; CX = 0 CMP DI, 05h ; ¿DI = 5? JG J000A1 ; MOV CX, [BP+25h] ; CX = 0 o 6 (se había puesto 0 o 6 en BP + 25h) J000A1: ADD CX, [BP+02h] ; Cilindro/sector de inicio de la partición INT 13h J000A6: JB J000D1 ; Error de lectura MOV SI, offset buff ; 0764h CMP BootSig, 0AA55h ; [7DFE] JZ J0010D ; Si la signatura está en su lugar, saltar ; partición activa. Imagino que con esta información ya se estará en condiciones de escribir un programa para leer y escribir la tabla de partición del disco duro. Eso queda de ejercicio para el lector. Sólo le recomiendo precaución y que siempre tenga un respaldo del MBR original de su disco por si acaso. --------------------------------------- Apéndice 2: Anidamiento de la partición --------------------------------------- Es posible anidar una partición en otra. Veámoslo con un ejemplo: M = MBR (y cualquiera de los sectores en la misma pista [track]) E = Extended partition record: (y cualquiera de los sectores en la misma pista [track]) pri = una partición primaria (el primer sector es un sector de "arranque") sec = una partición secundaria (el primer sector es un sector "arranque") |<---------------- Todo el disco -------------->| | | |M | | | | E<---- resto de la 1ra part ext ---->| | | | E<- resto de la 2da part ext ->| | | La primera partición extendida es descrita en el MBR y ocupa todo el resto del disco que sigue a la partición primaria. La segunda partición extendida es descrita en el *record* de la primera partición extendida y ocupa todo el resto del disco que sigue a la primera partición secundaria. ------------------------------------------- Apéndice 3: Enlace de la tabla de partición ------------------------------------------- Las tablas o *records* de la partición pueden formar listas enlazadas. Esto significa que el MBR tiene una entrada que describe (apunta a) la primera partición extendida, la primera tabla de la partición extendida tiene una entrada que describe (apunta a) la segunda tabla de la partición extendida, y así. En teoría no hay límite para el tamaño del enlace. Cuando se pide a FDISK que muestre las "unidades lógicas" DOS, lee las listas enlazadas en busca de todas las particiones de tipo DOS FAT. Hay que recordar que en una tabla de partición extendida, sólo pueden usarse dos de cuatro entradas. Una cosa más. Dentro de una partición, la disposición de los datos del sistema de archivos varía enormemente. Sin embargo, se espera que el primer sector de una partición sea el sector de "arranque". Un sistema de archivos DOS FAT tiene: un sector de arranque, los sectores de la primera FAT, los sectores de la segunda FAT, los sectores del directorio raiz y finalmente el área de archivos de datos. --------------------------------------------- Apéndice 4: Indicadores de tipo de partición --------------------------------------------- 00h vacía 01h DOS FAT de 12-bits 02h XENIX (sistema de archivo root) 03h XENIX (sistema de archivo /usr (obsoleto) ) 04h DOS FAT de 16-bits (hasta 32M) 05h DOS 3.3+ (partición extendida) 06h DOS 3.31+ Sistemas de archivos grandes (FAT de 16-bits, más de 32M) 07h QNX 07h OS/2 HPFS 07h Windows NT NTFS 07h Unix Avanzado 08h AIX: partición activa, SplitDrive 09h AIX: partición de datos 09h Sistema de archivos coherente 0Ah OS/2 Manager de arranque 0Ah OPUS 0Ah Partición de intercambio (swap) coherente 10h OPUS 11h OS/2 Manager de arranque escondido: partición FAT de 12-bits 12h Compaq: partición de diagnósticos 14h (resulted from using Novell DOS 7.0 FDISK to delete Linux Native part) 14h OS/2 Manager de arranque escondido: partición FAT de 16-bits sub-32M 16h OS/2 Manager de arranque escondido: partición FAT de 16-bits sobre-32M 17h OS/2 Manager de arranque escondido: partición HPFS 18h AST special Windows swap file 24h NEC MS-DOS 3.x 3Ch PowerQuest PartitionMagic: partición de recuperación 40h VENIX 80286 42h SFS (Secure File System) por Peter Gutmann 50h Disk Manager, partición de sólo lectura 51h Disk Manager, partición de lectura/escritura 51h Novell??? 52h CP/M 52h Microport System V/386 56h GoldenBow VFeature 61h SpeedStor 63h Unix SysV/386, 386/ix 63h Mach, MtXinu BSD 4.3 sobre Mach 63h GNU HURD 64h Novell NetWare 65h Novell NetWare (3.11) 70h DiskSecure Multi-Boot 75h PC/IX 80h Minix v1.1 - 1.4a 81h Minix v1.4b+ 81h Linux 81h Mitac: Manager Avanzado de Disco (Advanced Disk Manager) 82h Linux: partición de intercambio (Swap) 83h Linux: sistema de archivos nativo [native file system] (ext2fs/xiafs) 84h OS/2- tipo reenumerado: partición 04h (relativa a la unidad escondida DOS C:) 93h Amoeba: sistema de archivos 94h Amoeba: tabla de bloques malos [bad block table] A5h FreeBSD B7h BSDI: sistema de archivos (secundariamente de intercambio --swap) B8h BSDI: partición de intercambio (Swap) (secundariamente de sistema de archivos) C1h DR-DOS 6.0 LOGIN.EXE-partición FAT de 12-bits asegurada C4h DR-DOS 6.0 LOGIN.EXE-partición FAT de 16-bits asegurada C6h DR-DOS 6.0 LOGIN.EXE-partición enorme asegurada [secured Huge partition] C7h Cyrnix Boot DBh CP/M, CP/M Concurrente, DOS Concurrente DBh CTOS (Convergent Technologies OS) E1h SpeedStor partición FAT de 12-bits E4h SpeedStor partición FAT extendida de 16-bits F2h DOS 3.3+ secundaria F4h SpeedStor FEh LANstep FFh Xenix tabla de bloques malos [bad block table] ------------------------------------------------ TO BE CONTINUED -----------> emailme: numit_or@cantv.net webZ: http://mipagina.cantv.net/numetorl869/ http://oberon.spaceports.com/~tutorial/aks/