|
|
|
El funcionamiento de una macro en Fasm es prácticamente un "buscar y reemplazar" texto.
Todo comienza cuando utilizamos la macro en nuestro programa:
library kernel,'KERNEL32.DLL',\
user,'USER32.DLL'
|
Le decimos a Fasm que busque cada "library" y lo reemplace por todo lo que está contenido entre { y }, es decir:
macro library [name,string]
{ forward
local _label
dd RVA name,0,0,RVA _label,RVA name
common
dd 0,0,0,0,0
forward
_label db string,0
}
|
Fasm reemplaza el código para cada pareja [name,string] con los valores correspondientes. En éste caso son dos parejas y por lo tanto el código se repetirá dos veces.
Nos encontramos con una de las facultades avanzadas de las macros. Por medio de las directivas forward y common podemos decidir lo que se repite y lo que en cambio va reemplazado una sola vez.
|
|
Es más fácil de entender con el ejemplo:
dd RVA kernel,0,0,RVA _label00,RVA kernel
dd RVA user,0,0,RVA _label01,RVA user
dd 0,0,0,0,0
_label00 db 'KERNEL32.DLL',0
_label01 db 'USER32.DLL',0
|
Tres partes, la primera compuesta por código repetido dos veces, la segunda una y la tercera dos veces también.
Notamos que el resultado es la famosa tabla de librerías que vimos en el primer curso.
Donde había un "name", Fasm lo reemplazó por "kernel" la primera vez y por "user" la segunda. Lo mismo hizo con cada "string".
La directiva local hace que Fasm genere una etiqueta particular, que sea unívoca para cada una de las repeticiones. Lo hace añadiendo un número interno. Aquí suponemos que es de dos cifras para efectos visuales, pero en realidad se utilizan más cifras para evitar etiquetas repetidas.
Las directivas forward, common y local se utilizan sólo al interior de macros, no tienen ningún sentido fuera de ellas. Afectan la forma en que el texto es reemplazado y repetido en el código producido.
|
|
Análisis de código
|
|
|
|
Lo que sucede con la macro import es análogo a lo que acabamos de ver, con unas pequeñas excepciones.
Veamos la llamada a la macro con sus argumentos respectivos:
import kernel,ExitProcess,'ExitProcess'
|
La línea será interpretada siguiendo la definición de macro respectiva:
macro import name,[label,string]
{
common
name:
forward
local _label
label dd RVA _label
API.#label = 1
common
dd 0
forward
_label dw 0
db string,0
}
|
|
|
El primer argumento, "name", se encuentra por fuera de [ y de ]. Lo que pasa es que dicha macro espera recibir un nombre y luego una serie de pares "label", "string". Es decir, con los paréntesis cuadrados indicamos una serie de repetición para la macro y por lo tanto tiene que ser al final de los argumentos no seriados.
Dado que usamos sólo una función de la librería, la serie se repetirá una sola vez. Normalmente, cada programa necesita una larga lista de funciones y por consiguiente, se repite el código respectivo para cada una de ellas.
La siguiente situación es la línea de la igualdad. Se trata de un reemplazo ligeramente distinto; el operador # une el nombre de la etiqueta "API." al texto reemplazado por "label". Es decir, el resultado de la igualdad es:
La utilidad de la etiqueta formada la veremos durante la explicación de la macro siguiente.
El operador # se llama concatenador y se puede utilizar solo al interior de una definición de macro.
|
|
Análisis de código
|
|
|
|
La macro siguiente es call y nos va a permitir ampliar una serie de aspectos muy útiles.
Habíamos visto por ejemplo que se encarga de cambiar el comportamiento a una instrucción del lenguaje, es decir, personalizamos una instrucción del ensamblador.
Así hicimos la llamada:
call MessageBox,0,_message,_caption,MB_OK
|
Recordemos la definición para la macro:
macro call proc,[arg]
{
reverse
if arg eq
else
pushd arg
end if
common
if defined API.#proc
call [proc]
else
call proc
end if
}
|
Nos encontramos con otra de las directivas que sirven solo al interior de las macros: reverse. Su función es la misma de forward, solo que en orden inverso, de atrás para adelante.
La serie de repetición será [arg] y comenzará desde el último valor dado, en éste caso "MB_OK".
|
|
Aparece un grupo de directivas que permiten una compilación condicional. Al encontrarlas, Fasm verifica una condición. Si se cumple, interpreta lo que encuentra entre if y else. En caso contrario, lo que encuentra entre else y end if. La parte no interpretada es ignorada completamente.
Las directivas de compilación condicional las podemos utilizar en donde queramos, no es necesario estar dentro de la definición de una macro.
En el primer caso, la condición a verificar es si "arg" es igual a "nada". En el segundo caso, verifica si "API.MessageBox" ha sido utilizado en alguna parte del código, lo cual habíamos logradado con la macro anterior.
El resultado final será tener los valores empujados en el orden que teníamos en el curso anterior, ántes de llamar la función.
push MB_OK
push _caption
push _message
push 0
call [MessageBox]
|
Si no damos ningún argumento para "arg", la macro no empujará ningún valor a la pila. En caso contrario lo hará en orden inverso, que es exáctamente lo que queremos.
Si definimos "API.MessageBox" fue para identificar una llamada a una función importada de una librería para poderla llamar de manera indirecta.
Ambas cosas obedecen a lo que se llama la convención de llamada estándar y para comprenderla, necesitamos la teoría.
|
|
Un poco de teoría...
|
|
|
|
¿Que es una convención de llamada estándar?
Hemos visto que para llamar una función de las librerías de Windows, debemos primero haber puesto determinada información en la pila.
La función se encarga de sacar la información de la pila y una vez terminado su trabajo, retornar a nuestro programa en el punto inmediatamente después de la llamada, para seguir la ejecución.
Lo que no hemos visto es que para la función poder retornar a dicho punto, usa la pila también. Al momento de la llamada, la instrucción call del ensamblador, empuja el valor de posición de la instrucción siguiente en la pila. El valor será usado por la función para retornar al punto deseado.
La secuencia de llamada y retorno es parte del trabajo del procesador, pero la idea de usar la pila para las informaciones necesarias hace parte de una práctica común llamada Standard Calling Convention o abreviado stdcall. Es decir, varios programadores se pusieron de acuerdo para respetar una secuencia de operaciones y poder simplificar el acceso a las librerías comunes.
|
|
La secuencia de operaciones se deriva de dos secuencias utilizadas en dos famosos lenguajes de programación: el Pascal y el C.
La convención de llamadas del Pascal indica que los valores ingresen a la pila comenzando por el primero, y la función se encarga de organizar la pila retirando la información ántes de retornar. La convención del C en cambio, indica que los valores ingresen en orden inverso, empezando por el último y el programa que llama la función se encarga de organizar la pila al retornar.
Es decir, la convención estándar es una combinación de ámbas: usa el orden del C y organiza la pila al final cómo el Pascal.
CONCEPTOS RELACIONADOS:
Éste es un buen momento para ampliar éstos conceptos:
Llamadas
|
|
Conclusión
|
|
|
|
El segundo curso se acabó. Tratamos muchos conceptos que necesitan práctica ántes de ser dominados. Nos concentramos principalmente en el lenguaje de macros del Fasm y aún no hemos visto casi nada de ensamblador. Paciencia, primero necesitamos ciertas bases fundamentales.
|
|
Con lo poco que llevamos, podemos ya hacer programas elaborados en Windows, que se encarguen de las más variadas tareas, como veremos en el siguiente curso.
Sigue con: APLICACIONES.
|
|
|