FLUJO INCONDICIONAL DE UN PROGRAMA EN ASSEMBLY
Las instrucciones que se encuentran en esta categoría corresponden a aquellas que no se encuentran limitadas por instrucciones condicionales para que se su ejecución se lleve a cabo.
El siguiente es el programa de ejemplo que se usará para la demostración de los tópicos relacionados con el flujo incondicional de un programa en assembly, que como en casos anteriores se depurará paso a paso para su correcto entendimiento.
#Ejemplo de un programa usando instrucciones de flujo incondicional. .data HelloWorld: .asciz «Hello Assembly» CallDemo: .asciz «Call Demo» .text .globl _start nop _start: #Escribir contenido de HelloWorld usando la funcion write movl $4, %eax movl $1, %ebx movl $HelloWorld, %ecx movl $14, %edx int $0x80 call Calling jmp Exit Calling: movl $4, %eax movl $1, %ebx movl $CallDemo, %ecx movl $9, %edx int $0x80 ret Exit: #Instruccion para salir del programa. movl $1, %eax movl $0, %ebx int $0x80 |
INSTRUCCIÓN JUMP (JMP):
Esta instrucción se encarga de transferir el flujo del control del programa a un segmento del mismo identificado con una etiqueta que lo declara de forma única, de este modo el register EIP (que como se ha indicado anteriormente es el register que controla la ejecución de un programa en Assembly) apunta a la dirección de memoria del segmento invocado, esta instrucción es equivalente a la instrucción GOTO de C/C++
En un proceso de depuración del programa anteriormente listado, se procede a poner un punto de interrupción en la linea jump Exit, el resultado de la ejecución es el siguiente, utilizando GDB para la depuración del programa:
#Plataforma 64 bits.
(gdb) print $rip
$4 = (void (*)()) 0x4000cc <_start+27>
(gdb) disas _start
Dump of assembler code for function _start:
0x00000000004000b1 <+0>: mov $0x4,%eax
0x00000000004000b6 <+5>: mov $0x1,%ebx
0x00000000004000bb <+10>: mov $0x6000f4,%ecx
0x00000000004000c0 <+15>: mov $0xe,%edx
0x00000000004000c5 <+20>: int $0x80
0x00000000004000c7 <+22>: callq 0x4000ce <Calling>
=> 0x00000000004000cc <+27>: jmp 0x4000e5 <Exit>
Como puede apreciarse, después de ejecutar el print $rip, la ejecución actual del programa se encuentra en la instrucción jmp, la cual apunta a la posición de memoria 0x4000cc, este a su vez apunta al segmento “Exit” (0x4000e5) el cual será el objetivo para la transferencia de control del programa por parte de la instrucción jmp y cuya posición en memoria es 0x4000e5
Al continuar con la ejecución del programa podemos apreciar que la instrucción jmp hace justo lo que se espera que haga, es decir, transferir el control del programa al segmento Exit
(gdb) print $rip
$4 = (void (*)()) 0x4000e5 <Exit>
(gdb) disas Exit
Dump of assembler code for function Exit:
=> 0x00000000004000e5 <+0>: mov $0x1,%eax
0x00000000004000ea <+5>: mov $0x0,%ebx
0x00000000004000ef <+10>: int $0x80
End of assembler dump.
instrucción CALL
Esta instrucción es similar a lo que en lenguajes como C/C++ se conoce como la llamada a una función o procedimiento. A diferencia de la instrucción jmp, una vez el segmento de memoria ha finalizado su ejecución, este retorna el control a la posición de memoria desde donde se ha invocado. Por otro lado, el segmento que es invocado por la instrucción CALL, siempre tiene asociado una instrucción RET, que tiene un efecto similar es similar a la palabra reservada “return” en lenguajes como C/C++
Para probar el funcionamiento de esta característica, se implementa un punto de ruptura en la linea call Calling .
(gdb) disas _start
Dump of assembler code for function _start:
0x00000000004000b1 <+0>: mov $0x4,%eax
0x00000000004000b6 <+5>: mov $0x1,%ebx
0x00000000004000bb <+10>: mov $0x6000f4,%ecx
0x00000000004000c0 <+15>: mov $0xe,%edx
0x00000000004000c5 <+20>: int $0x80
=> 0x00000000004000c7 <+22>: callq 0x4000ce <Calling>
0x00000000004000cc <+27>: jmp 0x4000e5 <Exit>
End of assembler dump.
Como puede apreciarse, callq, efectuará la transferencia de control a la posición de memoria 0x4000ce que se encuentra etiquetada con <Calling>
(gdb) disas Calling
Dump of assembler code for function Calling:
0x00000000004000ce <+0>: mov $0x4,%eax
0x00000000004000d3 <+5>: mov $0x1,%ebx
0x00000000004000d8 <+10>: mov $0x600103,%ecx
0x00000000004000dd <+15>: mov $0x9,%edx
0x00000000004000e2 <+20>: int $0x80
0x00000000004000e4 <+22>: retq
End of assembler dump.
(gdb) print /x $esp
$1 = 0xffffe250
(gdb) print /x $rip
$3 = 0x4000c7
(gdb) s
Call DemoCalling () at FlujoIncondicional.s:28
28 ret
Llegados hasta este punto, se ha ejecutado la función write del sistema operativo con los argumentos especificados y se ha impreso en pantalla el valor de la variable, antes de ejecutar la instrucción RET, examinamos la memoria:
(gdb) print $rip
$5 = (void (*)()) 0x4000e4 <Calling+22>
Despues de ejecutar la instrucción RET:
(gdb) s
_start () at FlujoIncondicional.s:20
20 jmp Exit
(gdb) print $rip
$7 = (void (*)()) 0x4000cc <_start+27>
Ha retornado al punto de llamada, y ahora el register EIP estará ubicado justo en la siguiente posición de memoria, es decir en la linea jmp Exit, que corresponde a la posición de memoria 0x4000cc