Programación en Lenguaje Ensamblador

-El Verdadero Lenguaje de las Máquinas-

+++ 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

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 | , , , , | 5 comentarios

“Los 8 Jinetes del Apocalipsis”

La primera vez que alguien intentó explicarme lo que eran los registros internos de un procesador me dijeron algo así como –”los registros de procesador son los caballitos de batalla del procesador”– Esto para mi no tiene sentido ni aún ahora que llevo tantos años programando en Ensamblador. La mejor definición que encontré entonces fue que los registros del CPU son “Variables de Hardware”, pero cuando avancé en ensamblador, encontré que el término ‘variable’ como se ve en matemáticas no tiene sentido en el mundo de la computación. El concepto de registro de procesador está mas bien relacionado con el concepto de celda de memoria.

Los registros generales del cpu de intel son 8. Sus nombres son EAX, ECX,EDX,EBX,ESP,EBP,ESI Y EDI. Existen otros registros con otras funciones, pero los mas importantes son estos 8. Estos pertenecen a una misma unidad llamada ALU(Arithmetic-Logic Unit, Unidad de Aritmética y Lógica). Estos registros son semejantes a las bien conocidas celdas de memoria de byte. Solo que mas grandes. Pues pueden almacenar hasta 32 bits. U 8 cifras hexadecimales que como sabemos representan 4 bits cada una. Sin embargo, estos registros nacieron con el 8086 y han evolucionado junto con este. En sus inicios medían solo 16 bits y tenían funciones muy específicas. Pero lo mejor es presentar a cada uno de estos Jinetes del Apocalipsis en detalle:

EAX.- Este es el registro que mas veremos en acción. También es conocido como el Acumulador. Una de sus habilidades mas importantes es regresarnos el resultado de una función en la mayoría de los sistemas operativos. Operaciones de multiplicación y división de enteros y comunicación con los registros de estado como EFLAGS.Puede dividir sus 16 bits mas bajos en un par de 8 pequeñas celdas de 8 bits llamadas AH y AL. Esto para manejar datos de 8 bits. Esta capacidad también la comparten los registros ECX, EDX, y EBX. En su, epoca de 16 bits era conocido como AX (se escribe igual que ‘hacha’ en inglés) y en los CPU’s de 64 bits es conocido como RAX.

ECX.- Este registro es conocido como el contador. Cuando hacemos un proceso cíclico. (Como desplegar todos los elementos de la tabla del uno) Este registro se encarga de contar las iteraciones y detener la repetición cuando queda vacío. Algunas de las instrucciones que dependen directamente de este registro son LOOP, JECXZ, LODS,STOS,CMPS,SCAS,etc. Puede manejar independientemente sus 16 bits mas bajos en los subregistros CH (el del Chapulín Colorado) y CL. El pasado era CX y el de 64 es RCX.

EDX.- Este es el especializado en DATOS. Si EAX es nuestra mano derecha, EDX es nuestra mano izquierda. (O al revés para quienes sean zurdos). Si necesitamos sumar dos números lo mas seguro es que en el acumulador se guarde el resultado y en EDX el valor a sumar. Otra de sus gracias es comunicarse con el mundo exterior por medio de las instrucciones IN, OUT, INS y OUTS. En estas instrucciones podemos leer datos que vienen de dispositivos periféricos. El acumulador guarda los datos a entrar o salir mientras que EDX indica por cual de las puertas han de hacerlo. También puede manejar sus 16 bits mas bajos de manera independiente con los subregistros DH y DL.

EBX.- Conocido como Registro BASE. Una de sus propiedades que lo distinguió en sus primeros días era que tenía la capacidad apuntar a la memoria. Esto significa que si por ejemplo EBX contiene 1234ABCD podemos leer y escribir en la celda de memoria en la posición 1234ABCD del sistema. Esta propiedad podía usarse sola o en combinación con ESI, EDI y valores constantes. De modo que podía intercambiar datos entre el CPU e intrincadas estructuras de datos sin problemas. Sin embargo. Esta habilidad que era exclusiva de EBX, ESI y EDI. (Y en menor grado de ESP Y EBP) ahora es común a TODOS los registros generales. Al igual que los anteriores. También puede manejar los 16 bits mas bajos en la forma de BH y BL.

ESP.- Su nombre es Stack Pointer. Este registro, junto con su hermano EBP, son los responsables del control de una importante estructura de datos llamada STACK. La explicación de lo que es una stack merece su propia entrada independiente. Por ahora solo basta decir que el stack es una estructura en memoria que sirve entre otras cosas para manejar variables locales, el correcto flujo de ejecución, paso de argumentos a las funciones y temas espantalamers como el de la mentada recursión. (se dice que ha habido ingenieros en computación que han hecho tesis doctorales sobre este tema.). ESP nos permite manejar las variables locales de manera directa en la stack.

EBP.- Esta es la contraparte de ESP. Se le conoce como Base Pointer. Se podriá decir que si ESP se encarga de lo que pasa dentro del Stack. EBP cuida la puerta. Pues su responsabilidad es el Stack Frame.(el espacio dentro del stack que permite el manejo de variables locales). Cuando veamos como llamar a una función que use argumentos, variables locales y recursión veremos al par ESP y EBP en plena acción. Aunque a diferencia de los registros anteriores. No pueden subdividir sus 16 bits mas bajos en otros mas pequeños.

ESI.- Existe otra pareja de registros que trabajan juntos. Y ESI es uno de ellos. Su nombre completo es Source Index. Al comienzo ESI era uno de los pocos que podían usarse como índice para posicionarse dentro de la memoria. Existen operaciones que por ejemplo mueven grandes cantidades de datos de un lugar a otro de la memoria. Se llama MOVS. Y en ella, la función de ESI es apuntar al byte que será leído.

EDI.- El hermano del anterior. En instrucciones de movimiento o escritura automática de celdas de memoria. EDI apunta a la celda donde se ha de escribir. Ni ESI ni EDI pueden dividir sus 16 bits mas bajos.

Pues estos son los 8 registros principales de un CPU de Intel trabajando a 32 bits. Cuando la máquina opera a 64 bits, los registros son capaces de almacenar valores de 64 bits y sus nombres cambian a RAX, RCX,RDX, RBX, RSP, RBP(no confundir con RBD), RSI, RDI. Y aparecen otros 8 registros ‘escuderos’ cuyos nombres son R8, R9, R10, R11, R12(como la mascota de los rayados de monterrey), R13, R14, R15. Los registros numerados no tienen ninguna habilidad especial hasta donde yo se. En otra nota hablaré mas detenidamente sobre el modo de 64 bits.

He aquí algunas notas sobre los Registros Generales.

*Son llamados General Registers, porque son los que van al frente en casi todas ‘las batallas’

*Intel se refiere a estos registros como REGS. Cuando en los manuales vean REGS se refieren a cualquiera de estos registros.

*Originalmente estos registros medían 16 bits, de modo que eran capaces de dividirse en 2 mitades. De modo que si escribíamos el hexadecimal 1234 en el AX, AH contenía 12 y AL 34. Sin embargo, con el tiempo estos registros evolucionaron a los 32 y 64 bits y la parte capaz de dividirse se quedó de 16 bits de tamaño. Para entenderlo mejor, podríamos decir que 1os 16 bits son la ‘cola’ del registro. (Nota para los programadores de Colombia: Aquí la palabra cola se refiere al apéndice que les cuelga a animales como los perros, gatos, etc). Dicho de otro modo, la cola forma parte del gato pero este puede mover la cola como el quiera. O como diríamos en México: Cada quien hace de sus 16 bits mas bajos un papalote y los direcciona con los datos que mejor le convengan.

*Existe una cosa llamada ‘prefijos’ que permiten usar registros de arquitecturas diferentes en un mismo código. Por ejemplo usar registros de 16 bits en programas de 32 y viceversa. Pero esto además de que resta mucha velocidad al código puede ser muy peligroso.

*El movimiento de datos entre la memoria y el CPU es mucho mas rápido si el dato se encuentra en una posición divisible entre la longitud del registro que hace la transferencia. Por ejemplo. Un registro de 32 bits debe de leer datos en posiciones divisibles entre 4.

*Los registros del CPU son mas parecidos a manos. Puedes mover datos entre registro y memoria y entre registros. Pero los datos no pueden moverse solos de una localidad de memoria a otra. Al menos no sin la ayuda de instrucciones como MOVS, Funciones de sistema como FillMemory o el controvertido dispositivo de hardware conocido como DMA. (Direct Memory Access)

*Por último, a diferencia de las variables, los Registros Generales siempre están ahí. Sin importar que programa corra. Porque no ‘viven’ en la memoria, sino que son parte del CPU. En aplicaciones multitarea guardan su contenido en memoria y toman el de la siguiente aplicación del mismo modo como los actores de teatro cambian de vestuario entre una escena y otra para representar personajes diferentes. Esto último va para esos lamercillos de java que creen que los registros del CPU no poseen la propiedad de encapsulamiento.

Pues eso es todo por hoy, espero que el dibujo que acompaña a esta entrada les explique mejor el concepto de lo que son los Registros Generales REGS.

enero 7, 2009 Posted by | Uncategorized | , , | 6 comentarios

   

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 48 seguidores