a rtesanías
in italiano in english
artículos descargas cursos
Un taller en desarrollo para la construcción de futuras aplicaciones para MenuetOS y la divulgación de la programación en lenguaje ensamblador.

TOMANDO CONFIANZA:

Bienvenidos a éste segundo curso de programación en ensamblador. Utilizaremos las nociones aprendidas y las expandiremos hasta lograr un nivel de confianza adecuado con la plataforma de desarrollo.

Poco a poco nos vamos enterando de las peculiaridades del sistema operativo y su interacción con nuestras creaciones. Existen reglas que debemos respetar para que nuestros programas funcionen como esperamos y además existen normas y prácticas comunes que debemos seguir para que dichos programas se comporten como los usuarios esperan. Además damos la oportunidad a otros programadores de entender más fácil nuestro código y de ayudarnos con la depuración de errores.

Vamos a entrar en materia. Por ahora descarguemos el paquete de trabajo de éste segundo curso.


Paquete de entrenamiento tut02.zip (26 kB)


Sugerimos descargarlo en el mismo directorio del primer paquete, pues se trata de un complemento.

En el paquete se encuentra una pequeña utilidad para examinar el contenido de los archivos PE, con cada una de sus partes desglosadas.

Mayo 5 de 2004


En éste curso vamos a utilizar un poco de la potencia del fasm con el objetivo de simplificarnos la vida.

En el pasado curso, vimos como funcionaba una tabla de funciones a importar. Aprendimos a construir una y podemos adivinar que es un proceso bastante repetitivo y podría ser automatizado.
Para lograrlo, se usa el sistema de macros. Es un sistema tan potente que nos permite hacer las cosas más inesperadas. Por ejemplo, éstos cursos fueron escritos enteramente con macros del fasm para producir HTML en forma automatizada.

En sustancia, cuando uno tiene que escribir código repetitivo o que cumple con un esquema particular, el fasm nos facilita la escritura por medio del mecanismo de macros. Para usarlo, definimos una palabra clave que represente ese esquema y fasm escribirá por nosotros ése código, siguiendo nuestras instrucciones.

Usando macros podremos hacer que nuestro código sea más legible pero manteniendo el control en nuestras manos. Es decir, podremos crear nuestra propia sintaxis, un lenguaje propio ajustado a nuestras necesidades, para ahorrar tiempo y fatiga.

El sistema es muy versátil y por lo tanto puede parecer bastante complejo, por lo tanto es mejor que vamos al ejemplo. Compáralo con el ejemplo del curso anterior.

Listado de tut02es1.asm
format PE GUI

MB_OK              = 00h
MB_YESNO           = 04h
MB_ICONEXCLAMATION = 30h

include 'macros.inc'

section '.code' code readable executable

call MessageBox,0,_message,_caption,MB_OK
call ExitProcess,0

section '.data' data readable writeable

_caption db 'Ensamblando programas para Win32',0

_message db 'Con macros ahorramos tiempo y fatiga.',0


section '.idata' import data readable

library kernel,'KERNEL32.DLL',\
        user,'USER32.DLL'

import kernel,ExitProcess,'ExitProcess'

import user,MessageBox,'MessageBoxA''
Nuestro programa hace lo mismo que hacía el curso anterior, pero ahora luce más simple. Con éstas pocas líneas logramos el mismo efecto que con todo el código del curso precedente.
Hemos cambiado el aspecto del lenguaje, pero no su resultado final.
include
La directiva include es una herramienta poderosisima, que nos permite dividir nuestro código en varios archivos. En el ejemplo, la usamos para tener el código que contiene las macros en un archivo separado del programa en el que estamos trabajando. La idea es poder utilizar las mismas macros en cualquier otro programa que hagamos, con solo incluir el nombre del archivo.
Además, el programa es más legible porque cada archivo contiene solo lo que esté relacionado y capture nuestra atención en un determinado momento.
Ésta directiva hace que Fasm lea el archivo dado y lo inserte justo en éste punto antes de interpretarlo. El efecto es equivalente a tener un solo archivo con todo el contenido.

Notamos varios cambios importantes:
1. desaparecieron las instrucciones push
2. la instrucción call ahora recibe los valores y
3. la sección '.idata' se simplificó mucho.

Los cambios se deben al uso de macros. Éstos 3 puntos serán tratados en la primera parte del curso.

Usando macros tenemos la posibilidad de adaptar la forma de escribir el código de acuerdo a nuestros gustos y necesidades. Optimizarlo para poder reutilizar la mayor parte de lo que vayamos escribiendo.
La ventaja que se logra es en la lectura y depuración del código. Mientras más fácil sea retornar a la idea original de la cual se originó el código, nos será más facil encontrar los posibles errores o mejorar las funciones existentes.

Poco a poco iremos viendo ideas para lograr un código legible. Cada persona va desarrollando un propio estilo, aunque es importante mantener un respeto por ciertas reglas que nos permiten compartir nuestro código con la comunidad.

Ahora veamos cómo podemos construir macros y más adelante aprenderemos a modificarlas para adaptarlas a nuestro estilo de programación.

Listado de macros.inc
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
 }

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
 }

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
}
library

Ésta palabra la "inventamos" nosotros, no hace parte del ensamblador. Le dimos éste nombre por conveniencia, para poderlo recordar fácilmente.
Se encargará de construir la parte de la tabla que contiene los nombres de las librerías.
import

Ésta palabra también la escogimos con la intención de recordar el trabajo que la macro hace.
Análogamente a la anterior, se encargará de construir la parte de la tabla que contiene los nombres de las funciones a importar.
call

¿cómo hace Fasm para saber que se trata de una macro y no de la instrucción call que vimos en el curso anterior?
La respuesta es simple: no lo llega a saber nunca. Fasm interpreta primero las macros, reemplazandolas por su contenido. Solo después se encarga de las instrucciones.
Lo que hacemos en realidad es cambiar un poco el significado que nosotros atribuimos a call para que nos empuje primero los valores necesarios en la pila ántes de llamar la función.

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:
API.MessageBox = 1

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.


Retroalimentación:

Para nosotros es sumamente importante conocer sus preguntas y comentarios acerca de ésta página para poder mejorar los contenidos del curso. Responderemos y clasificaremos los comentarios con la mayor brevedad posible.

Gracias



Los programas distribuidos en éste sitio son gratuitos para cualquier uso.
Todos los derechos están reservados por sus respectivos autores. El material educativo y los artículos están portegidos por la
Licencia Artística Artesanal.
® 2004 Artisan Shop