Introducción al lenguaje ensamblador IL de .NET [Parte 4]

[ <-- Introducción al lenguaje ensamblador IL de .NET [Parte 3] ]

Definiendo Métodos

Una vez hemos aprendido acerca de las condiciones (saltos o ramificaciones), bucles y variables, es hora de ver en profundidad cómo son declarados los métodos en ILAsm. La forma de declarar métodos en ILAsm es muy parecida a la usada para hacerlo en lenguajes como C# o C++, excepto algunos pequeños detalles. Bien, veamos entonces un código de ejemplo para analizar en detalle qué hemos hecho:

//Metodos.il
//Pruebas creando métodos

.assembly extern mscorlib {}

.assembly Metodos
{
    .ver 1:0:1:0
}
.module Metodos.exe

.method static void main() cil managed
{
    .maxstack 2
    .entrypoint

    ldc.i4 10
    ldc.i4 20
    call int32 RealizarSuma(int32, int32)
    call void EscribirSuma(int32)
    ret
}

.method public static int32 RealizarSuma(int32, int32) cil managed
{
    .maxstack 2    

    ldarg.0
    ldarg.1
    add

    ret
}
.method public static void EscribirSuma(int32) cil managed
{
    .maxstack 2
    ldstr "El resultado es: "
    call void [mscorlib]System.Console::Write(string)

    ldarg.0
    call void [mscorlib]System.Console::Write(int32)

    ret
}
Figura 1.8 – Metodos.il, Definir y llamar a nuestros propios métodos

Este simple programa sumará dos números (predefinidos, para simplificar el código) y mostrará el resultado. Hemos definido dos métodos aquí. Toma nota que ambos métodos son estáticos por lo que podemos acceder a ellos directamente sin crear ninguna instancia de ellos. Primero hemos cargado dos números al stack y llamado al primer método al que hemos denominado ‘RealizarSuma‘, el cual estará esperando dos valores int32 al principio del stack. Si miramos al cuerpo del método (RealizarSuma), veremos que su declaración luce igual que el método main tal y como hemos visto en ocasiones anteriores, de nuevo hemos definido la directiva .maxstack pero date cuenta que esta vez no hemos incluido la directiva .entrypoint ya que un programa únicamente puede tener un punto de entrada y, en el caso de arriba, hemos declarado ese punto en el método main (como se suele hacer). Las instrucciones ldarg.0 y ldarg.1 cargan los valores de los argumentos del método al stack. Entonces simplemente hemos usado la instrucción de suma (add) y por ultimo el método finaliza retornando al punto donde fue invocado. Date cuenta de que el método devuelve un valor de tipo int32, por lo que, ¿qué valor habrá devuelto?. Por supuesto, ese valor fue devuelto y ahora está disponible en el stack de evaluación una vez terminó el método su trabajo. A partir de aquí el método main vuelve a tener el control para llamar esta vez al método ‘EscribirSuma‘. Este método requiere también de un parámetro de tipo int32 y, ya que nuestra anterior llamada a ‘RealizarSuma‘ ya nos devolvió ese valor, ‘EscribirSuma‘ lo utilizará como parámetro. Una vez llamado al nuevo método, este escribirá en pantalla el resultado de la suma usando ldarg.0 para cargar el argumento y usar su valor.

El código analizado demuestra que crear y usar métodos no es difícil en ILAsm y bueno, de momento lo es, pero los métodos también pueden recibir valores por referencia, por lo que en la siguiente sección hablaremos de ello.

 

Pasando variables por Referencia

ILAsm soporta también el paso de parámetros por referencia ya que lo soportan también lenguajes de alto nivel en .NET. Cuando pasamos cualquier variable por referencia, no le pasamos su valor sino su dirección de memoria (puntero) para que el método pueda trabajar con ella directamente. Veamos un ejemplo de como funciona:

.method static void main() cil managed
{
    .maxstack 2
    .entrypoint
    .locals init (int32, int32)

    ldc.i4 10
    stloc.0
    ldc.i4 20
    stloc.1

    //Cargamos la dirección de memoria de la 1era variable
    ldloca 0
    //Cargamos el valor de la 2ª variable
    ldloc.1
    call void RealizarSuma(int32 &, int32)
    ldloc.0
    //Cargamos la 1era variable de nuevo, pero esta vez su valor
    call void [mscorlib]System.Console::WriteLine(int32)
    ret
}
.method    public static void RealizarSuma(int32 &, int32) cil managed
{
    .maxstack 2
    .locals    init (int32)
    //Buscamos en memoria y copiamos su valor
    ldarg.0
    ldind.i4
    stloc.0
    ldloc.0
    //Realizamos la suma
    ldarg.1
    add
    stloc.0

    ldarg.0
    ldloc.0
    stind.i4

    ret
}
Figura 1.9 – VariablesPorRef.il, Pasando variables por referencia

El punto interesante en el código es el uso de algunas nuevas instrucciones como ldloca, la cual carga la dirección de memoria de una variable, en vez de su valor, al stack. En el método principal (main), hemos declarado dos variables (locales) y asignado unos valores (10 y 20 respectivamente). Entonces hemos cargado la dirección de memoria de la primera variable en la memoria y el valor de la segunda, para luego invocar el método ‘RealizarSuma‘. Notarás que se ha usado ‘&’ con el primer parametro int32, lo cual significa que el valor contendrá su referencia en memoria y no su valor para poder pasar tal variable por referencia. De la misma manera, en la definición del método ‘RealizarSuma’ encontraremos también el mismo símbolo ‘&’. Por lo que, tenemos una variable que es pasada por referencia y una segunda variable por valor. Ahora el problema está en que tenemos que acceder a esa dirección de memoria para poder acceder a su valor y modificarlo si fuese necesario. Para solucionar esto, hemos almacenado al stack el primer argumento (el cual contiene la dirección de memoria) y utilizado la instrucción ldind.i4, la cual carga el valor de un entero (32 bit) al stack utilizando una dirección en memoria dada. Hemos almacenado ese valor en una variable local para que podamos reutilizarla fácilmente (o tendríamos que hacer este paso varias veces). Entonces hemos cargado esa variable local y el segundo argumento (el cual ya va por valor) al stack, sumados ambos y guardados en la misma variable local. Por ultimo tenemos algo interesante, y es modificar el valor de esa variable pasada por referencia con el valor del resultado. Para ello hemos usado la instrucción ldind.i4, la cual modifica el valor de una variable en la dirección de memoria indicada en el stack (es decir, lee qué dirección de memoria tiene que acceder y escribe un valor). Una vez hecho esto, para testear que todo ha ido bien, no devolveremos el resultado con el método, en vez de esto, ya que hemos almacenado el resultado en la variable pasada por referencia, usaremos en el método main esa misma variable para escribir el resultado utilizando el método ‘WriteLine‘.

Esta es la forma en la que en ILAsm se utilizan variables por referencia. Hasta aquí, hemos visto formas de declarar variables, condiciones, bucles y por último métodos (con ambos tipos de parámetros). En la próxima parte entraremos de lleno con los espacios de nombre (namespaces a partir de ahora) y las clases, es decir, con la parte orientada a objetos de ILAsm.

¡Hasta la próxima entrega!

Advertisement

One Response to Introducción al lenguaje ensamblador IL de .NET [Parte 4]

  1. Pingback: Introducción al lenguaje ensamblador IL de .NET [Parte 3] « El Blog de JuDelCo

Deja un comentario

Fill in your details below or click an icon to log in:

Logo de WordPress.com

You are commenting using your WordPress.com account. Log Out / Cambiar )

Twitter picture

You are commenting using your Twitter account. Log Out / Cambiar )

Facebook photo

You are commenting using your Facebook account. Log Out / Cambiar )

Connecting to %s

Seguir

Get every new post delivered to your Inbox.

Únete a otros 228 seguidores