Programación en Lenguaje Ensamblador

-El Verdadero Lenguaje de las Máquinas-

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