Otras instrucciones importantes
En este apartado, veremos unas cuantas instrucciones que me ha faltado mencionar hasta ahora y que son muy útiles a la hora de programar en ensamblador. Para ver una lista completa recomiendo ir a www.intel.com y buscar su documentación (se encuentra en formato PDF). En el siguiente apartado, el 5.4, menciono otras cuantas operaciones que pueden resultar útiles, aunque no sean tan necesarias como estas.
CLC/STC
Se trata de dos instrucciones que manejan directamente los valores del Carry Flag. CLC significa CLear Carry (lo pone a cero como es de suponer) y STC es SeT Carry (poniéndolo a 1).
Instrucciones de desplazamiento lateral
Ya hemos visto como podemos operar con un registro o una posición de memoria con operaciones aritméticas (ADD, SUB, etc) y lógicas (AND, OR, etc). Nos faltan pues las instrucciones de desplazamiento lateral, de las que hay de dos tipos:
-Rotación: Aquí tenemos ROL y ROR, que significa ROtate Left y ROtate Right. El modo de funcionamiento es sencillo; si ejecutamos un ROR EAX,1, todos los bits de EAX se moverán un paso a la derecha; el primer bit pasará a ser el segundo, el segundo será el tercero, etc. ¿Qué pasa con el último bit, en este caso el bit 32?. Bien, es sencillo, este bit pasará a ser ahora el primero.
En el caso de ROL, la rotación es a izquierdas; viendo un caso práctico, supongamos la instrucción ROL AL,2, donde AL valga 01100101. El resultado de esta operación sería 10010101. Se puede comprobar que se han movido en dos posiciones los bits hacia la izquierda; y si alguno "se sale por la izquierda", entra por la derecha.
-Desplazamiento aritmético: Tenemos aquí a las instrucciones SHL y SHR, es decir, SHift Left y SHift Right. La diferencia con ROL/ROR es sencilla, y consiste en que todo lo que sale por la izquierda o por la derecha se pierde en lugar de reincorporarse por el otro lado.
Es decir, si hacemos un SHL AL,2 al AL aquel que decíamos antes y que valía 01100101, el resultado será en esta ocasión 10010100 (los dos últimos espacios se rellenan con ceros). Es interesante notar que un SHL de una posición es como multiplicar por dos la cifra, dos posiciones multiplicar por 4, tres por 8, etc.
De cadena
Existe toda una serie de instrucciones en el ensamblador 80x86 dedicadas a tratar cadenas largas de bytes; en estas instrucciones vamos a encontrar algunas como MOVSx, CMPSx, SCASx, LODSx y STOSx:
-MOVSx: La x (y lo mismo sucede con el resto) ha de ser sustituída por una B, una W o una D; esto, indica el tamaño de cada unidad con la que realizar una operación (B es Byte, 8 bits, W es Word, 16 bits, y D es Dword, 32 bits). Cuando se ejecuta un MOVSx, se leen tantos bytes como indique el tamaño de la "x" de la dirección DS:[ESI], y se copian en ES:[EDI]. Además, los registros ESI y EDI se actualizan en consecuencia según el "movimiento realizado".
Es decir, que si tenemos en DS:[ESI] el valor "12345678h" y en ES:[EDI] el valor "87654321h", la instrucción MOVSD hará que en ES:[EDI] el nuevo contenido sea ese "12345678h".
-CMPSx: En esta instrucción de cadena, se comparará el contenido del byte/word/dword (dependiendo del valor de "x", según sea B, W o D) presente en la dirección DS:[ESI] con el de la dirección ES:[EDI], actualizando los flags de acuerdo a esta comparación (como si se tratara de cualquier otro tipo de comparación). Como sucedía antes, ESI y EDI se incrementan en la longitud de "x".
-SCASx: Compara el byte, word o dword (según el valor de "x") en ES:EDI con el valor de AL, AX o EAX según la longitud que le indiquemos, actualizando el registro de flags en consecuencia. La utilidad de esta instrucción, reside en la búsqueda de un valor "escaneando" (de ahí el nombre de la instrucción) a través del contenido de ES:EDI (recordemos que, como en las anteriores, EDI se incrementa tras la instrucción en el valor indicado por la "x").
-LODSx: Carga en AL, AX o EAX el contenido de la dirección de memoria apuntada por DS:ESI, incrementando luego ESI según el valor de la "x". Es decir, que si tenemos en DS:ESI los valores "12h, 1Fh, 6Ah, 3Fh", un LODSB pondría 12h en AL, LODSW haría AX como 1F12h, y LODSD daría a EAX el valor de 03F6A1F12h.
-STOSx:**La operación contraria a LODSx, almacena AL, AX o EAX (según lo que pongamos en la "x") en la dirección apuntada por ES:EDI, incrementando después EDI según el valor de la "x". Ejemplificando, si por ejemplo AX vale 01FF0h y ES:EDI es "12h, 1Fh, 6Ah, 3Fh", un STOSW hará que en ES:EDI ahora tengamos "F0h, 1Fh, 6Ah, 3Fh".
-REP: La potencia de las anteriores operaciones se vería mermada si no contáramos con una nueva instrucción, REP, que les confiere una potencia mucho mayor. Este REP es un prefijo que se antepone a la operación (por ejemplo, REP MOVSB), y que lo que hace es repetir la instrucción tantas veces como indique el registro ECX (cada vez que lo repite, se incrementará ESI, EDI o los dos según corresponda y se decrementará ECX hasta que llegue a cero, momento en que para).
La utilidad es muy grande por ejemplo cuando queremos copiar una buena cantidad de datos de un lugar a otro de memoria. Supongamos que tenemos 200h datos a transferir en un lugar que hemos marcado con la etiqueta Datos1, y que queremos trasladarlos a una zona de memoria marcada por Datos2 como etiqueta:
lea esi,Datos1 ; la utilidad de LEA está explicada más adelante; carga en ESI la dirección de Datos1. lea edi,Datos2 ; lo mismo pero con EDI. mov ecx,200h ; Cantidad de datos a copiar rep movsb ; Y los copiamos...
- STD/CLD: Aunque es de un uso escaso, hay un flag en el registro de EFLAGS que controla algo relacionado con todo esto de las instrucciones de cadena. Estamos hablando del "Direction Flag", que por defecto está desactivado; cuando así es y se lleva a cabo alguna (cualquiera) de las operaciones de cadena anteriormente especificadas, ESI y/o EDI se incrementan de la forma ya mencionada. Sin embargo, cuando este flag está a "1", activado, ESI y/o EDI en la operación se decrementan en lugar de incrementarse.
La función entonces de las dos instrucciones indicadas, STD y CLD, es la de tener un control directo sobre ésta cuestión. La órden STD significa Set Direction Flag; pone a "1" este flag haciendo que al realizarse la operación los punteros ESI y/o EDI se decremente(n). La órden
-CLD significa Clear Direction Flag, y lo pone a "0" (su estado habitual), tornando en incremental la variación del valor de los punteros al llevarse a cabo las operaciones de cadena.
LEA
El significado de LEA, es "Load Effective Adress"; calcula la dirección efectiva del operando fuente (tiene dos operandos) y la guarda en el primer operando. El operando fuente es una dirección de memoria (su offset es lo que se calcula), y el destino es un registro de propósito general. Por ejemplo:
LEA EDX, [Etiqueta+EBP]: En EDX estará la dirección de memoria a la que equivale Etiqueta + EBP (ojo, la dirección, NO el contenido).
LOOP
La instrucción LOOP es un poco como la forma "oficial" de hacer bucles en ensamblador, y el porqué de que ECX sea considerado como "el contador". Este comando tiene un sólo parámetro, una posición de memoria (que normalmente escribiremos como una etiqueta). Cuando se ejecuta comprueba el valor de ECX; si es cero no sucede nada, pero si es mayor que cero lo decrementará en 1 y saltará a la dirección apuntada por su operando.
mov ecx, 10h ; Queremos que se repita 10h veces. Bucle: > > loop Bucle
Como es lógico, el loop actuará ejecutando lo que hay entre "Bucle" y él 10h veces, cada vez que llegue al LOOP decrementando ECX en uno.
XCHG
La operación XCHG, intercambia el contenido de dos registros, o de un contenido de un registro y la memoria. Son válidos, pues:
XCHG EAX,[EBX+12]: Intercambia el valor contenido en la posición de memoria apuntada por [EBX+12], y el registro EAX.
XCHG ECX, EDI
Existe una variante, XADD, que lo que hace es intercambiarlos igual, pero al tiempo sumarlos y almacenar el resultado en el operando destino. Esto es:
XADD EAX,EBX: Lo que hará es que al final EBX valdrá EAX, y EAX, la suma de ambos.
Otras instrucciones interesantes
Instrucciones a nivel de Bit
A veces no queremos operar con bytes completos, sino quizá poner a 1 un sólo bit, a 0, o comprobar en qué estado se encuentra. Bien que esto se puede hacer utilizando instrucciones como el AND (por ejemplo, si hacemos AND AX,1 y el primer bit de AX no está activado el flag de Zero se activará, y no así en otro caso), OR (los bits a 1 de la cifra con la que hagamos el OR activarán los correspondientes del origen y un 0 hará que nada varíe), y de nuevo AND para poner a cero (los bits del operando con el AND puestos a 1 no variarán, y los 0 se harán 0...).
El caso, que tenemos instrucciones específicas para jugar con bits que nos pueden ser útiles según en qué ocasiones (un ejemplo de utilización se puede ver en el código de encriptación de mi virus Unreal, presente en 29A#4). Estas son:
-BTS: Bit Test and Set, el primer operando indica una dirección de memoria y el segundo un desplazamiento en bits respecto a esta. El bit que toque será comprobado; si es un 1, se activa el carry flag (comprobable con JC, ya se sabe) y si es 0, pues no. Además, cambia el bit, fuera cual fuese en principio, a un 1 en la localización de memoria indicada. Por ejemplo, un "BTS [eax+21],16h" contaría 16h bits desde la dirección "eax+21", comprobaría el bit y lo cambiaría por un 1.
- BTR: Bit Test and Reset, como antes el primer operando se refiere a una dirección y el segundo a un desplazamiento. Hace lo mismo que el BTS, excepto que cambia el bit indicado, sea cual sea, por un 0.
-BT: Bit Test, hace lo mismo que las dos anteriores pero sin cambiar nada, sólo se dedica a comprobar y cambiar el Carry Flag.
-BSWAP: Pues eso, Bit Swap... lo que hace es ver el desplazamiento con el segundo operando (el formato, como en las anteriores), y cambiar el bit; si era un 1 ahora será un 0 y viceversa.
-BSF: Bit Scan Forward, aquí varía un poco el tema; se busca a partir de la dirección indicada por el segundo operando para ver cuál es el bit menos significativo que está a 1. Si al menos se encuentra uno, su desplazamiento se almacenará en el primer operando. Es decir, si hacemos BSF EAX,[EBX] y en EBX tenemos la cadena 000001xxx, EAX valdrá 5 tras ejecutar esta instrucción. Tenemos también una instrucción, BSR, que hace lo mismo pero buscando hacia atrás (Bit Scan Reverse).
CPUID
Se trata de una instrucción que devuelve el tipo de procesador para el procesador que ejecuta la instrucción. También indica las características presentes en el procesador, como si tiene coprocesador (FPU o Floating Point Unit). Funciona en todo procesador a partir de 80486.
Dependiendo del valor de EAX devuelve cosas distintas:
-Si EAX = 0: Aquí lo que estamos haciendo es comprobar la marca. Por ejemplo, con un Intel, tendríamos EBX = "Genu", ECX = "ineI' y EDX = "ntel". En el caso de AMD, tendremos EBX = "Auth", ECX = "enti" y EDX = "cAMD".
-Si EAX = 1: En este caso, se intenta averiguar características de la familia del procesador. En EAX, se devuelve la información de esta, así:
31-14: Sin usar 13-12: Tipo de Procesador 11-8: Familia del Procesador 7-4: Modelo de Procesador 3-0: Stepping ID
También en la web de Intel se puede encontrar información más detallada a este respecto. Reproduzco de todas formas, una tabla con los valores de diferentes procesadores que saqué hace tiempo (es escasa y no tiene en cuenta K6-3, K7 y Pentium III, pero sirve como muestra):
|| Familia || Procesador || Modelo ||
|| 0100 || 1000 || Intel DX4 ||
|| 0101 || 0001 || Pentium (60-66 Mhz) ||
|| 0101 || 0010 || Pentium (75-200 Mhz) ||
|| 0101 || 0100 || Pentium MMX (166-200 Mhz) ||
|| 0110 || 0001 || Pentium II ||
|| 0110 || 0011 || Pentium II, model 3 ||
|| 0110 || 0101 || Pentium II model 5 y Celeron ||
|| 0101 || 0110 || K6 ||
|| 0101 || 1000 || K6-2 ||
RDTSC
Esta instrucción, significa "ReaD TimeStamp Counter". Su función, es la de devolvernos en el par EDX:EAX el "Timestamp" almacenado por el procesador. Este contador es almacenado por el procesador y se resetea cada vez que lo hace el procesador, incrementándose en una unidad cada vez que sucede un ciclo de reloj. Por lo tanto, es obvia su utilidad como generador de números aleatorios, aunque por algún motivo que no alcanzo a comprender, dependiendo de un flag en el registro interno CR4 (el flag TSD, Time Stamp Disable) esta instrucción puede ser restringida para ser ejecutada en el modo User del procesador (en la práctica, por ejemplo bajo Windows 98 la instrucción no devuelve nada pues está deshabilitada en este flag, sin embargo en Linux sí lo da...).