FLUJO CONDICIONAL DE UN PROGRAMA EN ASSEMBLY
Existen una serie de instrucciones que consisten en la ejecución de una determinada rutina dependiendo de que se cumplan ciertas condiciones para su ejecución. En Assembly, algunas de estas instrucciones están relacionadas con las Flags contenidas en el register EFLAGS, de este modo cada una de estas instrucciones verifican si dicho register tiene establecida una determinada bandera y en función de dicha verificación, ejecutar o no una rutina de código, lo que en lenguajes como Pascal, C/C++ o Java se conoce como una instrucción IF, pero aquí es un poco mas compleja.
Las instrucciones de flujo condicional basadas en las banderas del register EFLAGS son:
JA: Jumping Above
JAE: Jumping Above Equal
JE: Jumping Equal
JZ: Jumping Zero
JNZ: Jumping No Zero
Estas instrucciones, como se ha indicado anteriormente dependen directamente de las banderas contenidas en el register EFLAGS, tales como:
ZF: Zero Flag
PF: Parity Flag
OF: Overflow Flag
SF: Sign Flag
CF: Carry Flag
En orden de usar un salto condicional es necesario tener una operación que establezca adecuadamente las banderas del register EFLAGS apropiadamente.
Como en las entradas anteriores, utilizamos el siguiente programa de ejemplo para comprender de un modo mas detallado
#Ejemplo de instrucciones para el Flujo condicional.dataHelloWorld:
.asciz «Hello Assembly» ZeroFlagSet: .asciz «Zero Flag Set» ZeroFlagNotSet: .asciz «Zero Flag Not Set» .text .globl _start _start: nop movl $10, %eax jz FlagSetPrint FlagNotSetPrint: movl $4, %eax movl $1, %ebx leal ZeroFlagNotSet, %ecx movl $17, %edx int $0x80 jmp ExitCall FlagSetPrint: movl $4, %eax movl $1, %ebx leal ZeroFlagSet, %ecx movl $13, %edx int $0x80 jmp ExitCall ExitCall: call PrintHelloWorldTenTimes movl $1, %eax movl $0, %ebx int $0x80 PrintHelloWorldTenTimes: movl $10, %ecx PrintTenTimes: pushl %ecx movl $4, %eax movl $1, %ebx leal HelloWorld, %ecx movl $14, %edx int $0x80 popl %ecx LOOP PrintTenTimes |
Depurando el programa anterior, se establece un punto de interrupción en la linea movl $10, %eax se obtiene lo siguiente:
(gdb) info registers
rax 0x0 0
rbx 0x0 0
………..
rip 0x4000b1 0x4000b1 <_start+1>
eflags 0x202 [ IF ]
Como se aprecia en la información de los registers anteriores, el register EFLAGS no tiene establecida la bandera correspondiente ZF, por este motivo, la instrucción siguiente, no ejecutará el código correspondiente al segmento denominado FlagSetPrint dado que la condición no se cumple
(gdb) s
16 jz FlagSetPrint
(gdb) s
FlagNotSetPrint () at FlujoCondicional.s:19
19 movl $4, %eax
Ahora, dado que el flujo del programa, no ha entrado por el segmento FlagSetPrint, continua hacia el siguiente segmento del programa que es: FlagNotSetPrint.
Ahora el programa anterior será un poco modificado con el fin de establecer el ZF, de este modo, se adiciona el uso de la instrucción XORL como se detalla en el listado del programa:
#Ejemplo de instrucciones para el Flujo condicional.dataHelloWorld:
.asciz «Hello Assembly» ZeroFlagSet: .asciz «Zero Flag Set» ZeroFlagNotSet: .asciz «Zero Flag Not Set» .text .globl _start _start: nop movl $10, %eax xorl %eax,%eax #Esta comparacion entre registers siempre será verdadera, por este motivo, se establecerá automáticamente la bandera ZF al register EFLAGS. jz FlagSetPrint FlagNotSetPrint: movl $4, %eax movl $1, %ebx leal ZeroFlagNotSet, %ecx movl $17, %edx int $0x80 jmp ExitCall FlagSetPrint: movl $4, %eax movl $1, %ebx leal ZeroFlagSet, %ecx movl $13, %edx int $0x80 jmp ExitCall ExitCall: movl $1, %eax movl $0, %ebx int $0x80 |
En la depuración del programa, se establece un punto de interrupción en la linea xorl %eax,%eax y el resultado es el siguiente
(gdb) s
17 jz FlagSetPrint
(gdb) info registers
rax 0x0 0
rbx 0x0 0
…………….
rip 0x4000b8 0x4000b8 <_start+8>
eflags 0x246 [ PF ZF IF ]
(gdb) s
FlagSetPrint () at FlujoCondicional.s:28
28 movl $4, %eax
Como puede apreciarse, el programa ha entrado en el segmento correspondiente a FlagSetPrint, dado que la bandera ZF ha sido establecida en el register EFLAGS.
Instrucción LOOP
Esta instrucción permite la ejecución cíclica de un un conjunto de instrucciones determinado, lo que en lenguajes como C/C++, Pascal, Java, entre otros, es conocido como un bucle, normalmente una sentencia DO WHILE.
La instrucción LOOP utiliza el register ECX para almacenar el numero de ejecuciones del ciclo y se encarga de disminuir el valor almacenado en dicho register cada vez que se ejecuta una iteración de dicho ciclo, el uso de esta instrucción tiene la siguiente sintaxis:
<codigo>
movl $NUMERE, %ecx #Numero de repeticiones del ciclo.
EtiquetaCiclo:
<codigo>
<codigo>
LOOP EtiquetaCiclo
El siguiente fragmento de código expresa el uso de la instrucción LOOP
#Ejemplo de instrucciones para el Flujo condicional.dataHelloWorld:
.asciz «Hello Assembly» ZeroFlagSet: .asciz «Zero Flag Set» ZeroFlagNotSet: .asciz «Zero Flag Not Set» .text .globl _start _start: nop movl $10, %eax xorl %eax,%eax #Esta comparacion entre registers siempre sera verdadera, por este motivo, se establecera automaticamente la bandera ZF al register EFLAGS. jz PrintHelloWorldTenTimes FlagNotSetPrint: movl $4, %eax movl $1, %ebx leal ZeroFlagNotSet, %ecx movl $17, %edx int $0x80 PrintHelloWorldTenTimes: movl $10, %ecx PrintTenTimes: pushq %rcx movl $4, %eax movl $1, %ebx leal HelloWorld, %ecx movl $14, %edx int $0x80 popq %rcx LOOP PrintTenTimes jmp ExitCall |
NOTA IMPORTANTE: El código anterior depende de una plataforma de 64 bits, en el caso de que se ejecute en una arquitectura de 32 bits, es necesario cambiar la instrucción pushq y popq por pushl y popl respectivamente.
En la depuración del programa, el resultado es el siguiente, una vez establecido el punto de interrupción en la linea movl $10, %ecx
(gdb) print $ecx
$1 = 10
(gdb) s
44 movl $1, %ebx
(gdb) s
45 leal HelloWorld, %ecx
(gdb) s
46 movl $14, %edx
(gdb) s
47 int $0x80
(gdb) s
Hello Assembly48 popq %rcx
(gdb) s
PrintTenTimes () at FlujoCondicional.s:49
49 LOOP PrintTenTimes
(gdb) print $ecx
$2 = 10
(gdb) s
42 pushq %rcx
(gdb) s
PrintTenTimes () at FlujoCondicional.s:43
43 movl $4, %eax
(gdb) print $rcx
$3 = 9
………….
El ciclo continua con su marcha hasta finalizar, como ha podido verse, se ha utilizado las instrucciones pushq y popq, el motivo de esto es que dado que la linea leal HelloWorld, %ecx sobre-escribe constantemente la posición de memoria del register ECX, los valores de este permanecen invariables durante cada iteración del ciclo, lo que conlleva a un bucle infinito, por esta razón se emplean las instrucciones pushq y popq, donde la instrucción pushq almacena el valor actual del register ECX, y posteriormente la instrucción popq, reestablece el contenido de dicho register en función de la ultima ejecución de la instrucción pushq. Estas instrucciones serán explicadas con una mayor profundidad en próximas entradas.
Loops Condicionales:
Existen dos instrucciones adicionales relacionadas con la instrucción LOOP, se trata de:
LOOPZ: Ejecuta un ciclo mientras que el register ECX sea mayor que cero y la bandera ZF se encuentre establecida en el register EFLAGS
LOOPNZ: Ejecuta un ciclo mientras que el register ECX sea mayor que cero y la bandera ZF no se encuentre establecida en el register EFLAGS
Para probar su funcionamiento en el programa anterior basta con reemplazar la instrucción LOOP por LOOPZ y LOOPNZ para ver su funcionamiento.
En el primer caso, LOOPZ se ejecuta de la misma forma que con LOOP dado que la instrucción que ha invocado este segmento ha sido jz PrintHelloWorldTenTimes lo que indica que la bandera ZF ha sido establecida en el register EFLAGS.
En el segundo caso, el comportamiento es diferente, dado que la condición de que la bandera ZF no este establecida, no se cumple, el ciclo solamente se ejecuta una sola vez, y finalmente finaliza su ejecución, por la tanto las invocación a la función write solamente se invoca una vez.