Registro de estado (FLAGS) e instrucciones de comparación
Flags
Como ya vimos, hay un registro bastante especial que es el de flags; la traducción literal de esta palabra es "bandera", y lo que significa realmente es que no se toma el valor de este registro como una cantidad en sí misma, sino que cada uno de sus bits significa algo en particular según su valor sea 0 o 1. El registro EFLAGS, de 32 bits, tiene como bits más importantes los 16 menos significativos (EFLAGS viene de Extended Flags, se añadieron 16 bits para indicar algunas otras cosas).
La forma de acceder a estos registros será de forma implícita cuando hagamos saltos condicionales (por ejemplo, hemos hecho una comparación entre dos términos y saltamos si son iguales; la instrucción JE, Jump if Equal, comprobará por si misma el ZF o Zero Flag para ver si ha de saltar o no), y de forma explícita con funciones de pila como PUSHF, POPF, PUSHFD y POPFD, que serán explicadas en el apartado referente a la pila. De todas formas, indicar ya que los únicos bits que se pueden modificar con un POPF son los 11, 10, 8, 7, 6, 4, 2 y 0 (y los 12 y 13 si tenemos IOPL = 0, es decir, nivel de administrador... estos 12 y 13 indican el nivel de ejecución del procesador).
En fin, veamos qué tiene que ofrecernos este registro:
.jpg)
Desglose del registro EFLAGS
Los bits que tienen puestos un "0" como indicador, no tienen función definida y conservan siempre ese valor 0 (también sucede con el bit 1, que está a 1). Los más importantes, los vemos a continuación:
- IOPL (12 y 13): IOPL significa "I/O priviledge level", es decir, el nivel de privilegio en que estamos ejecutando. Recordemos que normalmente vamos a tener dos niveles de privilegio que llamabamos de usuario y supervisor, o ring3 y ring0. Aquí podemos ver en cuál estamos; si los dos bits están activos estamos en ring3 o usuario, y si están inactivos, en ring0 (sólo pueden modificarse estos bits de los flags si estamos en ring0, por supuesto).
-IF (9): El "Interrupt Flag", controla la respuesta del procesador a las llamadas de interrupción; normalmente está a 1 indicando que pueden haber interrupciones. Si se pone a 0 (poner a 0 se hace directamente con la instrucción CLI (Clear Interrupts), mientras que STI (Set Interrupts) lo activa), se prohibe que un tipo bastante amplio de interrupciones pueda actuar mientras se ejecuta el código del programa (viene bien en algunas ocasiones en que estamos haciendo algo crítico que no puede ser interrumpido).
-ZF (6): El Zero Flag, indica si el resultado de la última operación fue 0. Téngase en cuenta que si hemos hecho una comparación entre dos términos, se tomará como si se hubiera hecho una operación de resta; así, si los dos términos son iguales (CMP EAX, EBX donde EAX = EBX p.ej), se dará que el resultado de restarlos es 0, con lo que se activará el flag (se pondrá a 1). Tal y como sucede con CF y OF, este flag es afectado por operaciones aritméticas (ADD, SUB, etc) y de incremento/decremento (INC/DEC).
-CF (0): Carry Flag o Flag de Acarreo. En ocasiones se da un desbordamiento en la operación aritmética, esto es, que no cabe. Si nos pasamos por arriba o por abajo en una operación (p.ej, con 16 bits hacer 0FFFFh
+ 01h), este resultado no va a caber en un destino de 16 bits (el resultado es 10000h, lo cual necesita 17 bits para ser codificado). Así pues, se pone este flag a 1. Hay también un flag parecido, OF (11) (Overflow Flag), que actúa cuando en complemento a 2 se pasa del mayor número positivo al menor negativo o viceversa (por ejemplo, de 0FFFFh a 0000h o al revés). También nos interesará para ello el SF (7) o flag de signo, que estará activo cuando el número sea negativo según la aritmética de complemento a dos (en realidad, cuando el primer bit del resultado de la última operación sea un 1, lo que en complemento a 2 indica que se trata de un número negativo).
Otros, de menor importancia (se puede uno saltar esta parte sin remordimientos de conciencia), son:
-ID (21): El Identification Flag, señala si se puede modificar que se soporta la instrucción CPUID
-VM (17): Este flag controla si se está ejecutando en Virtual Mode 8086; cuando está a 0 se vuelve a modo protegido (el modo virtual 8086 se usa por ejemplo para ejecutar las ventanas Ms-Dos bajo Win32).
-DF(10): El "Direction Flag", va a indicar en qué dirección se realizan las instrucciones de cadena (MOVS, CMPS, SCAS, LODS y STOS). Estas instrucciones, que veremos más adelante, actúan normalmente "hacia adelante". Activar este flag hará que vayan "hacia atrás"; no hace falta preocuparse más por esto, ya se recordará. Tan sólo añadir, que este bit se activa directamente con la instrucción STD (Set Direction Flag), y se desactiva (se pone a 0) con la instrucción CLD (Clear Direction Flag).
-TF (8): Es el "Trap Flag" o flag de trampa; se utiliza para debugging, y cuando está activo, por cada instrucción que el procesador ejecute saltará una interrupción INT 1 (se utiliza para depuración de programas).
-AF (4): Flag de acarreo auxiliar o "Adjust Flag". Se usa en aritmética BCD; en otras palabras, pasad de él ;=)
-PF (2): Es el flag de paridad; indica si el resultado de la última operación fue par, activándose (poniéndose a 1) cuando esto sea cierto.
-VIP (20), VIF (19), RF (16), NT(14): No nos van a resultar muy útiles; para quienes busquen una referencia, sus significados son "Virtual Interrupt Pending", "Virtual Interrupt Flag", "Resume Flag" y "Nested Task".
Instrucciones de comparación
Los flags son activados tanto por las instrucciones de operación aritmética (ADD, SUB, MUL, DIV, INC y DEC) como por otras dos instrucciones específicas que describo a continuación:
-CMP: Esta es la más importante; el direccionamiento (es decir, aquello con lo que se puede operar) es el mismo que en el resto, y lo que hace es comparar dos operandos, modificando los flags en consecuencia. En realidad, actúa como un SUB sólo que sin almacenar el resultado pero sí modificando los flags, con lo que si los dos operandos son iguales se activará el flag de cero (ZF), etc. No vamos a necesitar recordar los flags que modifican, puesto que las instrucciones de salto condicional que usaremos operarán directamente sobre si el resultado fue "igual que", "mayor que", etc, destaquemos no obstante que los flags que puede modificar son OF (Overflow), SF (Signo), ZF (Cero), AF (BCD Overflow), PF (Parity) y CF (Carry).
-TEST: Tal y como CMP equivale a un SUB sin almacenar sus resultados, TEST es lo mismo que un AND, sin almacenar tampoco resultados pero sí modificando los flags. Esta instrucción, sólo modifica los flags SF (Signo), ZF (Cero) y PF (Paridad).
Saltos, y saltos condicionales
La ejecución de un programa en ensamblador no suele ser lineal por norma general. Hay ocasiones en las que querremos utilizar "saltos" (que cambien el valor del registro EIP, es decir, el punto de ejecución del programa).
Estos saltos pueden ser de dos tipos; incondicionados y condicionales.
Saltos incondicionados (JMP)
La instrucción JMP es la que se utiliza para un salto no condicional; esto, significa que cuando se ejecuta una instrucción JMP, el registro EIP que contiene la dirección de la siguiente instrucción a ejecutar va a apuntar a la dirección indicada por el JMP.
Existen básicamente tres tipos de salto:
-Salto cercano o Near Jump: Es un salto a una instrucción dentro del segmento actual (el segmento al que apunta el registro CS).
-Salto lejano o Far Jump: Se trata de un salto a una instrucción situada en un segmento distinto al del segmento de código actual.
-Cambio de Tarea o Task Switch: Este salto se realiza a una instrucción situada en una tarea distinta, y sólo puede ser ejecutado en modo protegido.
Cuando estemos programando, lo normal es que utilicemos etiquetas y saltos cercanos. En todo compilador, si escribimos la instrucción "JMP ", al compilar el fichero la etiqueta será sustituida por el valor numérico de la dirección de memoria en que se encuentra el lugar donde queremos saltar.
Saltos condicionales (Jcc)
Un "Jcc" es un "Jump if Condition is Met". La "cc" indica una condición, y significa que debemos sustituirlo por las letras que expresen esta condición.
Cuando el procesador ejecuta un salto condicional, comprueba si la condición especificada es verdadera o falsa. Si es verdadera realiza el salto como lo hacía con una instrucción JMP, y en caso de ser falsa simplemente ignorará la instrucción.
A continuación se especifican todos los posibles saltos condicionales que existen en lenguaje ensamblador. Algunas instrucciones se repiten siendo más de una forma de referirse a lo mismo, como JZ y JE que son lo mismo (Jump if Zero y Jump if Equal son equivalentes). En cualquier caso hay que tener lo siguiente en cuenta:
-Las instrucciones de salto condicional más comunes son JE (Jump if Equal), JA (Jump if Above) y JB (Jump if Below), así como las derivadas de combinar estas (por ejemplo, una N entre medias es un Not, con lo que tenemos JNE, JNA y JNB... por otro lado, tenemos JAE como Jump if Above or Equal o JBE, Jump if Below or Equal)
-Puede resultar extraño el hecho de que hay dos formas de decir "mayor que" y "menor que". Es decir, por un lado tenemos cosas como JB (Jump if Below) y por otro JL (Jump if Less). La diferencia es que Below y Above hacen referencia a aritmética sin signo, y Less y Greater hacen referencia a aritmética en complemento a dos.
-Hay un tercer tipo de salto condicional, que comprueba directamente el estado de los flags (como pueda ser el de paridad). Entre ellos incluímos también dos especiales; uno que considera si salta dependiendo de si el valor del registro CX es 0 (JCXZ) y otro que considera si el valor de ECX es 0 (JECXZ).
|
Instrucción |
Descripción |
Flags |
|
Aritmética sin signo |
|
JZ, JE |
Jump if Zero, Jump if Equal |
ZF = 1 |
|
JNE, JNZ |
Jump if Not Equal, Jump if Not Zero |
ZF = 0 |
|
JA |
Jump if Above |
CF = 0 and ZF = 0 |
|
JNA, JBE |
Jump if Not Above, Jump if Below or Equal |
CF = 1 or ZF = 1 |
|
JNC, JNB, JAE |
Jump if Not Carry, Jump if Not Below, Jump if Above or Equal CF = 0 |
|
JNBE |
Jump if Not Below or Equal |
CF = 0 and ZF = 0 |
|
Aritmética en complemento a 2 |
|
JNAE, JB, JC |
Jump if Not Above or Equal, Jump if Below, Jump if Carry CF = 1 |
|
JGE, JNL |
Jump if Greater or Equal, Jump if Not Less |
SF = OF |
|
JL, JNGE |
Jump if Less, Jump if Not Greater or Equal |
SF <> OF |
|
JLE, JNG |
Jump if Less or Equal, Jump if Not Greater |
ZF = 1 or SF <> OF |
|
JNG, JLE |
Jump if Not Greater, Jump if Less or Equal |
ZF = 1 or SF <> OF |
|
JNGE, JL |
Jump if Not Greater or Equal, Jump if Less |
SF <> OF |
|
JNL, JGE |
Jump if Not Less, Jump if Greater or Equal |
SF = OF |
|
|
|
|
|
|
|
JNLE, JG |
Jump if Not Less or Equal, Jump if Greater |
ZF = 0 and SF = OF |
|
Comprobación directa de flags |
|
JNO |
Jump if Not Overflow |
OF = 0 |
|
JNP |
Jump if Not Parity |
PF = 0 |
|
JNS |
Jump if Not Sign |
SF = 0 |
|
JO |
Jump if Overflow |
OF = 1 |
|
JP, JPE |
Jump if Parity, Jump if Parity Even |
PF = 1 |
|
JPO |
Jump if Parity Odd |
PF = 0 |
|
JS |
Jump if Sign |
SF = 1 |
|
JCXZ |
Jump if CX is 0 |
CX = 0 |
|
JECXZ |
Jump if ECX is 0 |
ECX = 0 |