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

La Porrista de 100 Kilos

–Direccionamiento Base con Indice Escalado y Desplazamiento–

Direccionamiento de memoria. Esta es la parte mas confusa a la hora de programar en Ensamblador. Pues los registros generales no solo sirven para contener bits. También se usan para mover datos entre la memoria y el CPU. Del mismo modo que podemos usar una mano para sostener algo o podemos señalar con el dedo. Sin embargo, un CPU de Intel es capaz de combinar sus registros generales de forma que puede guardar y recuperar datos de las estructuras mas extrañas con gran eficiencia.

Esto es muy similar a esas pirámides humanas que hacen las porristas de los eventos deportivos. Donde las mas fuertes se apoyan en cuatro en el suelo. Las de mayor equilibrio se paran sobre sus espaldas y la mas agil de todas trepa hasta la punta y salta realizando una vistosa pirueta en el aire…

Bueno. Como ya me está comenzando a sangrar la nariz mejor le sigo con el Ensamblador. Los registros del CPU pueden ser usados para ‘apuntar’ a la memoria. Quienes han programado en C de seguro escucharon alguna vez el concepto de ‘punteros‘. Los punteros son variables que contienen posiciones de memoria (que al fin y al cabo siguen siendo bits). Un CPU de Intel puede hacer aritmética de punteros ¡Por Hardware!. Aunque puede verse como algo muy complicado. Solo consiste de 3 partes: Base, indice y desplazamiento. Aunque estos nombres son los oficiales de Intel no necesariamente son los mas acertados y veremos porqué.

BASE.- Es el registro sobre el que se apoya el índice. En la pirámide, sería la porrista que está enmedio. Su contenido puede ser cambiado y con ello se pueden lograr direccionamientos relativos. Esto es lo que hace funcionar las variables locales en un Stack Frame.

INDICE.- Es el que está hasta el final. Se apoya sobre el registro base. La porrista que está en la punta de la pirámide y hace las acrobacias. Su contenido puede cambiarse y avanzar por las partes mas recónditas de las estructuras.

DESPLAZAMIENTO.- Es la parte constante del direccionamiento. Se trata de un número que puede ser una posición absoluta de la memoria o relativa a otro registro. Sin embargo, su valor no puede cambiarse sin cambiar el código máquina de la instrucción. El mejor uso de esta parte es para apuntar a posiciones fijas de la memoria como son las variables globales o el principio de un gran arreglo de estructuras. Por analogía con las porristas. El desplazamiento sería: ¡La Infame Porrista de Cien Kilos! Quien por su peso no puede moverse ni mucho menos pararse sobre la pirámide. Por seguridad, la posición que le toca a esta gorda es la de soporte de la piramide.

Un direccionamiento completo con estos tres elementos se vería así:

                        MOV    EAX, [EBX + ESI + 1234h]

Esta instrucción suma el contenido de los registros EAX, ESI y el valor 1234 hexadecimal. Esto da un número que corresponde a una posición de memoria. Entonces, el valor de 32 bits almacenado en esa posición de memoria se almacena en el registro EAX. Pero aún hay mas. Algo llamado factor escalar.

Como recordarán, el valor de las posiciones de memoria es una cantidad en bytes. Los bytes son celdas de 8 bits. No de 16, 32 ni 64. Solo 8 Entonces, ¿Que pasa cuando tenemos que trabajar con valores que no son de 8 bits? Un programador inexperto y desconfiado usaría operaciones aritméticas como ADD; Uno igual de inexperto pero con mas seguridad en si mismo recurriría a la instrucción SHL. Pero alguien que realmente separ de programación sabe que puede usar factores escalares. Un factor escalar en un direccionamiento de memoria multiplica por 2 (16 bits), 4(32 bits) u 8 (64 bits) única y exclusivamente al registro índice. De este modo, es posible recorrer todos los valores internos de una estructura.

De nuevo, con el ejemplo. El valor índice marca que tan alto es el salto que debe de dar la porrista que está hasta arriba de la pirámide.

Ahora al código máquina:

Esto parece demasiado complicado. Sin embargo. Cuando vemos el formato del código máquina todo se aclara. Y aquí es donde entra el byte SIB. Que es el acrónimo de Scalar Index Base. He aquí los bits:

Bits[7:6].- 2 bits que indican el factor escalar. 00 es 0; 01 es 2; 10 es 4 y 11 es 8. Para quien le entienda a las matemáticas, estos son las potencias de 2: 2 ^ 0= 1; 2^1 = 2; 2^2 = 4 y 2^4 = 8.

Bits [5:3].- 3 bits que indican el registro índice. Se interpretan de manera CASI igual que el campo REG de ModR/M. Pero hay pequeñas diferencias que pueden meternos en lios. Recuerden que solo el Indice se puede escalar. Nótese que ESP no puede ser un Indice escalado.

Bits[2:0].- 3 bits que representan la base. De nuevo se interpreta CASI igual que REG en ModR/M. Sin embargo. La combinación binaria 101 (5 decimal) y que correspondría a ESP, se interpreta diferente dependiendo de los 2 bits [5:7] Del byte ModR/M. Para mas detalles vean el dibujo.

Para terminar, cabe mencionar que los bytes ModR/M y SIB trabajan juntos. La existencia e interpretación de SIB depende directamente de ModR/M y este a su vez del OpCode. Además, algunas instrucciones utilizan una parte de ModR/M como parte del código de operación. Un ejemplo de esto es la instrucción DEC cuyo equivalente en C sería el “- -”. El campo REG del byte ModR/M de esta instrucción siempre es 001. Aunque tiene una forma que solo usa un byte.

Mas adelante explicaré como conectar OpCode, ModR/M y SIB. Pero por ahora, ya tienen suficiente para explorar por cuenta propia el código máquina de un programa para los procesadores de Intel. Y ahí si, si logran comprender, no necesitarán a nadie que les eche porras (espero que los programadores brasileños no se ofendan por esta expresión), y mucho menos a una porrista de cien kilos.

abril 7, 2009 Posted by | Uncategorized | , , , , , | 4 comentarios

Operandos

–Como se representan los operandos en lenguaje máquina–

Este asunto del blog me ha mostrado la gran diferencia que existe entre saber sobre un tema y poder explicarlo. En este caso, también he visto la utilidad de los dibujos para explicar los conceptos mas difíciles. Hasta ahora me la he llevado bien con el Paint del Windows. Sin embargo, algunos diagramas son tan grandes y complejos que, aunque técnicamente es posible hacerlos en Paint, me tomaría demasiado tiempo y el resultados sería tan enredado que de poco le serviría a quien los viera. Supongo que el Paint en este caso funcionará como una especie de candado. De este modo no generaré dibujos demasiado complejos que solo quitan tiempo a quien los hace y los mira. Asi como ocupan espacio en los servidores.

El tema del que quería hablarles hoy es sobre los OpCode Maps. Un OpCode Map es un mapa de todas las combinaciones posibles que pueden tomar los códigos máquina de un CPU y se puede desplegar en un arreglo bidimensional semejante al de un tablero de ajedrez. En el caso de los procesadores de Intel, Este mapa tiene 3 pisos, además de que se divide en otros mapas mas pequeños. En este caso, vamos a movernos desde los mapas mas pequeños a los mas grandes.

En este caso, el mapa mas pequeño y mas útil que conozco se llama Mod/RM.

Mod/RM es un BYTE que controla la combinación de los operandos de una instrucción. Si uno comprende esto del Mod/RM, ya no se va a enredar con la lógica de los operandos. Que si registro con registro, registro con memoria, base mas índice, etc. Pues aunque matemáticamente es posible hacer un número estratosférico de combinaciones, el byte Mod/RM solo puede asumir 255 valores diferentes.

Para empezar es bueno aclarar porqués esta cosa se llama Mod/RM. Y es porque este byte define el modo de acceso, y la combinación de registros y registro/memoria. No se preocupen, para lo único que necesitarán saber esto es para saber que combinaciónes de operandos son válidas y cuales no. A menos que quieran hacer su propio ensamblador o que quieran escribir operaciones en código máquina puro nada mas para fastidiar.(Cosa que veremos aquí: ver código puro y fastidiar).

A grandes rasgos el byte Mod/RM se divide en 3 partes, aunque en realidad son solo 2 pero una está partida:

bits [7:6].- Son los 2 bits mas altos del valor RM

bits [5:3].- Bits que indican un registro de CPU (puede ser general, mmx, simd, etc)

bits [2:0].- Los 3 bits mas bajos del valor RM.

La parte mas sencilla de comprender son los 3 bits que indican un registro. Como sabemos, en el modo de ejecución de 32 bits. (Y también en el de 16 pero no se lo digan a los lamers) tenemos 8 registros de propósito general. Y también sabemos que con 3 bits podemos representar 8 combinaciones que son los números del 0 al 7. En esta colorida tablita les dejo el valor máquina de los registros generales:


cuadro gris

Aunque ya había hablado sobre esto en la nota ‘Los 8 jinetes del Apocalípsis, es hasta que uno ve esta tabla el porqué es importante aprenderse los registros en ese orden en particular y no basándose en el alfabeto. Ahora vamos con el campo R/M de 5 bits y a explicar porqué se separa en 2 partes:

MOD.- Estos son 2 bits que representan el tipo de direccionamiento que vamos a usar. Dependiendo de estos 2 bits es como [2:0] han de ser interpretados. Veamos otra tabla de la parte MOD


cuadro gris

Como podemos ver, los 4 valores del MOD indican el tipo de desplazamiento constante. Que puede ser cero, de 8, 32 o registro. Cuando los 2 bits del campo mod están encendidos, significa que la operación tiene por argumentos dos registros del CPU. En este caso, los bits R/M que son [2:0] se interpretan del mismo modo que los bits [5:3] del campo REG.

Entre estas entradas hay algo llamado código de escape. Nótese que a menos que los bits MOD estén ambos activos. El trio R/M no puede tomar el valor binario 100 (4 decimal). Este es un código de escape y cuando aparece significa que luego del byte ModR/M sigue el bite SIB.

SIB debe su nombre a Scalar Index Base. Si ModR/M fuera el hermano mayor, SIB sería el hermano menor y si combinamos ambos podemos hacer un direccionamiento de la memoria mas sofisticado. Pero por ahora, prosigamos con el ModR/M.

Para terminar aquí hay algunos puntos relacionados con el byte ModR/M:

*Este byte no trabaja solo. Depende de 2 bits dentro del OpCode de la instrucción (para quienes no leyeron la entrada anterior, un OpCode es un código de operación del CPU.  Estos bits son D, W e I. El bit D indica quien es el operando fuente y quien el destino. Si uno se equivoca al activar este bit puede hacer que una operación haga exactamente lo opuesto a lo que quiere. El bit W indica el ancho de los operandos (Wide). De ahí que pueda distinguir entre EAX o AL cuando el campo REG del ModR/M está en cero. En la mayoría de las instrucciónes los operandos deben de ser del mismo tamaño. Con Excepción de las operaciones de extensión como CBW CWD y CDE. Hay un buen chiste sobre este tipo de instrucciones que les contaré cuando veamos el tema y los niños se hayan ido a dormir.

El Bit I indica que uno de los operandos es un valor inmediato. Un valor inmediato es una constante que se pasa como argumento. Es importante tomar en cuenta que siempre los operandos inmediatos son operandos fuente. Nunca se debe poner un valor inmediato como operando destino de una instrucción. Por Ejemplo, para sumar 2 al acumulador hacemos MOV EAX, 2. Pero de ninguna manera podemos hacer MOV 2, EAX.

Como nota histórica, en la antigua era de lo 16 bits. El ModR/M era suficiente para manejar todos los direccionamientos de memoria posibles. Esto se debía a que únicamente se podían usar 3 de los 8 registros generales para apuntar a la memoria. Estos eran BX, SI y DI. Pero ahora todos los registros tienen esa capacidad. Otra cosa nueva es el uso de escalares para direccionar elementos de memoria cuyo ancho fuera mayor que el de un byte sin recurrir a aritmética turculenta que solo retrasa la ejecución. Pero como le dijo la Nana Goya a Conan el Bárbaro:

–Pero esa es una histora para ser contada en otra entrada…–

abril 4, 2009 Posted by | Uncategorized | , , , , , | 1 comentario

Mas Máquina que Hombre

–Como programar en lenguaje máquina–

Un programador de Ensamblador que no sabe programar en lenguaje máquina es como un químico que desconoce la manipulación del átomo. También se ha dicho mucho que programar en lenguaje máquina es imposible. Sin embargo, luego de algunos meses de insultar lamers y ver dibujitos en paint, considero que ya están listos para comenzar a hacer tal proeza.


cuadro gris

No todos los campos se usan en todas las instrucciones. El único indispensable es el de OpCode. Todos los demás son opcionales.

Veamos un poco que significa cada campo:

Prefijos.- Existen prefijos que modifican las instrucciones. Algunos de ellos son los modificadores de ancho de operando/dirección (66h y 67h), los de repetición para funciones de manejo de cadenas y el temible prefijo REX del que se hablará en otra nota.

OpCode.- Este es el único obligatorio. Una instrucción puede medir desde un solo byte, como por ejemplo un NOP (90h) o hasta 16 bytes como en algunos saltos largos. El campo de OpCode por si solo puede medir entre 1 y 3 bytes.

ModR/M.- Este es un byte que especifica los operandos de la instrucción. Por lo general se pueden expresar las combinaciones de operandos mas usuales tan solo con este, pero a veces es necesario otro byte para direccionamientos mas complejos.

SIB.- Su nombre viene de Scalar Index Base. Es el complemento de ModR/M. Con este es posible hacer combinaciones de registros y desplazamientos que no era posible lograr en los antiguos modelos de 16 bits.

Desplazamiento.- Puede medir entre 1 y 4 bytes. Especifica una localidad de memoria que puede ser absoluta o relativa.

Valor Inmediato.- Semejante al anterior, pero este número se usa para representar un valor inmediato. Un valor inmediato es cuando uno de los operandos de la instrucción es una constante.

Definitivamente tengo que poner un link mas directo a los manuales de Intel.

Ahora a insultar.- Lo primero que han de preguntarse es que gana uno programando en lenguaje máquina. Bueno, ademas de hacer algo que no cualquiera puede hacer y el aumento de la autoestima a lo que esto conduce. Si conocen por ejemplo los bytes ModRM y SIB ya no tendrán dudas sobre que combinaciones son válidas. También es posible activar instrucciones de los procesadores mas recientes sin tener que esperar a que aparezca un compilador que las use. Aunque en mi experiencia personal es mas sencillo construir tu propio ensamblador que hacer programas completos en lenguaje máquina y el resultado es igual de eficiente. Otra gracia que tiene es que cuando las instrucciones cumplen ciertas reglas de alineación dentro de la memoria se ejecutan mucho mas rápido que si solo las aventaras así como van.

Por cierto, este diagrama solo es válido para lo que Intel llama “modo de compatibilidad” es decir que si su sistema Windows es de 64 bits. El formato de la instrucción cambia un poco. En ese formato conocido como “IA-32e”. En ese modo de operación es posible manejar memoria mas allá de la barrera de los 4 Gbytes de manera directa. Aunque la única diferencia entre lo que acabo de explicar y este modo, tan solo es el prefijo REX que va entre los prefijos y el OpCode.

Por último, y nada mas para que se sientan un poco como Neo de Matrix, intenten insertar en el código de siempre algunas instrucciones en lenguaje máquina.

Para insertar una instrucción en lenguaje máquina, hacemos lo mismo que cuando inicializamos una localidad de memoria. Por ejemplo. Supongamos que tenemos este código:

         mov    eax, 666h

Si mandamos desplegar el contenido del acumulador con una función o usamos un depurador de códigos, veremos que eax contiene el valor hexadecimal 666. Ahora, si modificaramos el código de esta forma:

         mov	eax, 666h
         db  	90h

no sucedería nada. Pues db 90h es la instrucción NOP, daría lo mismo si escribiéramos NOP en lugar de 90h. Estanstrucción aperentemente inutil se usa para alinear código y acelerar su ejecución dentro del CPU. Ahora hagamos algo mas visible:

              mov       eax, 666h
              db	90h
              db	33h,0c0h

Si ahora desplegamos el contenido de EAX, este mostrará CERO. Esto es porque 33h,0c0h es el código máquina de XOR EAX, EAX. Pone en cero el contenido de EAX y es mucho mas rápida y ocupa un tercio de la memoria que MOV EAX, 0. En realidad no es necesario poner cada instrucción en un renglón. Es posible poner todos los códigos máquina en uno solo o partirnos como queramos. Al final el Ensamblador va a juntarlos todos de manera lineal.

Bueno, ya me extendí mucho, en próximas notas veremos mas sobre el lenguaje máquina y de paso exploraremos un poco el modo de operación de 64 bits. Esperen un pequeño BOOM de notas, porque ya vienen las vacaciones y voy a tener mas tiempo libre para escribir estupideces. Y si se aburren, consiganse la segunda parte de los manuales de intel (Instruction Set Reference) y traten de desensamblar a mano el programa PEDEMO.EXE. Con solo un editor HEX, o si no se atreven, con el OllyDbg pero sin mirar las instrucciones. Si pueden hacer esto y además se divierten, definitivamente les va a gustar programar en Ensamblador.

abril 4, 2009 Posted by | ensamblador, programación, Uncategorized | , , , , , | Deja un comentario

Viendo lo Invisible

–Como Desplegar el Contenido de los Registros Generales–

Hace mucho alguien me dijo que programar era como ver el mundo a traves de un tubo de papel higiénico. Esto es particularmente cierto para los códigos fuente. Sean del lenguaje que sean. Sin embargo, esta vez se trata de algo diferente. Pues esta es la continuación de la nota llamada ‘Piel Transparente’. Si entendieron mi código de seguro ya son capaces de ver un valor de 32 bits en hexadecimal a la vez. (Y si no lo entendieron espero tener la paciencia para escribir un código mucho mas estructurado). Veamos ahora un poco mas de lo que pueden hacer tan solo con las funciones ‘bin2asc’ y MessageBox:

regs

Como ya dije en la nota ASCII-zofrenia. El código Ascii no solo representa letras y números, sino signos de puntuación y valores de control y edición de texto. Y la función de la API de Win32 puede interpretar la mayoría de estos valores. Por ejemplo. Si el cero final de una cadena ASCII-Z lo cambiamos por el par de bytes 0dh, 0ah. Que representan 13 y 10 en decimal, conseguimos escribir en lineas separadas. Por ejemplo, en esta imagen se muestra una caja de texto que despliega los 8 registros generales de un procesador Intel. En realidad, MessageBox solo toma una cadena ASCII-Z y la despliega en varias lineas. Si ustedes son programadores mas o menos competentes serán capaces de crear cualquier caja de texto con información del sistema, y si apenas están aprendiendo y me encuentran de buen humor, puede que les muestre el código para hacerlas.

Otra cosa que puede hacerse es desplegar una sección de la memoria de esta misma forma. En esta otra ventana se muestra como. Esta función es bastante mas dificil de hacer que la que despliega los registros. Pues esta función implementa un buffer relativamente grande en el Stack donde crea la cadena que es enviada a MessageBox. Como es de esperarse, esta función depende de ‘bin2asc’ y de otra función que pega una cadena AsciiZ al final de otra (APPEND como diría un DBA). Esta técnica es especialmente importante para aplicaciones que construyen frases a partir de cadenas de texto, o como en este caso, para funciones que convierten numeros binarios a cadenas de texto representable en la pantalla de la computadora.

En general, es posible hacer casi cualquier ventana de información básica mezclando las funciones MessageBox, bin2asc y uniendo cadenas Ascii-z. Solo recuerden estos puntos importantes a considerar:

*PUSHA Y POPA. Este par de instrucciones se usan para guardar los 8 registros generales (Vel los 8 jinetes del Apocalipsis) de un solo golpe. Esto es especialmente importante con ESP y EBP

*Cuando adjunten una cadena ascii-z a otra cadena previa asegurense que la cadena que recibe tenga suficiente espacio para recibir el ‘apéndice’ o de lo contrario pueden sobreescribir otra sección de segmento de código o peor aún, desatar el famoso Fallo de Protección General. Que no por nada es la excepción 13.

*MessageBox tiene un límite en cuanto a la longitud de la cadena que puede representar. No lo he medido con exactitud pero ronda 1os 256 bytes.

*No se olviden que una cadena Ascii-z termina en cero binario. Y no confundan el cero binario (0) con el cero ascii (30h = 48)

Bueno, espero que esto sea suficiente para que puedan experimentar con código en Ensamblador sin necesidad de un emulador o depurador de código. Por ahora los dejo porque me acabo de encontrar una nueva página de desarrollo de videojuegos (la página es nueva pero fue fundada por los mismos lamers de siempre) y quiero fastidiarlos ahorita que están desprevenidos. Ya les informaré de como me fue en esta pequeña aventura.

marzo 15, 2009 Posted by | Uncategorized | , , , , | 7 comentarios

Piel Transparente

–Como hacer un programa facil de depurar–

Hace no mucho tiempo, científicos japoneses de la universidad de Hiroshima desarrollaron una rana con piel transparente. De modo que uno puede ver el funcionamiento de sus órganos en vivo y en directo sin necesidad de diseccionarla. Pueden ver el video aqui.

¿Y que tiene que ver una rana con piel transparente con el Ensamblador? Bueno, la verdad es que llevo años como programador y he leído muchos libros que dicen como se supone que se debe de programar. El problema es que todos esos libros se escribieron en una época en la que los programadores no contaban con una computadora en su casa. Pues todos dicen que uno debe de hacer una serie de pruebas exhaustivas antes de la compilación. Pero en mi experiencia personal, esto es demasiado tardado y no sirve para investigar nuevos algoritmos. Mas bien soy de la idea de que uno debe de construir un programa de tal forma que el mismo nos ayude a localizar los errores. Exactamente del mismo modo que lo hace la rana transparente. Imaginense que los programas que hacen fueran transparentes y pudieramos detectar los errores con solo verlos ejecutarse.

Si bien la rana de piel transparente es un gran avance de la genética, hacer esto mismo con un programa de computadora no requiere demasiado esfuerzo. De hecho, se requieren muchos menos recursos para hacer un programa que se autorevise solo que usar programas exernos para buscar errores. Y si esto suena demasiado complejo veamos algo de historia:

Si ustedes rondan los 30 años y son nerds, de seguro recuerdan el siguiente código: arriba, arriba, abajo, abajo, izquierda, derecha, izquierda, derecha, B, A, START. Este código se introducía en las pantallas de presentación de los juegos de la compañia Konami (Contra, Life Force, etc.) y uno podía obtener 30 vidas en lugar de 3. En otros juegos existían otros códigos para elegir nivel, cambiar de armas, invulnerabilidad, etc. Estos códigos no siempre eran trampas para ayudar a los jugadores mas flojos sino rutinas para buscar y reparar errores dentro del juego. Si un programador quería revisar que no hubiera errores en el nivel 50 no tenía que jugar desde el principio. Era posible revisar el daño que los monstruos le hacían al jugador sin necesidad de morir cada vez. O probar el efecto de las armas mas exóticas sin tener que pasar horas buscándolas.

Al inicio de un programa, uno puede construir rutinas que nos ayuden a construirlo y si somos demasiado perezosos podemos dejarlas en el programa de modo mas o menos oculto. No puedo explicar lo útiles que son estas rutinas. Sobre todo porque de todos los libros de computación que he leído en ninguno hablan de este estilo de programación. La rutina mas elemental es la que despliega el contenido del acumulador en hexadecimal. Aquí les digo como lograrlo.

Para desplegar el contenido del acumulador en hexadecimal es necesario combinar algunas funciones que ya vimos. Veámoslo por pasos:

debug:
        pusha
        call	bin2asc
        push	0
        push	debug_caption
        push	debug_message
        push	0
        call	[MessageBox]
        popa
        ret

Esta es la función básica. Primero guarda el contenido de los registros generales en la pila con PUSH, luego llama a la función bin2asc y luego llama a MessageBox, la cual recibe la posición de una cadena ASCII como uno de sus parámetros. Ahora revisemes la función bin2asc:

bin2asc:
        pusha
        xor	eax, eax
        mov	ecx, 8
        mov	edx, [eax_bin]
lee_binario:
        mov	ebx, 0fh
        and	ebx, edx
        mov	al,  [ebx + digitos]
        push	eax
        shr	edx, 4
        loop	lee_binario
        mov	ecx, 8
        mov	edi, eax_ascii
escribe_binario:
        pop	eax
        mov	[edi], al
        inc	edi
        loop	escribe_binario
        popa
        ret

Esta función recibe un valor binario de 8 bits y genera una cadena ASCII-Z que es posible desplegar en una caja de diálogo de MessageBox. Las 4 primeras instrucciones salvan los registros generales con pusha, pone en cero el acumulador con XOR EAX, EAX (cualquier número XOR consigo mismo es cero), carga un 8 en el registro contador ECX y carga en el registro EDX el valor binario a convertir, en este caso [eax_bin]. La segunda parte es un ciclo que se repite 8 veces; a grandes rasgos se toman los bits de EDX en grupos de 4 en 4 y ese número (que solo puede tomar valores entre 0 y 15) se usa como un ‘offset’ en el arreglo DIGITOS. Este arreglo contiene los números del 0 al 9 y las letras de la A a la F de modo que su posición dentro de la memoria corresponde con el valor numérico de estos símbolos en el sistema Hexadecimal. Noten que estos símbolos se guardan en el STACK con PUSH desde el último (bits menos significativos) hasta el primero (bits mas significativos). Ahora la cadena ascii que representa el número ya se encuentra en el STACK.

La última parte consiste en escribir esa cadena en una posición dada. En este caso tal buffer está en ‘eax_ascii’ Hacemos que EDI que es el Destination Index Register (y no Destiny como dicen los lamers de la Universidad de Guadalajara) y cargamos en el contador ECX otro 8. En sucesión rápida sacamos los valores del STACK (que son de 32 bits) en EAX y escribimos los 8 bits mas bajos de EAX en el arreglo con MOV [EDI], AL e incrementamos EDI con INC EDI en una posición. Lo siguiente restaurar el contenido original de los registros generales con POPA y terminar la función con RET.

Noten que esta función está pesimamente estructurada, pues los datos no están autocontenidos en variables locales. Pero esta fue la única rutina sencilla de explicar que encontré entre mis códigos. Supongo que esto será suficiente para quien realmente le interese el Ensamblador y no a los lamers que solo quieren aprobar una materia para convertirse en DBA’s. Por ahora la nota ya quedó demasiado larga así que ahí le dejamos por ahora, mas adelante veremos mas código e insultaremos algunos lamers.

marzo 11, 2009 Posted by | Uncategorized | , , | Deja un comentario

Prog, Hex and Rock’n’Roll

–Todo Sobre el Sistema Hexadecimal–

Los sistemas numéricos son un mal necesario en el mundo de la computación, de hecho, el sistema decimal es sencillo de manejar y es el que mejor conocemos, pero para la computadora no significa nada. El binario es su idioma nativo y es con el que trabaja. Pero es demasiado tardado y confuso usarlo. La pregunta de los 65,536 sería ¿Habrá un sistema numérico que tenga las ventajas de ambos? La respuesta es si, se llama sistema hexadecimal y será el tema de esta nota.

Antes de continuar, se que este blog tiene 2 tipos principales de lectores (sin contar a los que vienen por la botana y a ligar) la primera clase son los lamercitos de escuela que les dejan de tarea copiar e imprimir páginas de internet como esta y la otra, los menos, son los principiantes que realmente les interesa el Ensamblador. En realidad, sería raro que un programador de cierto nivel buscara información en español. Mas bien teclean “Assembly Language” en el Google. Así que tomaré esto con calma.

Los ‘números’ de todos los días son 10, y van del 0 al 9. Los hexadecimales son 16. Se usan las letras de la A a la F para representar del 10 al 15. Vean la siguiente tabla:

DECIMAL   HEXADECIMAL       BINARIO

      0             0          0000

      1             1          0001

      2             2          0010

      3             3          0011

      4             4          0100

      5             5          0101

      6             6          0110

      7             7          0111

      8             8          1000

      9             9          1001

     10             A          1010

     11             B          1011

     12             C          1100

     13             D          1101

     14             E          1110

     15             F          1111

Esta tabla es importante, así que les recomiendo que la impriman y la peguen en la pared junto al programa PEDEMO.EXE del Fasm. Ahora veamos como hacer las conversiones rápidas.


cuadro gris

El primer paso para convertir un número a sistema Hexadecimal es convertirlo a binario. Moverse entre decimal y hexadecimal es mentalmente muy tardado y requiere mucha experiencia. Para el caso de las celdas de un BYTE, primero hay que escribir los 8 bits. Esa ristra de ceros y unos se parte en 2 mitades de 4 bits cada una y a continuación se convierte cada una de estas viboritas(llamadas NIBBLES en los libros mas antiguos) equivale exactamente a una cifra Hexadecimal. El dibujo que acompaña a esta entrada describe de manera clara como se convierte una celda de 8 bits a hexadecimal. Pero antes de que se harten y se vayan a trabajar en Visual Basic (las palabras ‘programar y ‘Visual Basic’ no pueden ir en la misma frase) veamos algunas de las virtudes de los números hexadecimales:

*La conversión entre Hexadecimal y Binario se puede hacer mentalmente de manera muy rápida.

*Con solo ver un número hexadecimal podemos saber cuantos bits se necesitan para representarlo.

*Podemos saber si un entero es positivo o negativo con solo ver su primer cifra hexadecimal.

*En cuanto a las posiciones de memoria. Podemos saber si están alineadas adecuadamente por la cantidad de ceros al final.

*No importa que número representemos, mientras solo usemos 2 cifras no sobrepasaremos el límite de un byte. Lo mismo aplica para las 4 cifras en un word(l6 bits) y 8 cifras para el DWORD (32 bits)

*El máximo número representable por un byte es ‘FF’, de un word es ‘FFFF’ y de un dword es ‘FFFFFFFF’. En el caso de la aritmética entera, estos números también significan menos uno.

*Si no aprenden Hexadecimal van a cagar chayotes cuando quieran programar de verdad. Así que pónganse a practicar o si no vayan buscándose algún trabajo mas sencillo como hacer querys.

Existe otro sistema llamado Octal, que es lo mismo pero cada cifra es representada por 3 bits. No se usa mucho, solo lo he visto para representar OpCodes en Intel y algunas cosas con los discos duros. Y si ya se aburrieron esperen a ver lo que sigue, será una auténtica ‘bienvenida al Mundo Real’.

>>>>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 31, 2008 Posted by | Uncategorized | , , , , , | 14 comentarios