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.