FUNCIONES EN ASSEMBLY
En Assembly es posible utilizar funciones del mismo modo que se utilizan en cualquier lenguaje estructurado como C/C++ o Pascal, su funcionamiento es simple, solamente basta con definir el nombre de la función y posteriormente invocarla con la instrucción call. En este sentido es bastante similar al concepto de segmento de ejecución, sin embargo, tiene la diferencia de que se ejecuta de forma independiente del flujo principal del programa (a diferencia de un segmento de ejecución que se define en algún punto del flujo principal) por esta razón, el uso de funciones resulta conveniente para separar instrucciones del flujo de ejecución e invocarlas solamente cuando resulte conveniente.
La sintaxis de una función en assembly es la siguiente:
.type NombreFuncion, @function
NombreFuncion:
<codigo>
<codigo>
ret
Para invocar una función, como se ha mencionado anteriormente, es por medio de la instrucción call, en este caso, desde un segmento de ejecución podría ejecutarse la instrucción call NombreFuncion para que se ejecute su lógica.
Un ejemplo básico del uso de funciones en assembly se lista a continuación.
#Ejemplo del uso de funciones en Assembly..dataHelloWorld:
.asciz «Hello Assembly\n» HelloFunction: .asciz «Hello Function» .text .globl _start .type MyFunction, @function MyFunction: #Establece la funcion write() para ser invocada por el sistema, la cadena y su longitud son establecidos desde el punto de llamada de esta funcion movl $4, %eax movl $1, %ebx int $0x80 ret _start: leal HelloWorld, %ecx movl $16, %edx call MyFunction #Se imprime la variable HelloWorld desde la funcion. leal HelloFunction, %ecx movl $14, %edx call MyFunction # Se imprime la variable HelloFunction desde la funcion. ExitCall: movl $1, %eax movl $0, %ebx int $0x80 |
La ejecución y depuración del programa es igual a como se ha visto en los tópicos anteriores, en este caso cuando se llegue a la instrucción call MyFunction, el register EIP a puntará a la localización de memoria que apunta a la primera instrucción de la función:
Breakpoint 1, _start () at FunctionsParams.s:30
30 call MyFunction
(gdb) print /x $rip
$3 = 0x4000e2
(gdb) x /1xw $rip
0x4000e2 <_start+23>: 0xffffc9e8
Dump of assembler code for function MyFunction:
0x00000000004000b0 <+0>: mov $0x4,%eax
0x00000000004000b5 <+5>: mov $0x1,%ebx
0x00000000004000ba <+10>: mov 0x60012c,%ecx
0x00000000004000c1 <+17>: mov 0x600130,%edx
0x00000000004000c8 <+24>: int $0x80
0x00000000004000ca <+26>: retq
End of assembler dump.
(gdb) s
MyFunction () at FunctionsParams.s:18
18 movl $4, %eax
(gdb) print /x $rip
$4 = 0x4000b0
Como puede apreciarse apreciarse al ejecutar el desensamblado de la función, la primera localización de memoria es 0x4000b0 y posteriormente el register EIP que es el encargado de apuntar a la instrucción actual de ejecución se actualiza con el valor correspondiente a 0x4000b0
Pasando parámetros y retornando valores a funciones Assembly
Para pasar parámetros a una función en assembly existen 3 mecanismos:
- Por medio de registers
- Localizaciones globales de memoria
- StackPor otro lado, los mecanismos de retorno son 2:
- Por medio de registers
- Localizaciones globales de memoriaEjemplo de código de uso de Funciones con parámetros y retorno.
#Ejemplo de funciones con parametros.dataHelloWorld:
.asciz «Hello World» HelloFunction: .asciz «Hello Function» .bss .lcomm StringPointer, 4 .lcomm StringLength, 4 .text .globl _start .type MyFunction, @function MyFunction: movl $4, %eax movl $1, %ebx movl StringPointer, %ecx movl StringLength, %edx int $0x80 ret _start: nop #Imprime el valor de la variable HelloWorld movl $HelloWorld, StringPointer movl $11, StringLength call MyFunction movl $HelloFunction, StringPointer movl $17, StringLength call MyFunction ExitCall: movl $1, %eax movl $0, %ebx int $0x80 |
En este caso, los parámetros se establecen por medio de la variable global StringPointer y StringLength, que son establecidos justo antes de invocar la función y sin embargo, son utilizados por la función en el momento que esta lo requiere, con la posición de memoria adecuada. Esta misma lógica aplica para los valores de retorno, donde se pueden establecer, dentro de la función, valores para las variables o los registers y posteriormente ser utilizados por el flujo del programa que ha invocado la función.