Programación en Lenguaje Ensamblador

-El Verdadero Lenguaje de las Máquinas-

Como Imprimir un Entero de 32 Bits en ASM

printf(«Imprimir en ASM no es tan facil como tu crees»);

El semestre en las universidades termina y con ello baja la cantidad de visitantes, dejando solo aquellos que vienen por gusto o porque realmente les interesa el ASM. Esta vez traigo un programita que convierte un valor binario de 32 bits en una cadena ASCII-Z.

entero en messagebox

Ya se lo que se han de estar preguntando los que no estan muy familiarizados con la programación en ensamblador: ¿Porque este desperdicia espacio en una tontería como esta? O peor aún ¿Y que este idiota no sabe que los números son diferentes a los “strings”?

Mientras mas avanzo en esto del Ensamblador mas me doy cuenta del daño que causa a a los programadores principiantes el empezar su aprendizaje con lenguajes de “alto nivel” (como me fastida ese término). Por todos es bien conocido el daño que hace iniciarse a programar con BASIC y todos sus descendientes y tal parece que cada nueva versión de BASIC tiene nuevas y mas novedosas maneras de echar a perder a los programadores mas jóvenes. No cabe duda de que el daño hecho por iniciarse en este tipo de lenguajes es muy dificil de remediar. Si me pusiera a enumerar todos necesitaría otras 3 entradas nada mas para el puro BASIC, pero en este caso creo que voy a blasfemar un poco.

Todos saben que como radical que soy, tengo muy poco respeto por la mayoría de los lenguajes autodenominados de “alto nivel”. Excepto por el viejo C que es el que mas se acerca al ASM. Sin embargo, para quienes se inician programando en C o alguno de sus derivados mas amistosos topan con piedra a la hora de comenzar a programar en ASM. Pues cosas que son relativamente sencillas en estos lenguajes son completamente diferentes, cuando no imposibles en ensamblador. Esta lista merece su entrada a parte pero el asunto a discutir ahora es la modesta función printf( );

printf(“La suma de %d + %d es igual a %d”,numero1, numero2, resultado);

Veamos los parámetros: el primero es una cadena de texto con signos de control que son remplazados por el contenido de las variables numéricas llamadas numero1, numero2 y resultado. Pero este proceso no es directo, primero la función debe de procesar por medio de un aceptor la secuencia de signos que se encuentran entre las comillas dobles e interpretar los signos de control como instrucciones. ¡En realidad esta sencilla cadena de caracteres puede considerarse un programa completo!. Y lo que dice es: Toma los 3 números cuyos valores te envío por el Stack, conviertelos en strings decimales e intégralos con el resto del string y luego le dices al sistema operativo que los despliegue. Eso sin mencionar la comprobación exhaustiva de tipos de las variables ni los formatos numéricos en los que debe de ser mostrado. De hecho, la leyenda de que una sola instrucción de C se convierte en cientas o miles de instrucciones en ASM es cierta sobre todo por instrucciones como printf(); y scanf();

Bueno, ahora que ya saben que un printf no es tan sencillo como parece vamos con el código:

;BIN2ASC.ASM
;Escrito por Mario Salazar el 11 de diciembre del 2009
;https://asm86.wordpress.com
;http://itzasm.ning.com
;Programita que despliega un numero de 32 bits en hexadecimal
;por medio de MessageBox. Para compilarlo con FASM solo presione
;la tecla F9. El numero solo puede ser cambiado en el codigo
;Lean el archivo bin2asc.xls para una referencia de las variables

format PE GUI
entry start

section '.code' code readable executable

  start:

			mov [eax_bin], 1234abcdh
		   ;cambia el 1234abcdh por el numero que quieras entre 0 y 2^32

			call bin2asc
		   ;llamada a la funcion que convierte ese numero en cadena ascii-z

			push	0
			push	debug_caption
			push	eax_ascii
			push	0
			call	[MessageBoxA]
		   ;mostrar una MessageBox que despliegue el numero

			push	0
			call	[ExitProcess]
		   ;terminar el programa

bin2asc:
			pusha
		    ;guardar los registros generales en el stack

			xor	eax, eax	 ;prepararse para el ciclo y
			mov	ecx, 8		 ;cargar valor binario en edx
			mov	edx,[eax_bin]
    lee_binario:
			mov	ebx, 0fh	 ;extraer los 4 bits mas bajos
			and	ebx, edx	 ;con una mascara de bits
			mov	al,  [ebx + digitos]
			push	eax		 ;usar esos 4 bits como indice
			shr	edx, 4		 ;a la tabla digitos
			loop	lee_binario	 ;por cada 8 numeros
		    ;obtener 8 simbolos ascii y guardarlos en el stack

			mov	ecx, 8
			mov	edi, eax_ascii	 ;apuntar edi a la cadena ascii
    escribe_binario:
			pop	eax
			mov	[edi], al
			inc	edi
			loop	escribe_binario
		    ;sacar los simbolos ascii del stack y
		    ;guardarlos en la cadena ascii

			popa
			ret
		    ;restablecer los registros generales y terminar proceso

section '.data' data readable writeable

	eax_bin 	  dd	   0			      ;valor binario a convertir
	debug_caption	  db	   'Hex de 32 bits:',0	;nombre de la ventana
	eax_ascii	  db	   '00000000',0 	      ;cadena de salida
	digitos 	  db	   '0123456789ABCDEF'	      ;arreglo de simbolos hexadecimales

section '.idata' import data readable writeable

  dd 0,0,0,RVA kernel_name,RVA kernel_table
  dd 0,0,0,RVA user_name,RVA user_table
  dd 0,0,0,0,0

  kernel_table:
    ExitProcess dd RVA _ExitProcess
    dd 0
  user_table:
    MessageBoxA dd RVA _MessageBoxA
    dd 0

  kernel_name db 'KERNEL32.DLL',0
  user_name db 'USER32.DLL',0

  _ExitProcess dw 0
    db 'ExitProcess',0
  _MessageBoxA dw 0
    db 'MessageBoxA',0

section '.reloc' fixups data readable discardable

En realidad la mayor parte del source pertenece al viejo PEDEMO.EXE del FASM. Pero no he “encapsulado” este “método” para que fuera mas sencillo de entender y a la vez mas dificil de lamear. Para compilarlo solo cárguen este programa en el IDE del FASMW y opriman F9. Pueden cambiar el número 1234abcdh por el que ustedes quieran ¡Incluso si lo dan en formato decimal o como una palabra de 4 letras!. Si hacen lo de las palabras con 4 letras (muchas de las cuales suelo gritarle a los lamers todo el tiempo) no olviden ponerla entre comillas dobles. No se asusten si de pronto alguna aparece al reves, se debe a algo llamado Little Endian que explicaré (de nuevo) otro día.

El programa tiene 3 elementos de datos:

eax_bin.- Es un espacio en memoria de 32 bits donde se va a guardar el dato a desplegar

eax_ascii.- Array de 9 bytes. Ocho son para los digitos y el ultimo en un cero binario que indica el fin de la cadena ASCII-Z

digitos.- Este es el mas importante. Es un array de bytes donde cada uno representa uno de los 16 digitos ascii que serán usados para formar la cadena de texto.

Ahora vamos con las instrucciones:

Antes y después de la función se guardan y restauran los valores de los registros generales con PUSHA Y POPA. Luego de guardarlos se escribe el entero de 32 bits en el registro EDX y en ECX se escribe un 8 en preparación para el ciclo de lectura que ha de repetirse 8 veces.

Hay un ciclo que lee el valor de 32 bits en porciones de 4 bits cada una, este ciclo está entre la etiqueta lee_binario: y la instrucción loop lee_binario. Para extraer los 4 bits mas bajos del registro EDX se hace un AND entre este y EBX que poco antes fue cargado con el valor 0Fh (quince decimal y 00001111 en binario) El resultado de este AND deja en EBX los 4 bits mas bajos del registro EDX y el resto los pone a cero. Esto es lo que los programadores de ASM llamamos “máscara de bits”.

Ahora viene lo interesante: Para convertir esos 4 bits en un dígito ASCII con una sola instrucción de lenguaje Ensamblador usamos EBX como offset del arreglo digitos. De modo que estos 4 bits (que solo pueden tomar valores entre 0 y 15) nos den la posición de memoria de su correspondiente dígito ASCII una vez que se sumen a la posición de memoria indicada por la etiqueta digitos. La instrucción que hace esto y guarda el resultado en la parte baja del acumulador EAX es MOV AL, [EBX + digitos]

Luego de esto, el acumulador completo se guarda en el stack con push y luego se hace un desplazamiento binario hacia la derecha de 4 bits en EDX con SHR EDX, 4. Con esto ahora los 4 bits mas bajos en EDX corresponden al siguiente dígito hexadecimal a transformar. Esto se repite 8 veces hasta que se obtienen todos los valores ASCII para formar la cadena hexadecimal de 32 bits.

La siguiente es bastante sencilla, primero se carga en ECX otro 8 para escribir los 8 digitos del hexadecimal de 32 bits. A continuación se carga en el registro EDI (Extended Destination Index) la posición de memoria de la zona donde vamos a escribir la cadena ASCII. Entonces, por cada ciclo vamos a ir sacando valores de la pila con POP EAX y escribimos los 8 bits mas bajos en la posición guardada por edi con MOV [EDI], AL e incrementamos el ‘puntero’ EDI con INC EDI para ir avanzando en la cadena.

Luego de estos 2 ciclos restauramos los registros generales con POPA y devolvemos el control al llamador con RET. Es importante siempre restaurar los registros generales en Windows, sobre todo EBX, ESI y EDI antes de que el Windows vaya a hacer cualquier cosa, de lo contario podemos desatar su ira y nos echará abajo la aplicación.

Una cosa mas, por si no lo han notado en el primer ciclo los digitos de 4 bits se leen comenzando por el menos significativo pero a la hora de escribir se comienza por el digito mas significativo. Esto es posible porque usamos el Stack. Y como todos saben de la clase de ensamblador con Solis el primer elemento en entrar a estas estructuras es también el último en salir.

Ya para terminar, es importante poder convertir enteros binarios en cadenas ASCII porque la mayor parte de los sistemas operativos (o en este caso la API del Windows) no pueden representar números binarios facilmente. Además de que un mismo número binario puede ser representado en muchas formas diferentes dependiendo del sistema numérico (decimal, hexadecimal, fraccionario, etc.) mas adelante veremos como desplegar un número binario en formato decimal. En realidad solo basta con agregar 2 instrucciones y un dato de 1 byte a este mismo código. Les aconsejo que sean organizados con sus viejos códigos y los reutilicen tanto como les sea posible porque los programas escritos en ASM pueden llegar a ser enormes en source aunque su ejecutable tan solo mida unos pocos kilobytes.

diciembre 16, 2009 - Posted by | Uncategorized | , , , , , , ,

9 comentarios »

  1. Hola mario , sobre esta convercio’n ya has escrito en otros post , puedes usar la «mia» que es mas simple y rapida (eso creo) . Para combertir a binario tengo una que termine hace poco que es bastante rapida , espera un momento y de doy el link ….

    http://foro.elhacker.net/asm/imprimir_los_bits_que_hay_en_una_direccion_de_memoria-t273429.0.html

    Lee el post completo !
    Espero tu opinion !!

    Saludos
    Desde la obscuridad del dia ..

    Comentarios por Kapetres | diciembre 18, 2009 | Responder

  2. hola amigo soy de oaxaca,mx. estoy iniciando con ensamblador y he cometido un error de iniciar con lenguales de alto nivel como lo comentas en tu blog pero ahora quiero entender el ensablador no se si me puedes ayudar estoy en 1% de ensamblador casi 0..
    gracias

    Comentarios por ezequiel lopez | enero 4, 2010 | Responder

    • Pues bienvenido al barco. Comenta lo que quieras en este blog o forma parte de la Red Social de Programadores de Ensamblador. La parte mas dificil al principio es desaprender todo lo que los lenguajes de «Alto Nivel» para poder entender el ASM.

      Considera este blog y su red social relacionada como tu nuevo cuartel general donde poder aprender ensamblador y no temas preguntar.

      Comentarios por asm86 | enero 5, 2010 | Responder

  3. Hola compañero algo complejo tu codigo..
    necesito imprimir 4 caracteres de una suma mas o menos así
    .model small
    .stack 100h
    .data
    .code

    mov ax,@data
    mov ds,ax

    mov ax,55
    add ax,50
    aam

    add ax,3030h
    mov dl,ah
    inc ah

    mov ah,2
    push ax
    int 21h

    pop ax
    mov dl,al
    inc al
    int 21h

    mov ax,4C00h
    int 21h

    end

    PERO SOLO IMPRIME DOS CARACTERES. ME PUEDES AYUDAR??
    POR CIERTO TIENES ALGUN CODIGO DE ANIMACION SIMPLE DIGAMOS
    ALGUN OBJETO EN MOVIMIENTO MUÑECO O FIGURA..
    TE LO AGRADEZCO DE ANTE MANO

    Comentarios por Nano | junio 7, 2010 | Responder

    • Normalmente no hago programas de 16 bits pero puedo darte unos pocos consejos:

      1.- Si usas registros de 16 bits solo puedes representar 2 cifras usando ascii. Intenta repetir ese mismo codigo pasandole como argumento el valor de la suma dividido por 100 con la instruccion DIV.
      2.- trata de imprimir los digitos de uno en uno en lugar de 2 en 2 para facilitar el codigo
      3.- Si ese mismo programa usara registros de 32 bits o usar DX:AX en combinacion puede que logres desplegar las 4 cifras

      Respecto a lo de la animacion sencilla lee sobre la interrupcion 10h que controla el video en modo de 16 bits, el modo 13h es particularmente sencillo de programar y puede mostrar hasta 256 colores simultaneos.

      Comentarios por asm86 | junio 7, 2010 | Responder

      • Jaja, otra vez te agarraron de hacerle la tarea. Me da la impresion que se los encargo el Solis por el @data.

        Comentarios por blackpig | junio 9, 2010

      • Seria bueno preguntarle pero por lo que dijo el Rosas el Solis no da la clase de ensamblador y en la carrera de computacion ya no se da esa materia. Pero no seria raro que todos se copiaran del mismo ejemplo que lleva publicado desde hace decada y media que menciona ese famoso @data.

        Por cierto, ¿Sabian que si en Twitter ponen @data pueden comunicarse con un japones llamado «Daisuke Tanida» ?Me pregunto si ese japones conoce al Solis.

        Comentarios por asm86 | junio 9, 2010

  4. necesito hacer un arborl con (* )y no se nada de assembler ayuda!

    Comentarios por sad | enero 10, 2012 | Responder

    • una salida facil es usar simples cadenas de texto. Busca la funcion para imprimir cadenas de texto (de seguro has de estar usando el viejo ensamblador de DOS asi que debe de ser la int 9) y luego imprimes cadenas con asteriscos y espacios en blanco. Las cadenas deben de quedar una sobre otra como renglones. Recuerda que para brincar renglon tienes que poner 13 y 10 binarios (no en cadena ascii) al final de cada renglon. Esto te da una idea:

      arbolito db "    *    ",13,10
               db "   ***   ",13,10
               db "  *****  ",13,10
               db " ******* ",13,10
               db "$",0
      

      Al final solo mandas imprimir el string a partir de la posicion de la etiqueta arbolito y asunto arreglado. Y da gracias a que todavia estoy de fiesta porque normalmente no le hago la tarea a nadie.

      Comentarios por asm86 | enero 11, 2012 | Responder


Deja un comentario