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

+++ Positivo y Negativo – – –

–Números Binarios Con y Sin Signo–

Hasta ahora, la entrada de este blog que ha sido mas leida es la de ¿Cuantos bits tiene un Byte?. En esa entrada se deja en claro lo que es un byte. Una celda de 8 bits que puede almacenar cualquier número entero entre 0 y 255.

Sin embargo, cada uno de estos valores puede ser interpretado de 2 maneras diferentes dependiendo del contexto en que se trabaje.

Números sin signo.- Estos son los de siempre. Cuenta de cero a 255.

Números con signo.- Con los mismos 8 bits de un byte, podemos representar los números desde el -128 al 127. Si se fijan, estos son 256 valores diferentes.

Todos estos conceptos son suficientemente complejos para que se les dedique un examen completo en una carrera de Ingeniería en Computación. Sin embargo, en este dibujo que recuerda a un Yin-Yang chino pueden verse la mayoría de los conceptos que tienen que ver con esto de los números enteros con y sin signo:


binyang

Este círculo se lee exactamente igual que un reloj analógico. (De manecillas) Se parte del punto mas alto donde está el valor 0 y se recorre toda la circunferencia hasta llegar a donde empezamos. De hecho, al igual que el reloj, la cuenta que lleva un byte vuelve a comenzar al dar una vuelta completa. Este fenómeno se llama WRAP-AROUND y es la base de la aritmética de enteros signados, (que no es lo mismo que santiguados). Este fenómeno era muy común en los juegos de principios de los años 80 como Asteroids, en donde si uno salía por un lado de la pantalla, magicamente aparecía por el lado de la pantalla opuesto.

Sin embargo, las similitudes con el reloj convencional terminan aquí. Pues esta rueda puede moverse en sentido opuesto a como lo hacen las manecillas. Pues del mismo modo que si tenemos 255 y le sumamos uno el contador se reinicia en cero, si tenemos cero y restamos uno, el contador marcará 255. Esto tiene perfecto sentido en los números sin signo, representados aquí en color gris tanto en decimal como en Hexadecimal. Entonces, para dar una vuelta completa al circulo, hay que dar 256 pasos. Pero eso es solo porque esta representación es de 1 byte.

Números con signo.- Cuando usamos signo, la mitad de los números son positivos y la mitad negativos. Aunque hay 2 casos especiales:

Cero Positivo.- Aunque matemáticamente el cero no es ni positivo ni negativo, y puede dividirse entre cualquier número. Dentro de un CPU un cero se considera positivo y par. Pero esto es válido solo para las representaciones de números enteros, pues en Punto Flotante pueden representarse ceros positivos (porque tienen SIDA) y negativos para su uso en cálculo infinitesimal. El cero siempre tiene todos los bits puestos en cero sin importar si se trata de un registro de 8, 16, 32, 64, etc. En el diagrama, el cero se encuentra en la parte mas alta del círculo del lado positivo (azul).

El Gran Negativo.- El -128 es el número negativo con mayor valor absoluto que puede representarse en 8 bits. Para compensar el hecho de que los negativos no tienen cero, los positivos no tienen el +128. El número negativo con mayor valor absoluto puede ser:

-128 en 8 bits,

-32768 en 16 bits,

-2,147,483,648 en 32 bits

y -9,223,372,036,854,775,808 en 64 bits.

En realidad no importa el ancho del registro, el Gran Negativo siempre se representa con todos los bits en cero excepto el bit mas significativo. En el diagrama, el Gran Negativo se encuentra en la parte mas baja del círculo, del lado negativo (rojo).

Lo interesante es que la computadora no sabe ni le importa si el valor en el registro tiene signo o no. Si en la parte baja del acumulador hay un A0h el CPU no sabe si se trata de un 160 sin signo o un -96 con signo. Mas adelante veremos como el CPU evalúa un resultado con el registro EFLAGS. Los números entre el 0 y el 127 con signo se manejan igual a los que no tienen signo.

Bit Signo.- Es el bit mas alto del registro. Si está en uno el valor es negativo y se está en cero es positivo. Podría decirse que ese es el ‘bit del mal’. Pero ojo, si queremos convertir un número positivo a negativo, o viceversa, hace falta mucho mas que solo cambiar el valor de este bit. Hay que ‘negarlo’ con la instrucción NEG.

NOT.- Esta operación es muy simple, enciende todos los bits apagados y apaga todos los bits entendidos. En el círculo, esta operación funciona como un espejo,pues solo hay que trazar una linea horizontal desde el valor que queremos hacerle el NOT hasta el otro lado de la rueda. Pero Ojo, los números no son simétricos. La suma de cualquier número mas su valor NOT es igual a todos los bits activos, en este caso -1 con signo y 255 con signo.

NEG.- La instrucción NEG cambia el signo de un valor entero. Si es positivo lo hace negativo y el negativo a positivo. Para que no se les olvide, el NEG es como una opreración de cambio de sexo (u operación jarocha como diríamos en México).Primero hace un NOT y luego suma un uno. Esto se ve en el círculo donde están las flechas azul y roja y la palabra NEG. Se traza una linea hasta el otro lado de la rueda y se avanza una unidad para cambiar el signo. Pero hay 2 excepciones: El Cero y en Gran Negativo. Si le aplicamos el NEG a cualquiera de estos 2 valores no sucederá nada. Tan solo se activará la bandera CARRY en el registro EFLAGS.


rango entero

Por último, hay que dejar claro que para el CPU hay una gran diferencia entre el hecho de que un número sea mayor o menor que otro y que esté por encima o por debajo de otro. En este otro dibujo vemos el mismo círculo de la primera ilustración en forma ‘desenrrollada’ y podemos ver lo complicado que resulta representar números con signo de manera no circular.

El concepto de MAYOR y MENOR solo aplica para los números con signo. Mientras que ARRIBA y ABAJO es solo para los que no tienen signo. Por ejemplo, el -1 es menos que 100 pero a la vez -1 está por encima del 100 porque -1 es 0FFh mientras 100 es 64h. Esto tiene una ventaja, si queremos ver si un resultado está entre 0 y 50, en lugar de hacer 2 comparaciones (resultado < 0 y resultado > 50) hacemos solo una, donde vemos si el resultado está por debajo del 50. Pero esto ya es asunto del registro EFLAGS.

De acuerdo, ya me extendí demasiado. Para acabar solo queda decir que no hay que confundir el Carry con el Overflow. Supongo que ya debería de hablar de como toma decisiones la computadora. Pero por ahora a insultar:

Hace unas horas eché un vistazo al circo de pulgas. Es increible como llevan casi un mes tratando de unir todos los archivos de sus sprites lameados con alguna aplicación. Al parecer alguien se queja de que la única aplicación que encontró para hacer esto solo los ordena de modo vertical. ¡Estos lamers no saben que en esa disposición es mas sencillo su almacenamiento y recuperación en la memoria! Quieren que el programa acomode los sprites en una matriz bidimensional donde todos los sprites quedan en celdas del mismo tamaño. De hecho en el foro viene una secuencia de animación de un Ha-Do-Ken. (Suerte que esta vez no le dijeron ‘abuken’) y cada cuadro de animación es por lo menos 9 veces mas grande que el sprite que contiene. Lo que significa que, como no creo que sepan nada de compresión en tiempo real, van a gastar 9 veces mas memoria solo en esa animación. En otro thread, al parecer están planeando una orgía. Pero eso ya no tiene nada que ver con la programación. Bueno, ahí le dejo porque tengo que planear nuevas bromas crueles para este blog.

abril 16, 2009 Posted by | Uncategorized | , , , , , , , , | 21 comentarios

¿Cuantos Bits tiene un Byte?

Los gringos tienen un chiste en que un ingeniero del departamento de sistemas le pregunta a alguien del área administrativa si sabe cuantos bits tiene un byte, y este contesta que eso depende del tamaño del perro. Este chiste no tiene el menor sentido en español. Sin embargo en inglés la palabra BIT significa mordida o huella de diente y BYTE suena igual que “Bite” que es el verbo ‘morder’.

Comencemos por el principio. Diario escuchamos que un archivo mide una cierta cantidad de bytes pero muy pocos, inclusive muchos ingenieros, se han topado cara a cara con uno. Asi que usaré todos mis recursos para explicar un concepto tan elementalmente como me sea posible.

El equivalente del BIT en esta dimensión es como un foco que puede representar cero o uno. Encendido o apagado. Eso cualquiera lo sabe, al igual que saben que un átomo tiene protones, electrones y neutrones. Pero pocos interactuan directamente con estos. Un grupo de 8 bits que pueden ser cero o uno tiene el nombre de BYTE.

Lo mas parecido que existe a un BYTE en el mundo de los humanos es una CAJITA. Una cajita muy pequeña que puede almacenar un número. Este número puede ser cualquiera entre 0 y 255. O lo que es lo mismo, 8 bits.

La dichosa memoria RAM(nada que ver con los carneros) es un chorizote de varias celdas de memoria con capacidad de un byte. Para que se den una idea, una máquina con un Gigabyte en ram tiene algo así como 2 elevado a la 30 que es mas o menos 1,073,741,824 cajitas como estas. En el dibujo que acompaña a esta entrada viene una de estas celdas de memoria. Como se puede ver, una de estas celdas de capacidad de un byte tiene 8 espacios para guardar bits (aquí en rojo). Cada una de estas celdas tiene un valor posicional, como en el decimal se manejan unidades, decenas,centenas,etc. Es una suma de potencias de dos, contando de derecha a izquierda la primera posición vale 2 elevado a la cero = 1 y la última 2 elevado a la 7 = 128. Como puede verse en la última celda del dibujo, los números mas chiquitos representan el valor posicional de cada celda y su contenido nos dice si este valor es sumado (uno) o ignorado (cero) en el resultado final.

En el ejemplo del dibujo, la primer celda tiene todos sus bits en cero, lo que significa que la celda contiene un cero. El segundo ejemplo tiene una celda de byte llena. Si sumamos 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 nos da 255 que es el valor máximo que podemos representar con un solo byte. Para números mas grandes se usan varios bytes juntos. Pero eso lo veremos otro día. En el tercer dibujo, la celda de memoria contiene 100, pues 64 + 32 + 4 = 100.

Existe una forma para representar los bytes llamada Hexadecimal, con esta notación es posible representar un byte con 2 cifras, algo similar a lo que pasa con las centenas y un par de cifras decimales. Además de que el binario puro ocupa demasiado espacio en pantalla.

Bueno, ya fue mucha clase por hoy, ahora pónganse a chatear para que no les duela la cabeza pues mas adelante vamos a ver el tema de los arreglos y los números en Hex. Creo que lo de hex lo veremos primero.

Esto es un byte

Esto es un byte

>>>>Pasa al siguiente nivel>>>>

***Esta nota pertenece a “La Saga del Completo Principiante”*** Da click en este enlace para pasar a la siguiente entrada de esta serie.

diciembre 27, 2008 Posted by | Uncategorized | , , , | 28 comentarios