Mostrando entradas con la etiqueta ensamblador. Mostrar todas las entradas
Mostrando entradas con la etiqueta ensamblador. Mostrar todas las entradas

lunes, 27 de agosto de 2012

Recursos Ensamblador 8086

Durante los últimos meses estuvimos publicando diversos posts acerca de lenguaje ensamblador. En este post nos gustaría compartir las mejores fuentes de información que estuvimos usando:

  • Esta presentación de modos de direccionamiento resultó muy útil para estar constantemente revisando los diferentes detalles al direccionar localidades de memoria.
  • La siguiente página muestra una lista muy buena de interrupciones y posibles valores para los parámetros de dichas interrupciones.
  • Una breve lista de instrucciones.
  • Definitivamente esta página contiene bastante detalle. En ella consultamos cómo exportar variables y funciones entre módulos y cómo ligar varios módulos en un ejecutable.

martes, 24 de julio de 2012

Ensamblador 8086 Buffer Doble

En este último post de la serie, explicaremos la técnica de buffer doble para pintar en la memoria de gráficos y comentaremos algunas posibles mejoras al código. Como platicábamos en el post anterior, si escribimos continuamente al área de memoria, puede suceder que versiones incompletas de nuestra imagen sean desplegadas. La técnica de buffer doble consiste en reservar un área de memoria del mismo tamaño de nuestra memoria de gráficos, en nuestro caso de 64,000 bytes (320x200); y pintar nuestros pixeles en este buffer. Una vez que tengamos nuestra imagen final lista, entonces copiamos nuestro buffer al área de memoria de gráficos. Esto minimiza el tiempo en el que estamos escribiendo directamente al área de gráficos y también el parpadeo. El siguiente video muestra el resultado final.

En el siguiente proyecto de mercurial podrán encontrar los archivos para construir el ejemplo. El archivo ensambla.bat contiene la instrucción usada para crear el ejecutable.

Reservar Memoria

Usamos la interrupción 21h con el servicio 48h para reservar memoria dinámicamente para el buffer doble, el registro bx contiene la cantidad de memoria deseada en bloques de 16 bytes (función iniciar_video en libgraf.asm). Puedes encontrar más información acerca de memoria dinámica en las siguientes referencias: referencia 1 y referencia 2. La variable vram guarda la dirección inicial del buffer doble. Ahora en el código, en lugar de escribir a la dirección A000h (memoria de gráficos), escribimos al buffer doble (vram).

Es importante notar que como reservamos memoria dinámicamente al momento de ligar los archivos (link) es necesario indicar cuanta memoria extra utilizará nuestro programa en tiempo de ejecución, es por ello que usamos la opción /link /cp:4000 en el comando ml del archivo ensambla.bat. El parámetro 4000 corresponde a la cantidad que solicitamos con la interrupción 21h, servicio 48h (4000 x 16 = 64,000 = 320 x 200).

Transparencia

Pareciera que la imagen de la pelota tiene transparencia, pero si la abrimos en un editor, el exterior es de color negro. Para lograr este efecto, en la función LoadBMP de libgraf.asm ignoramos los pixeles cuyo valor sea cero, que corresponde en este caso al color negro de nuestra paleta de colores.

Posibles Mejoras

Es notorio que la pelota se mueve lentamente, esto puede deberse a que estamos haciendo mucho procesamiento innecesario. En cada ciclo del programa (1) abrimos y leemos dos archivos (uno para la imagen de fondo y otro para la imagen de la pelota), (2) escribimos dos veces a la paleta de colores, (3) escribimos las dos imágenes al buffer y (4) copiamos el buffer doble a la memoria de gráficos. Los puntos 1 y 2 sólo son necesarios al inicio del programa.

Para el punto 1, podemos modificar el método mostrar_bmp (en libgraf.asm) para que abra el archivo, reserve memoria dinámica y guarde la imagen en esta memoria. El método mostrar_bmp arrojará como resultados el ancho, el alto y el apuntador a los pixeles de la imagen. De esta forma, en nuestro ciclo principal del programa trabajaremos con información en RAM y no tendremos que abrir continuamente archivos, lo cual es un proceso que consume mucho tiempo.

Con respecto al punto 2, estamos asumiendo que todas las imágenes usadas en el programa utilizan la misma paleta de colores. En caso de que no sea así, algunas imágenes podrían lucir extrañas. Hasta el momento, las imágenes que hemos guardado con Paint parecen tener una paleta común (usamos el programa pixelformer para observar la paleta de colores de las imágenes). Habría que modificar la función mostrar_bmp para que reciba un parámetro que indique si deseamos actualizar la paleta de colores con la paleta de colores de la imagen.

Aún así, en cada ciclo del programa estamos copiando al menos 64,000 bytes del buffer doble al área de memoria de gráficos (función copiar_buffer en libgraf.asm). Creo que resultaría interesante explorar otros modos de video, como el que menciona la siguiente página, que ya tiene soporte directo para un buffer doble y en donde es posible cambiar los buffers con pocas instrucciones.

miércoles, 18 de julio de 2012

Ensamblador 8086 Moviendo imagen BMP

En el post anterior de esta serie mostramos cómo desplegar una imagen bmp. En este post expandiremos el código para mover la imagen alrededor de la pantalla. El siguiente video muestra el objetivo final del programa.

Lo primero que realizamos fue separar las rutinas relacionadas con desplegar la imagen, éstas fueron colocadas en el archivo libgraf.asm con su respectivo archivo libgraf.inc para poder exportar las funciones más importantes a otros módulos (el siguiente post muestra cómo exportar funciones entre módulos). La función principal para desplegar una imagen bmp (mostrar_bmp) ahora recibe los siguientes parámetros: la ruta del archivo y la posición inicial de la esquina superior izquierda de la imagen en la pantalla. De esta forma ya podemos colocar una imagen en cualquier parte de la pantalla y no sólo en la esquina superior izquierda.

La lógica para mover la imagen en la pantalla es muy sencilla (bmp01.asm):

1 Inicializar el modo de video
2 Ciclo hasta que presionemos una tecla:
   2.1 Limpiar pantalla
   2.2 Actualizar la coordenada x en la dirección actual
   2.3 Si hubo una colisión contra el límite izquierdo o derecho
      2.3.1 Ajustar coordenada x
      2.3.2 Invertir dirección en x
   2.4 Realizar pasos 2.2 y 2.3 para y
   2.5 Desplegar imagen

El código que implementa esta lógica se muestra a continuación (bmp01.asm). En el siguiente proyecto de mercurial podrás descargar los 3 archivos necesarios para construir el ejecutable. El proyecto también incluye la imagen bmp usada. Para ensamblar y ligar estos archivos basta con invocar:

ml bmp01.asm libgraf.asm

Como pueden observar en el video, la imagen parpadea notablemente, esto se debe a que estamos escribiendo al área de memoria de gráficos al mismo tiempo que esta área está siendo desplegada en la pantalla. Esto causa que frecuentemente se desplieguen en la pantalla versiones incompletas de la imagen final. En el siguiente post exploraremos una opción para mejorar esto.

.model  small
.stack 128
INCLUDE libgraf.inc
.data
; El nombre del archivo debe terminar en 0
pelotaf         db "pelota.bmp",0
pelotax         dw 100
pelotay         dw 100
pelotad         dw 40
maxx            dw 320
maxy            dw 200
deltax          dw 2
deltay          dw 2

.code
.startup
  mov ax,@data
  mov ds,ax
main proc
  ; Configurar modo grafico VGA con resolucion de 320x200
  call    InitVid
  
  ciclo1:
  call limpiar_pantalla
  ; -------------------------------------------------------
  ; Controlar posicion x de la pelota
  ; -------------------------------------------------------
  testX:
  mov ax, pelotax
  ; avanzar la pelota en la direccion actual
  add ax, deltax
  
  ; validar colision contra limite derecho
  testmaxX:
  mov cx, maxx
  sub cx, pelotad
  cmp cx, ax
  jg testminX
  ; si la pelota esta en el limite derecho
  ; alinearla al limite e invertir la direccion
  mov ax, cx
  mov deltax, -2
  
  ; validar colision contra limite izquierdo
  testminX:
  cmp ax,0
  jg testY
  ; si la pelota esta en el limite izquierdo
  ; alinearla al limite e invertir la direccion
  mov ax,0
  mov deltax, 2
  
  ; -------------------------------------------------------
  ; Controlar posicion y de la pelota
  ; -------------------------------------------------------
  testY:
  mov bx, pelotay
  ; avanzar la pelota en la direccion actual
  add bx, deltay
  
  ; validar colision contra limite inferior
  testmaxY:
  mov cx, maxy
  sub cx, pelotad
  cmp cx, bx
  jg testminY
  ; si la pelota esta en el limite inferior
  ; alinearla al limite e invertir la direccion
  mov bx, cx
  mov deltay, -2
  
  ; validar colision contra limite superior
  testminY:
  cmp bx,0
  jg dibpelota
  ; si la pelota esta en el limite superior
  ; alinearla al limite e invertir la direccion
  mov bx,0
  mov deltay, 2
  
  ; -------------------------------------------------------
  ; Actualizar posicion (x,y) y pintar pelota
  ; -------------------------------------------------------
  dibpelota:
  mov pelotax, ax
  mov pelotay, bx
  mov dx, offset pelotaf
  call mostrar_bmp

  ; Si una tecla es presionada, salimos del ciclo.
  ; Noten que esta interrupcion no espera por una tecla,
  ; solo verifica si fue presionada y si no, continua.
  mov ah,01h
  int 16h
  jnz fin

  jmp ciclo1

  fin:
  ; Regresar a modo texto
  mov ax,0003h
  int 10h
  
  ; Finalizar el programa
  mov ax,4c00h
  int 21h
  ret
main endp
end

domingo, 8 de julio de 2012

Ensamblador 8086 Creando módulos

Conforme desarrollamos ejemplos más elaborados, el tamaño de nuestros archivos aumenta y muchas veces copiamos el mismo código en varios programas (por ejemplo, el código que despliega una imagen bmp). En este post, veremos una forma de cómo reusar funciones que están en un módulo (archivo) independiente.

Tomemos como ejemplo el programa de este post. El programa convierte dos cadenas a enteros usando la función atoi (ascii to integer), los suma y convierte el resultado a cadena usando itoa (integer to ascii) para poder desplegarlo. Las funciones itoa/atoi son funciones que podemos reusar en varios programas, por lo que es buena idea ponerlas en un archivo independiente.

Comencemos por colocar estas funciones en un archivo llamado libcad.asm. El segundo paso es crear un archivo que contenga los prototipos de las funciones que queremos usar en otros archivos. Llamemos a este archivo libcad.inc. El contenido del archivo libcad.inc es el siguiente:

.CODE
EXTERNDEF atoi:NEAR
EXTERNDEF itoa:NEAR
@CurSeg ENDS

Noten que debemos especificar el segmento en donde se encuentra lo que queremos exportar. En este caso nuestras funciones se encuentran en el segmento .CODE; además, es recomendable cerrar el segmento ya que incluiremos este archivo en otros archivos. La palabra clave EXTERNDEF declara lo que queremos exportar, en este caso es seguida por el nombre de la función y el tipo de la función (NEAR, porque estamos usando .MODEL SMALL en nuestros programas).

El archivo libcad.inc se debe incluir tanto en el archivo que define las funciones como en los archivos que las usan. El archivo libcad.asm queda de la siguiente forma:

.model SMALL
.STACK 128
INCLUDE libcad.inc
.CODE
; ========= Convertir cadena a numero =====================
; Parametros
; si: offset inicial de la cadena con respecto a DS
; Retorna
; bx: valor
atoi proc NEAR
  xor bx,bx   ;BX = 0

  atoi_1:
  lodsb       ;carga byte apuntado por SI en AL
              ;e incrementa si
  cmp al,'0'  ;es numero ascii? [0-9]
  jb noascii  ;no, salir
  cmp al,'9'
  ja noascii  ;no, salir

  sub al,30h  ;ascii '0'=30h, ascii '1'=31h...etc.
  cbw         ;byte a word
  push ax
  mov ax,bx   ;BX tendra el valor final
  mov cx,10
  mul cx      ;AX=AX*10
  mov bx,ax
  pop ax
  add bx,ax
  jmp atoi_1  ;seguir mientras SI apunte a un numero ascii
  noascii:
  ret         ;BX tiene el valor final
atoi endp

; =============== Convertir numero a cadena ===============
; Parametros
; ax: valor
; bx: donde guardar la cadena final
; Retorna
; cadena
itoa proc NEAR
  xor cx,cx  ;CX = 0

  itoa_1:
  cmp ax,0   ; El ciclo itoa_1 extrae los digitos del
  je itoa_2  ; menos al mas significativo de AX y los
             ; guarda en el stack. Al finalizar el 
  xor dx,dx  ; ciclo el digito mas significativo esta
  push bx    ; arriba del stack.
  mov bx,10  ; CX contiene el numero de digitos
  div bx
  pop bx
  push dx
  inc cx
  jmp itoa_1

  itoa_2:
  cmp cx,0    ; Esta seccion maneja el caso cuando
  ja itoa_3   ; el numero a convertir (AX) es 0.
  mov ax,'0'  ; En este caso, el ciclo anterior
  mov [bx],ax ; no guarda valores en el stack y
  inc bx      ; CX tiene el valor 0
  jmp itoa_4

  itoa_3:
  pop ax      ; Extraemos los numero del stack
  add ax,30h  ; lo pasamos a su valor ascii
  mov [bx],ax ; lo guardamos en la cadena final
  inc bx
  loop itoa_3

  itoa_4:
  mov ax,'$'  ; terminar cadena con '$' para 
  mov [bx],ax ; imprimirla con la INT21h/AH=9
  ret
itoa endp

end

Y el archivo de ejemplo que usa las funciones es el siguiente (ejem01.asm):

.model SMALL
.STACK 128
INCLUDE libcad.inc
.DATA
  cadena1 db '123$'
  cadena2 db '444$'
  cadena3 db 6 DUP(?)
  op1 dw ?
  op2 dw ?

.CODE
  .STARTUP
  mov ax,@data
  mov ds,ax

main proc
  ; SI parametro
  mov si, offset cadena1
  call atoi
  mov op1,bx
   
  ; SI parametro
  mov si, offset cadena2
  call atoi
  mov op2,bx
   
  ; sumar
  mov ax, op1
  add ax, op2
  mov bx, offset cadena3
  call itoa
   
  mov dx, offset cadena3
  call desplegar
   
  ; INT 21h / AH=4Ch retorna el control al sistema operativo
  ;                  termina el programa
  salir:
  mov ax,4c00h
  int 21h
  ret
main endp

; ============ Proc: Desplegar mensaje ====================
; Parametros
; dx: offset de cadena terminada por $ con respecto a DS
desplegar proc
   ; INT 21h / AH=9 - despliega cadena apuntada por DS:DX. 
   ; la cadena debe estar terminada por '$'.
   mov ah,09h
   ;mov dx, offset cad
   int 21h
   ret
desplegar endp

end

Para ensamblar y ligar estos archivos basta con invocar

ml EJEM01.ASM LIBCAD.ASM

sábado, 12 de mayo de 2012

Ensamblador 8086 Desplegar una imagen BMP

En el post anterior hablamos acerca del formato BMP que contiene una paleta de colores. Ahora veremos cómo podemos desplegar en pantalla una imagen de este tipo.

A continuación mostraremos el código que es responsable de desplegar la imagen BMP en pantalla utilizando el modo gráfico 13h, la memoria de gráficos y la paleta de colores, pero primero haremos una descripción general de cómo está estructurado este código.

Lo primero que encontramos son las definiciones de las variables que contienen información acerca de la imagen, los mensajes de error y la ruta donde se encuentra guardada la imagen. En el código encontramos varios procedimientos y el primero de ellos es InitVid que inicializa el modo de video y se asegura de que el segmento ES apunte a la memoria de video para que más adelante se pueda desplegar la imagen en la pantalla.

El procedimiento principal se llama mostrar_bmp. La llamada a este procedimiento es lo único que debemos realizar para desplegar la imagen. Dentro de mostrar_bmp encontramos las llamadas correspondientes a procedimientos auxiliares que desempeñan distintas tareas, como por ejemplo: abrir el archivo, leer el encabezado de la imagen, validar el formato BMP, leer la paleta, cargar y desplegar el BMP.

El código correspondiente a cada uno de estos procedimientos auxiliares así como sus descripciones se encuentra a continuación de la definición del procedimiento mostrar_bmp.

Además, en el siguiente link podrás descargar la imagen BMP que usamos para las pruebas.

; This program is free software: you can redistribute it 
; and/or modify it under the terms of the LGNU Lesser 
; General Public License as published by the Free Software 
; Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be 
; useful, but WITHOUT ANY WARRANTY; without even the 
; implied warranty of MERCHANTABILITY or FITNESS FOR A 
; PARTICULAR PURPOSE.  See the LGNU Lesser General Public 
; License for more details.
;
; You should have received a copy of the LGNU Lesser 
; General Public License along with this program.  If not, 
; see .
 
; Programa para desplegar una imagen tipo bitmap (mibmp.asm)

; El procedimiento mostrar_bmp carga un archivo tipo BMP 
; de Windows y la despliega en la pantalla. Recibe los 
; siguientes parametros: DS:DX que apunta a una cadena tipo 
; ASCIIZ la cual contiene la ruta donde esta guardado el 
; archivo BMP. La resolucion maxima en modo grafico es de 
; 320x200, con 256 colores disponibles. Este codigo esta 
; adaptado del codigo de Diego Escala, Miami, Florida
; y puede encontrarse en: 
; http://www.ece.msstate.edu/~reese/EE3724/labs/lab9/bitmap.asm

.model  small
.stack 128

.data
Header          label word
HeadBuff        db 54 dup('H')
palBuff         db 1024 dup('P')
ScrLine         db 320 dup(0)

BMPStart        db 'BM'

PalSize         dw ?
BMPHeight       dw ?
BMPWidth        dw ?

msgInvBMP       db "Archivo BMP invalido.",7,0Dh,0Ah,24h
msgFileErr      db "Error al abrir archivo.",7,0Dh,0Ah,24h
; El nombre del archivo debe terminar en 0
ruta            db "imagen.bmp",0

.code
;.startup
  mov ax,@data
  mov ds,ax
main proc
  ; Configurar modo grafico VGA con resolucion de 320x200
  call    InitVid
  mov dx, offset ruta
  call mostrar_bmp

  ; Esperar por cualquier tecla
  mov ah,10h
  int 16h

  ; Regresar a modo texto
  mov ax,0003h
  int 10h
  
  ; Finalizar el programa
  mov ax,4c00h
  int 21h
  ret
main endp


mostrar_bmp proc
  ; Guardar los registros en la pila
  PUSH AX
  PUSH CX
  PUSH DX
  PUSH BX
  PUSH SP
  PUSH BP
  PUSH SI
  PUSH DI
  
  ; Abrir el archivo que se encuentra en la direccion 
  ; a la que esta apuntando DS:DX
  call    abrir_lectura           
  ; Error? Desplegar mensaje de error y salir
  jc      FileErr
  ; Colocar el manejador del archivo en BX
  mov     bx,ax
  ; Leer el encabezado de 54 bytes que contiene la 
  ; informacion del archivo BMP
  call    ReadHeader
  ; No es un archivo BMP valido? Desplegar mensaje de error
  ; y salir
  jc      InvalidBMP
  ; Leer la paleta del BMP y colocarla en el buffer
  call    ReadPal
  push    es
  ; Cargar la imagen y desplegarla
  call    LoadBMP
  pop     es

  ; Cerrar el archivo
  call    cerrar_archivo
  jmp     ProcDone

  FileErr:
  mov     ah,9
  mov     dx,offset msgFileErr
  int     21h
  jmp     ProcDone

  InvalidBMP:
  mov     ah,9
  mov     dx,offset msgInvBMP
  int     21h

  ProcDone:
  ; Restaurar los registros
  POP DI
  POP SI
  POP BP
  POP SP
  POP BX
  POP DX
  POP CX
  POP AX
  
  ret
mostrar_bmp endp

; =========================================================
InitVid proc
  ; Este procedimiento inicializa el modo de video y hace
  ; que el registro ES apunte a la memoria de video.
  mov     ax,13h
  ; Configurar el modo de video para: 320x200x256.
  int     10h
  mov ax, 0A000h
  ; ES = A000h (apunta al segmento de memoria de video)
  mov es, ax
  ret
InitVid endp


; ============= Abrir archivo en modo de lectura ==========
; Parametros
; DS:DX: direccion del nombre del archivo
; Retorna
; AX: si puede abrirlo contiene el manejador del archivo
abrir_lectura proc
  ; INT 21h / AH= 3Dh - abrir un archivo existente.
  ;   AL = Servicios:
  ;     mov al, 0   ; leer
  ;     mov al, 1   ; escribir
  ;     mov al, 2   ; leer/escribir 
  ;   DS:DX -> Nombre del archivo con formato ASCIZ.
  ;   Retorna:
  ;     CF Carry Flag en cero (operacion exitosa)
  ;     con AX = manejador de archivo.
  ;     CF Carry Flag en 1 (operacion fallida)
  ;     con AX = codigo de error.
  ;  nota 1: el apuntador apunta al inicio del archivo.
  ;  nota 2: el archivo debe existir.
  MOV ax,3D00h
  INT 21h
  ret
abrir_lectura endp

cerrar_archivo proc
  MOV ah,3eh     ;Peticion para salir
  INT 21h
  ret
cerrar_archivo endp


; =============== Leer Encabezado del BMP =================
; Este procedimiento revisa el encabezado del archivo para 
; asegurarse de que es un archivo tipo BMP valido
; y obtiene la informacion necesaria del mismo.
ReadHeader proc
  mov     ah,3fh
  mov     cx,54
  mov     dx,offset Header
  ; Lee encabezado y lo guarda en el buffer.
  int     21h
  ; Si no se leyeron los 54 bytes del encabezado, terminar.
  jc      RHdone

  ; Este procedimiento obtiene informacion importante del 
  ; encabezado del BMP y la coloca en las variables
  ; adecuadas
  ; AX = Desplazamiento de la direccion donde comienza
  ;      la imagen
  mov     ax,header[0Ah]
  ; Restar la longitud del encabezado de la imagen
  sub     ax,54
  ; Dividir el resultado entre 4
  shr ax,1
  shr ax,1
  ; Obtener el numero de colores del BMP
  ; (cada elemento de la paleta tiene 4 bytes de longitud)
  mov     PalSize,ax
  ; Guardar el ancho del BMP en BMPWidth
  mov     ax,header[12h]
  mov     BMPWidth,ax             
  ; Guardar la altura del BMP en BMPHeight
  mov     ax,header[16h]    
  mov     BMPHeight,ax 

  RHdone:
  ret
ReadHeader endp


; ================ Leer la paleta de video ================
ReadPal proc
  mov     ah,3fh
  ; Numero de colores de la paleta en CX
  mov     cx,PalSize
  ; Multiplicar este numero por 4 para obtener tamanio de 
  ; la paleta en bytes
  shl     cx,1
  shl     cx,1
  mov     dx,offset palBuff
  ; Poner la paleta en el buffer
  int     21h

  ; Este procedimiento recorre el buffer de la paleta y 
  ; envia informacion de la paleta a los registros de 
  ; video. Se envia un byte a traves del puerto 3C8h que 
  ; contiene el primer indice a modificar de la paleta  
  ; de colores. Despues, se envia informacion acerca de 
  ; los colores (RGB) a traves del puerto 3C9h
  
  ; SI apunta al buffer que contiene a la paleta.
  mov     si,offset palBuff
  ; Numero de colores a enviar en CX
  mov     cx,PalSize
  mov     dx,3c8h
  ; Comenzar en el color 0
  mov     al,0
  out     dx,al
  ; DX = 3C9h
  inc     dx
  sndLoop:
  ; Nota: los colores en un archivo BMP se guardan como
  ; BGR y no como RGB

  ; Obtener el valor para el rojo
  mov     al,[si+2]
  ; El maximo es 255, pero el modo de video solamente
  ; permite valores hasta 63, por lo tanto dividimos 
  ; entre 4 para obtener un valor valido
  shr     al,1
  shr     al,1
  ; Mandar el valor del rojo por el puerto 3C9h
  out     dx,al
  ; Obtener el valor para el verde
  mov     al,[si+1]
  shr     al,1
  shr     al,1
  ; Mandar el valor del verde por el puerto
  out     dx,al
  ; Obtener el valor para el azul
  mov     al,[si]
  shr     al,1
  shr     al,1
  ; Enviarlo por el puerto
  out     dx,al

  ; Apuntar al siguiente color
  ; (Hay un caracter nulo al final de cada color)
  add     si,4
  loop    sndLoop
ret
ReadPal endp


; ====================== Cargar BMP =======================
LoadBMP proc
  ; Las imagenes BMP se guardan de cabeza. Este 
  ; procedimiento lee la imagen linea por linea, y la
  ; despliega comenzando con la linea inferior hasta la 
  ; superior. La esquina superior izquierda de la imagen 
  ; se pinta en la esquina superior de la pantalla.

  ; La memoria de video es un arreglo bidimensional de 
  ; bytes que se pueden manipular individualmente. Cada 
  ; byte representa un pixel en la pantalla y contiene 
  ; el color del pixel que se va a pintar en esa posicion

  ; Numero de lineas a desplegar
  mov     cx,BMPHeight
  ShowLoop:
  push    cx
  ; Hacer una copia de CX en DI
  mov     di,cx
  ; Multiplicar CX por 64
  shl     cx,1
  shl     cx,1
  shl     cx,1
  shl     cx,1
  shl     cx,1
  shl     cx,1
  ; Multiplicar DI por 256
  shl     di,1
  shl     di,1
  shl     di,1
  shl     di,1
  shl     di,1
  shl     di,1
  shl     di,1
  shl     di,1
  ; Todo esto es equivalente a DI = CX * 320
  ; DI apunta al primer pixel de la linea deseada en 
  ; la pantalla
  add     di,cx
  
  ; Copia del archivo bmp una linea de la imagen en un
  ; buffer temporal (SrcLine).
  mov     ah,3fh
  mov     cx,BMPWidth
  mov     dx,offset ScrLine
  ; Colocar una linea de la imagen en el buffer
  int     21h

  ; Limpiar la bandera de direccion (direction flag) para
  ; usar movsb
  cld
  mov     cx,BMPWidth
  mov     si,offset ScrLine
  ; Instruccion movsb: 
  ; Mover el byte en la direccion DS:SI a la direccion 
  ; ES:DI. Despues de esto, los registros SI y DI se 
  ; incrementan o decrementan automaticamente de acuerdo
  ; con el valor que contiene la bandera de direccion
  ; (DF). Si la bandera DF esta puesta en 0, los 
  ; registros SI y DI se incrementan en 1. Si la bandera 
  ; DF esta puesta en 1, los registros SI y DI se
  ; decrementan en 1.
  
  ; Instruccion rep: repite cx times una operacion
  ; Copiar en la pantalla la linea que esta en el buffer
  rep     movsb

  pop     cx
  loop    ShowLoop
  ret
LoadBMP endp
end

martes, 10 de abril de 2012

Ensamblador 8086 Formato BMP

Hasta el momento hemos hablado del modo 13h, la memoria de gráficos y la paleta de colores. En este post veremos cómo está compuesto el formato BMP. En particular hablaremos del formato que contiene una paleta de colores, lo cual lo hace ideal para poder desplegarlo en el modo 13h.

El formato BMP con paleta de colores tiene 3 secciones principales:

  1. Encabezado fijo de 54 bytes: donde podemos encontrar información como el ancho y el alto de la imagen, y la dirección donde empieza la información de cada pixel de la imagen (sección 3).
  2. Paleta de colores: la cual puede variar en tamaño. Cada entrada tiene 4 bytes en el siguiente orden: azul (B), verde (G), rojo (R), byte reservado. Para calcular el número de elementos de la paleta tomamos la dirección donde inicia la sección 3 (información de cada pixel) y restamos los 54 bytes del encabezado fijo. Esto nos da el tamaño en bytes de la paleta de colores, simplemente dividimos este número entre cuatro y obtenemos el número de entradas o colores de la paleta.
  3. Información de cada pixel de la imagen.

Usaremos la siguiente imagen y la abriremos con un visualizador de datos en hexadecimal (GHex) para analizar algunos puntos importantes de su contenido.

Encabezado Fijo

Los valores numéricos están guardados en formato little endian, es decir el byte menos significativo está en la dirección menos significativa. Si tenemos el número 36 9A, lo leeremos 9A36.

  • 42 4D: son los valores ASCII de BM, los archivos en formato BMP empiezan con estos 2 bytes.
  • 00 00 9A 36: la imagen mide 39,478 bytes.
  • 00 00 04 36: donde empiezan los datos de cada pixel, es decir en el byte 1078 del archivo.
  • 00 00 00 28: hasta este punto van 14 bytes del encabezado y faltan 40 bytes más (28h), que dan los 54 bytes del encabezado fijo.
  • 00 00 00 F0: ancho de la imagen (240 pixeles).
  • 00 00 00 A0: alto de la imagen (160 pixeles).
  • 00 00 96 00: número de bytes de la imagen (38,400 = 240 * 160)

Si los datos de cada pixel inician en el byte 1078 y el encabezado fijo mide 54 bytes, entonces la paleta de colores mide 1024 bytes; es decir tiene 256 entradas.

Pixeles y Paleta de Colores

Ahora realizaremos un ejercicio para mostrar que leer los colores de la imagen es prácticamente equivalente a la explicación que vimos para el modo 13h.

  1. Tomamos la información de un pixel: el primer pixel inicia en 1078 (436h) y contiene 37h. Esta es la entrada de la paleta de donde tomaremos el color.



  2. La paleta de colores inicia en 36h, por lo que la entrada 37h esta en: 36h + (37h * 4) = 112h.



  3. Si ponemos el código de color en un editor de imágenes podemos ver que corresponde al color del fondo de la imagen.

El valor 37h aparece repetidamente al inicio del archivo ya que es el valor del fondo de la imagen. Ahora, busquemos el primer valor diferente y veamos su color.

  1. El primer valor encontrado es 71h.



  2. La entrada de la paleta que buscamos esta en: 36h + (71h * 4) = 1FAh.



  3. Nuestra intuición diría que el color debe ser rojo; sin embargo, podemos ver que el color es verde, y corresponde al cuadro de la parte inferior. Esto se debe a que en el formato BMP, las líneas se guardan de abajo hacia arriba con respecto a como se deben mostrar en la pantalla. Es decir, la primer línea de datos del archivo es la línea inferior de la imagen.

Muy bien, ya tenemos todos los elementos para poder desplegar una imagen BMP en ensamblador 8086 modo 13h, en el siguiente post veremos el código.

La siguiente página tiene información adicional acerca del modo 13h y del formato BMP.

domingo, 8 de abril de 2012

Ensamblador 8086 Modo 13h Paleta de Colores

En post anterior explicamos que para desplegar pixeles en la pantalla usando el modo 13h tenemos que escribir a memoria y configurar la paleta de colores. Sin embargo, el ejemplo sólo mostró como escribir a la memoria de gráficos y usamos la paleta default de colores. En este post realizaremos un pequeño experimento para mostrar cómo configurar la paleta de colores.

El siguiente programa pinta 10 franjas horizontales de 20 pixeles de ancho cada una. Recordemos que hay 64,000 pixeles en la pantalla (320x200), por lo que cada franja de 20 pixeles de ancho ocupa un 10% de la pantalla es decir 6,400 pixeles. La primera parte del programa llena el área de memoria de gráficos (que inicia en A000:0000) con 6,400 ceros, seguidos de 6,400 unos, seguidos de 6,400 dos, y así sucesivamente hasta nueve.

Los colores que vemos en la pantalla son los colores de las primeras diez entradas de la paleta default de colores.

Después de presionar una tecla, el programa configura en tonos de azules las primeras diez entradas de la paleta de colores. Podemos observar los resultados en la siguiente imagen. Es importante recalcar que NO modificamos el área de memoria de gráficos, la cual sigue teniendo diez conjuntos de 6,400 pixeles numerados del cero al nueve.

Por último, volvemos a modificar las primeras diez entradas de la paleta de colores pero esta vez con tonos de verde.

.model small
.stack 128
.data

.code
.startup
main proc
  ; INT 10h / AH = 0 - configurar modo de video.
  ; AL = modo de video deseado.
  ;     00h - modo texto. 40x25. 16 colores. 8 paginas.
  ;     03h - modo texto. 80x25. 16 colores. 8 paginas.
  ;     13h - modo grafico. 40x25. 256 colores. 
  ;           320x200 pixeles. 1 pagina. 
  mov ax,0013h
  int 10h
  mov ax, 0A000h
  mov ds, ax  ; DS = A000h (memoria de graficos).

  ; ============== Franjas horizontales ===================
  ; Pintar 10 franjas horizontales de 20 pixeles de ancho
  ; cada una. La primer franja usara el indice 0 de la 
  ; paleta de colores, y asi sucesivamente. 
  ; Es decir, tenemos que pintar toda la pantalla 64,000
  ; pixeles, pero cada 6,400 pixeles cambiamos el color.
  mov cx,0FA00h ; todos los pixeles de la pantalla
  xor dx,dx ; color para cada franja
  xor bx,bx ; contador de pixeles por franja
  xor di,di

  ciclo_1:
  mov [di], dx ; poner color en A000:DI
  inc di
  inc bx
  cmp bx,6400
  jne sig_pix1
            ; nueva franja
  xor bx,bx ; resetear contador de pixeles por franja
  inc dx    ; cambiar color
  sig_pix1:
  loop ciclo_1

  ; esperar por tecla
  mov ah,10h
  int 16h
  
  ; ======= Cambiar paleta de colores a tonos de azul =====
  mov dx,3c8h ; Empezamos a modificar la paleta de colores
  mov al,0    ; desde la entrada 0
  out dx,al
  inc dx      ; DX = 3C9h.
  mov cx,10

  ciclo_2:   ; Modificar 10 entradas de la paleta
  mov al,0   ; Rojo.
  out dx,al
  mov al,0   ; Verde
  out dx,al
  mov al,6   ; Azul
  mul cl
  out dx,al
  loop ciclo_2

  ; esperar por tecla
  mov ah,10h
  int 16h

  ; ======= Cambiar paleta de colores a tonos de verde =====
  mov dx,3c8h ; Empezamos a modificar la paleta de colores
  mov al,0    ; desde la entrada 0
  out dx,al
  inc dx      ; DX = 3C9h.
  mov cx,10

  ciclo_3:   ; Modificar 10 entradas de la paleta
  mov al,0   ; Rojo.
  out dx,al
  mov al,6   ; Verde
  mul cl
  out dx,al
  mov al,0   ; Azul
  out dx,al
  loop ciclo_3

  ; esperar por tecla
  mov ah,10h
  int 16h

  ; regresar a modo texto
  mov ax,0003h
  int 10h
  
  ; finalizar el programa
  mov ax,4c00h
  int 21h
  ret
main endp
end

jueves, 5 de abril de 2012

Ensamblador 8086 Modo 13h Memoria para Gráficos

El programa del post anterior pinta pixeles usando la interrupción 10h servicio 0Ch; podríamos pensar que estamos mandando la instrucción de pintar un pixel directamente a la tarjeta de video. Sin embargo, lo que realmente estamos haciendo es escribir al área de memoria de gráficos que inicia en A000:0000.

Esta área consta de 320x200 bytes (64,000 bytes) y cada byte corresponde a un pixel. Para cada byte del área de memoria de gráficos, la tarjeta de video lee el byte y usa ese byte como índice en la paleta de colores para obtener el color del pixel.

En la pantalla, el pixel superior-izquierdo (fila 0, columna 0) corresponde al byte en la dirección A000:0000, el pixel superior-derecho (fila 0, columna 319) corresponde a A000:013F, el primer pixel de la segunda fila (fila 1, columna 0) está mapeado a la dirección A000:0140. En general, el pixel en la fila i, columna j corresponde a A000:(140h*i+j).

La siguiente imagen muestra cómo interactuamos con el área de memoria y la paleta de colores; la tarjeta de video usa esta información para desplegar el contenido de la pantalla.

El siguiente programa muestra la misma funcionalidad del programa del post anterior, pero esta vez escribiendo directamente al área de memoria de gráficos.

.model small
.stack 128
.data

.code
.startup
main proc
  ; INT 10h / AH = 0 - configurar modo de video.
  ; AL = modo de video deseado.
  ;     00h - modo texto. 40x25. 16 colores. 8 paginas.
  ;     03h - modo texto. 80x25. 16 colores. 8 paginas.
  ;     13h - modo grafico. 40x25. 256 colores. 
  ;           320x200 pixeles. 1 pagina. 
  mov ax,0013h
  int 10h
  mov ax, 0A000h
  mov ds, ax  ; DS = A000h (memoria de graficos).

  ; ============== Lineas verticales ======================
  ; Queremos pintar 256 colummas, cada una con un alto de 
  ; 200 pixeles. Podemos ejecutar 51,200 ciclos.
  ; Como la memoria de graficos es lineal, es mejor pintar
  ; una fila a la vez, cada fila tiene 320 columnas, pero
  ; solo pintamos 256. Al llegar a la columna 256 saltamos
  ; a la siguiente fila sumando 320-256 = 64
  ; Cada pixel cambia de color para dar el efecto de lineas
  ; verticales
  mov cx,0C800h ; # de pixeles
  xor dx,dx     ; contador de columnas y color
  xor di,di

  ciclo_1:
  mov [di], dx ; poner color en A000:DI
  inc di
  inc dx
  cmp dx,256
  jne sig_pix1

  add di,0040h ; saltar al inicio de siguiente fila
  xor dx,dx    ; reiniciar columnas y color
  sig_pix1:
  loop ciclo_1

  ; esperar por tecla
  mov ah,10h
  int 16h

  ; ============== Lineas horizontales ======================
  ; Queremos pintar 200 filas, cada una con un largo de 
  ; 320 pixeles. Podemos ejecutar 64,000 ciclos.
  ; Despues de pintar los 320 pixeles correspondientes a
  ; una fila, cambiamos el color para pintar la siguiente
  ; fila y dar el efecto de lineas horizontales.
  mov cx,0FA00h ; todos los pixeles de la pantalla
  xor dx,dx ; color para cada fila
  xor bx,bx ; contador de columnas
  xor di,di

  ciclo_2:
  mov [di], dx ; poner color en A000:DI
  inc di
  inc bx
  cmp bx,320
  jne sig_pix2
            ; nueva fila
  xor bx,bx ; resetear contador de columnas
  inc dx    ; cambiar color
  sig_pix2:
  loop ciclo_2

  ; esperar por tecla
  mov ah,10h
  int 16h

  ; regresar a modo texto
  mov ax,0003h
  int 10h
  
  ; finalizar el programa
  mov ax,4c00h
  int 21h
  ret
main endp
end

martes, 3 de abril de 2012

Ensamblador 8086 Modo Gráfico 13h

El modo gráfico 13h nos permite manejar la pantalla como una matriz de 320 píxeles de ancho por 200 píxeles de alto. Cada píxel puede tomar uno de 256 colores, estos colores están definidos en una paleta de colores la cual podemos configurar.

El siguiente programa despliega primero líneas verticales, espera por una tecla, despliega líneas horizontales y espera por otra tecla. Los colores mostrados son los de la paleta default de colores. Usamos la interrupción 10h, servicio 0Ch para modificar los píxeles; el parámetro AL más que especificar directamente el color, indica la entrada de la paleta de colores que se debe usar para el píxel ubicado en la fila DX y la columna CX.

En el siguiente post veremos con más detalle la paleta de colores y cómo podemos pintar en pantalla mediante escrituras a memoria en lugar de usar la interrupción 10h.

La siguiente entrada en Wikipedia muestra la paleta default de colores que corresponde a los colores mostrados por el programa.

.model small
.stack 128
.data

.code
.startup
main proc
  ; INT 10h / AH = 0 - configurar modo de video.
  ; AL = modo de video deseado.
  ;     00h - modo texto. 40x25. 16 colores. 8 paginas.
  ;     03h - modo texto. 80x25. 16 colores. 8 paginas.
  ;     13h - modo grafico. 40x25. 256 colores. 
  ;           320x200 pixeles. 1 pagina. 
  mov ax,0013h
  int 10h

  ; ============== Lineas verticales ======================
  ; INT 10h / AH = 0Ch - cambiar color de un pixel.
  ;   AL = color
  ;   CX = columna
  ;   DX = fila

  ; for CX=0 to 256
  ;   for DX=0 to 200
  ;     Poner pixel (DX,CX) con color CX
  mov cx,0000h
  mov dx,0000h

  ciclo_1:
  mov ah,0ch
  mov al,cl
  int 10h
  inc dx
  cmp dx,200
  jne ciclo_1

  mov dx,0000h
  inc cx
  cmp cx,256
  jne ciclo_1

  ; esperar por tecla
  mov ah,10h
  int 16h

  ; ============== Lineas horizontales ======================
  ; INT 10h / AH = 0Ch - cambiar color de un pixel.
  ;   AL = color
  ;   CX = columna
  ;   DX = fila

  ; for DX=0 to 200
  ;   for CX=0 to 320
  ;     Poner pixel (DX,CX) con color DX
  mov cx,0000h
  mov dx,0000h

  ciclo_2:
  mov ah,0ch
  mov al,dl
  int 10h
  inc cx
  cmp cx,320
  jne ciclo_2
  
  mov cx,0000h
  inc dx
  cmp dx,200
  jne ciclo_2

  ; esperar por tecla
  mov ah,10h
  int 16h

  ; regresar a modo texto
  mov ax,0003h
  int 10h
  
  ; finalizar el programa
  mov ax,4c00h
  int 21h
  ret
main endp
end

domingo, 1 de abril de 2012

Ensamblador 8086 itoa

Una vez entendiendo la función atoi del post anterior, decidí implementar la función que realiza la operación inversa. Ahora la entrada es un valor entero y lo debemos transformar en cadena. El pseudo-código se muestra a continuación:

1 temp = 0
2 indice = 0
3 while entero > 0
4   residuo = entero modulo 10
5   entero = entero div 10
6   cadena[indice] = residuo + 30h
7   indice = indice + 1
8 end while

La idea es ir extrayendo los dígitos (empezando por el menos significativo) e ir guardándolos en la cadena, noten que sumamos 30h para guardar el valor ascii que representa el dígito. Sin embargo, esta implementación genera una cadena al revés, es decir, si entero inicia con valor de 1234, la cadena final contendrá “4321”. Por lo que es necesario invertir esta cadena.

Pero es posible evitar invertir la cadena al final, para ello usaremos el stack (una estructura lifo – last in first out).

01 temp = 0
02 indice = 0
03 ndigitos = 0
04 while entero > 0
05   temp = entero modulo 10
06   entero = entero div 10
07   push temp
08   ndigitos = ndigitos + 1
09 end while
10 while ndigitos > 0
11   pop temp
12   cadena[indice] = temp + 30h
13   ndigitos = ndigitos – 1
14   indice = indice + 1
15 end while

El primer ciclo (línea 04-09) extrae los dígitos y los guarda en el stack, al final del ciclo el dígito más significativo se encuentran arriba del stack. El segundo ciclo (línea 10-15) extrae los dígitos del stack y los guarda en la cadena final.

El siguiente programa convierte dos cadenas a enteros usando atoi, los suma y convierte el resultado a cadena usando itoa para poder desplegarlo.

.model SMALL
.STACK 128
.DATA
  cadena1 db '411$'
  cadena2 db '144$'
  cadena3 db 6 DUP(?)
  op1 dw ?
  op2 dw ?

.CODE
  .STARTUP
  mov ax,@data
  mov ds,ax

main proc
  ; SI parametro
  mov si, offset cadena1
  call atoi
  mov op1,bx
   
  ; SI parametro
  mov si, offset cadena2
  call atoi
  mov op2,bx
   
  ; sumar
  mov ax, op1
  add ax, op2
  mov bx, offset cadena3
  call itoa
   
  mov dx, offset cadena3
  call desplegar
   
  ; INT 21h / AH=4Ch retorna el control al sistema operativo
  ;                  termina el programa
  salir:
  mov ax,4c00h
  int 21h
  ret
main endp

; ============ Proc: Desplegar mensaje ====================
; Parametros
; dx: offset de cadena terminada por $ con respecto a DS
desplegar proc
   ; INT 21h / AH=9 - despliega cadena apuntada por DS:DX. 
   ; la cadena debe estar terminada por '$'.
   mov ah,09h
   ;mov dx, offset cad
   int 21h
   ret
desplegar endp

; ========= Convertir cadena a numero =====================
; Parametros
; si: offset inicial de la cadena con respecto a DS
; Retorna
; bx: valor
atoi proc
  xor bx,bx   ;BX = 0

  atoi_1:
  lodsb       ;carga byte apuntado por SI en AL
              ;e incrementa si
  cmp al,'0'  ;es numero ascii? [0-9]
  jb noascii  ;no, salir
  cmp al,'9'
  ja noascii  ;no, salir

  sub al,30h  ;ascii '0'=30h, ascii '1'=31h...etc.
  cbw         ;byte a word
  push ax
  mov ax,bx   ;BX tendra el valor final
  mov cx,10
  mul cx      ;AX=AX*10
  mov bx,ax
  pop ax
  add bx,ax
  jmp atoi_1  ;seguir mientras SI apunte a un numero ascii
  noascii:
  ret         ;BX tiene el valor final
atoi endp

; =============== Convertir numero a cadena ===============
; Parametros
; ax: valor
; bx: donde guardar la cadena final
; Retorna
; cadena
itoa proc
  xor cx,cx  ;CX = 0

  itoa_1:
  cmp ax,0   ; El ciclo itoa_1 extrae los digitos del
  je itoa_2  ; menos al mas significativo de AX y los
             ; guarda en el stack. Al finalizar el 
  xor dx,dx  ; ciclo el digito mas significativo esta
  push bx    ; arriba del stack.
  mov bx,10  ; CX contiene el numero de digitos
  div bx
  pop bx
  push dx
  inc cx
  jmp itoa_1

  itoa_2:
  cmp cx,0    ; Esta seccion maneja el caso cuando
  ja itoa_3   ; el numero a convertir (AX) es 0.
  mov ax,'0'  ; En este caso, el ciclo anterior
  mov [bx],ax ; no guarda valores en el stack y
  inc bx      ; CX tiene el valor 0
  jmp itoa_4

  itoa_3:
  pop ax      ; Extraemos los numero del stack
  add ax,30h  ; lo pasamos a su valor ascii
  mov [bx],ax ; lo guardamos en la cadena final
  inc bx
  loop itoa_3

  itoa_4:
  mov ax,'$'  ; terminar cadena con '$' para 
  mov [bx],ax ; imprimirla con la INT21h/AH=9
  ret
itoa endp

end

lunes, 26 de marzo de 2012

Ensamblador 8086 atoi

Para implementar la tarea donde tuvimos que leer del usuario dos números enteros, realizar operaciones con ellos y desplegar el resultado, fue necesario implementar dos rutinas muy interesantes: itoa (para convertir un entero a cadena) y atoi (para convertir una cadena a entero). Los nombres de estas dos rutinas los tomé de las funciones en C que ayudan a realizar estás tareas, entero a cadena (integer to ascii por sus siglas en inglés) y cadena a entero (ascii to integer). En este post analizaremos la implementación de atoi.

El objetivo de esta función es tomar una cadena y convertirla a su valor entero. Por ejemplo la cadena “120” debe generar el entero 120. El pseudo-código para esta función es el siguiente:

1 entero = 0
2 temp = 0
3 indice = 0
4 while cadena[indice] es un digito
5   temp = convertir cadena[indice] a digito
6   entero = entero * 10
7   entero = entero + temp
8   indice = indice + 1
9 end while

Recuerden que en ascii los caracteres '0' al '9' se encuentran de manera consecutiva, de hecho el valor númerico de '0' es 30h y el de '9' es 39h. Por lo que el paso 4 del código anterior se puede implementar fácilmente verificando si cadena[indice] se encuentra entre '0' y '9'. De la misma forma, en el paso 5, sólo basta con restar el valor de '0' a cadena[indice] para obtener el valor del dígito en esa posición. El siguiente post muestra el código en ensamblador para esta función (sólo tuve que cambiar los registros de 32 a 16 bits, por ejemplo de eax a ax).

La siguiente función muestra la versión de 16 bits de atoi, la cadena a convertir debe iniciar en DS:SI y el valor final lo regresa en BX.

; ========= Convertir cadena a numero ===========
; Parametros
; si: offset inicial de la cadena con respecto a DS
; Retorna
; bx: valor
atoi proc
  xor bx,bx   ;BX = 0

atoi_1:
  lodsb       ;carga byte apuntado por SI en AL
              ;e incrementa si
  cmp al,'0'  ;es numero ascii? [0-9]
  jb noascii  ;no, salir
  cmp al,'9'
  ja noascii  ;no, salir

  sub al,30h  ;ascii '0'=30h, ascii '1'=31h...etc.
  cbw         ;byte a word
  push ax
  mov ax,bx   ;BX tendra el valor final
  mov cx,10
  mul cx      ;AX=AX*10
  mov bx,ax
  pop ax
  add bx,ax
  jmp atoi_1  ;seguir mientras SI apunte a un numero ascii
  noascii:
  ret         ;BX tiene el valor final
atoi endp

lunes, 19 de marzo de 2012

Ensamblador 8086 en Ubuntu

En la clase de lenguaje ensamblador hemos estado usando un usb para iniciar en modo DOS y poder ejecutar nuestros programas de 16 bits. En este post, mostraré cómo realizar pruebas de nuestros programas en Ubuntu. Sin embargo, es importante probar nuestros programas al final en el modo DOS nativo en caso de que haya instrucciones no compatibles o que se comporten diferente.

  1. Instalar DOSBox que emula el ambiente DOS.
    sudo apt-get update
    sudo apt-get install dosbox
  2. Copiar los archivos de nuestro usb masm/bin en una carpeta, por ejemplo: /media/common/prgrmmng/8086. Para su conveniencia el siguiente zip contiene los archivos.
  3. Abrir DOSBox (Applications > Games > DOSBox Emulator), montar la carpeta del paso anterior en la unidad c, y cambiar a la unidad c.
  4. Ya estamos listos para empezar a programar. La siguiente imagen muestra la primer tarea corriendo en DOSBox.

El siguiente post también tiene información para bajar DOSBox para windows.

Si alguien usa DOSBox, por favor compartan si encuentran alguna incompatibilidad o problema con los programas.