Hay un registro especial llamado rflags.
Sus bits actúan como banderas para diversas condiciones.
Algunas de ellas se enumeran a continuación:
| nombre | símbolo | bit |
|---|---|---|
| acarreo | CF | 0 |
| cero | ZF | 6 |
| signo | SF | 7 |
| desbordamiento | OF | 11 |
Para ver la lista completa, consulta el Manual de Intel.
Las banderas de rflags no se modifican directamente.
En su lugar, las establecen muchas instrucciones distintas.
Por ejemplo, muchas operaciones aritméticas o bit a bit establecen ZF cuando el resultado es cero.
Una de las instrucciones más comunes para comprobar condiciones es cmp.
Recibe dos operandos y actualiza las banderas, pero no modifica sus operandos.
La instrucción cmp resta el segundo operando al primero y establece las banderas según el resultado.
Si A es el primer operando y B el segundo:
| bandera | se establece cuando |
|---|---|
| CF | A < B (sin signo) |
| ZF | A == B |
| SF | A < B (con signo, sin desbordamiento) |
| OF | hay desbordamiento en la resta con signo |
Por defecto, el código en x86-64 se ejecuta secuencialmente de arriba abajo.
Pero hay muchas situaciones en las que es necesario modificar este comportamiento. Por ejemplo, para ejecutar un conjunto distinto de instrucciones en respuesta a una condición.
En los lenguajes de alto nivel, esto se suele hacer con abstracciones como los condicionales if...else.
Sin embargo, en x86-64 no existen.
En su lugar, x86-64 proporciona instrucciones que transfieren de hecho la ejecución a otro punto del código.
A esto se le llama branching.
Ya hemos visto dos de estas instrucciones: call y ret.
Cuando se llama a una función, la ejecución se transfiere de quien llama a la función llamada. Y, al volver, la ejecución se transfiere de nuevo a quien llamó.
Si no se encuentra un ret, la ejecución cae de una función a la siguiente.
Esto a veces puede usarse para optimizar el flujo del código.
La instrucción jmp transfiere la ejecución del programa a otro punto del código de forma incondicional. Su único operando es una etiqueta que contiene la dirección del punto donde continuará la ejecución.
Considera, por ejemplo, la siguiente función:
fn:
mov rax, 5
jmp end
add rax, 10
end:
ret
Cuando se llama a fn, la ejecución comienza en mov rax, 5.
Esto establece el valor de rax en 5 en ese punto.
La siguiente instrucción es jmp end, que transfiere la ejecución a la etiqueta end.
Después de end, la siguiente instrucción es ret, que transfiere la ejecución de vuelta a la función que llamó.
Fíjate en que, como add rax, 10 está situada después de jmp end y antes de end, nunca se ejecuta.
El valor de rax cuando fn devuelve el control es 5.
La familia de instrucciones jcc transfiere la ejecución del programa a otro punto solo si se cumple una condición concreta. En caso contrario, la ejecución continúa de forma secuencial.
Cada condición se corresponde con una o varias banderas de rflags.
Algunas variantes de jcc comprueban que una bandera está establecida; otras comprueban que está a cero.
El cc de jcc no es literal, sino que hace referencia al sufijo concreto asociado a la bandera que se comprueba.
Hay muchos sufijos y muchos de ellos comprueban la misma condición que otro. Algunos se refieren directamente a una bandera, de modo que la instrucción salta a una etiqueta si esa bandera está establecida:
| sufijo | salta si |
|---|---|
| z | ZF == 1 |
| c | CF == 1 |
| s | SF == 1 |
| o | OF == 1 |
Muchos otros se eligen para referirse a su significado en una instrucción cmp.
Por ejemplo:
| instrucción | sufijo | salta si |
|---|---|---|
| cmp A, B | e | A == B |
| cmp A, B | l | A < B (con signo) |
| cmp A, B | b | A < B (sin signo) |
| cmp A, B | g | A > B (con signo) |
| cmp A, B | a | A > B (sin signo) |
Es posible añadir e después de l, b, g o a para incluir la igualdad en la condición:
cmp rcx, r8
jge two ; this jumps to 'two' if rcx is greater than, or equal to, r8 in a signed comparison
jbe two ; this jumps to 'two' if rcx is lesser than, or equal to, r8 in an unsigned comparison
Para todos los sufijos hay variantes que comprueban el comportamiento opuesto.
Tienen la misma sintaxis, pero con una n delante del sufijo.
Por ejemplo, jnz salta cuando ZF no está establecida.
De forma similar, jnae salta cuando A no es >= B (A y B interpretados como enteros sin signo).
Algunos sufijos son alias de las mismas condiciones.
Por ejemplo, jz (sufijo z, por ZF) y je (sufijo e, por «igualdad») saltan ambos cuando ZF está establecida.
Esto se debe a que, con cmp, ZF se establece cuando la resta da cero, lo que corresponde a que los dos operandos sean iguales.
Otros sufijos, en cambio, comprueban una combinación de banderas y no se pueden sustituir directamente por el sufijo de una sola bandera.
Prefiere el sufijo que describa mejor la semántica de tu comparación.
Las etiquetas son visibles en todo el archivo fuente; no son locales a una función. Por ello, es imposible reutilizar el nombre de una etiqueta.
Para imitar el comportamiento de una etiqueta local, NASM tiene una notación especial para las etiquetas declaradas con un punto (.) delante.
Esta notación define una etiqueta que incluye de forma implícita el nombre de la etiqueta anterior sin punto:
section .text
fn1:
...
.example: ; this is fn1.example
...
ret
fn2:
...
.example: ; this is fn2.example
...
ret
Aún es posible saltar a esta etiqueta desde cualquier punto del código usando el nombre completo de la etiqueta; por ejemplo, jmp fn1.example.
Sin embargo, un salto que use la parte de la etiqueta a partir del punto se dirigirá a la etiqueta dentro de la función superior.
Por ejemplo, .example se comporta como si fuera local a la función:
section .text
fn1:
...
.example:
...
jmp .example ; this jumps to fn1.example
fn2:
...
.example:
...
jmp .example ; this jumps to fn2.example
Fíjate en que una etiqueta sin punto dentro de una función define, en la práctica, otra función:
section .text
fn1:
...
.example: ; this is 'fn1.example'
...
non_dotted:
...
.example: ; this is 'non_dotted.example'
...
ret