jueves, 16 de mayo de 2013

Acceso a discos en lenguaje ensamblador

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.





5 comentarios:

  1. Porque desactiváis la opción de copiar las páginas web, programáis códigos inútiles, acaso no sabéis que mientras exista buscadores que presenten el código fuente de las paginas siempre será posible copiar cualquier pagina web de hecho tengo una copia de esta página solo como reto personal para verificar esta posibilidad, aunque realmente mientras se siga en la línea de código interpretado como es el HTML y no código compilado es inútil evitar que cualquier programador no necesariamente hábil copie sin problemas cualquier pagina, es mas alentáis a esa posibilidad solo por el hecho de prohibir es un reto que dejáis a la imaginación humana

    ResponderEliminar
  2. Tan fácil como es CTRL + U, y ya lo copias

    ResponderEliminar