Introducción al lenguaje ensamblador IL de .NET [Parte 2]
16 diciembre 2011 2 comentarios
[ <-- Introducción al lenguaje ensamblador IL de .NET [Parte 1] ]
El Stack de evaluación
El stack de evaluación puede ser considerado como un stack normal como otro cualquiera, sin embargo este stack es únicamente usado para guardar información justo antes de ejecutar una instrucción. Sabemos que la información guardada en memoria cuando podemos tener acceso a realizar alguna operación sobre ella. Lo mismo pasa si movemos esa información a los registros en un lenguaje ensamblador antes de invocar alguna instrucción o interrupción. De la misma manera que tenemos que mover la información (en el caso del primer tutorial fue una cadena de texto) al stack antes de procesarla (para mostrar esa cadena en pantalla). Al principio de nuestro método ‘main’ (véase figura 1.1 en la anterior entrada), nos damos cuenta que tuvimos que almacenar información a lo largo de nuestro método. Por ello usamos la directiva .maxstack, si hubiéramos indicado la directiva con un número tres (.maxstack 3), entonces el JIT hubiera creado un stack con 3 celdas para su uso posterior en el método. Ojo, esto no significa que únicamente podemos cargar 3 valores en la vida de nuestro método, simplemente significa que podemos albergar 3 valores a la vez como máximo. Una vez que el método finaliza, el stack y sus valores se eliminan. Así es como funciona el recolector de Basura en .NET. Además, no hay limitación en cuanto al tipo de dato que podemos usar para almacenar en el stack. Podremos mover cualquier tipo de dato (cadenas, enteros, objetos, etc) al stack siempre que queramos.
Tomemos otro ejemplo que nos aclarará el concepto del stack de evaluación:
//Sumar.il //Suma dos números .assembly extern mscorlib {} .assembly Sumar { .ver 1:0:1:0 } .module Sumar.exe .method static void main() cil managed { .maxstack 2 .entrypoint ldstr "La suma de 50 y 30 es = " call void [mscorlib]System.Console::Write (string) ldc.i4.s 50 ldc.i4 30 add call void [mscorlib]System.Console::Write (int32) ret }
Figura 1.3 – Sumar.il, sumando dos números predefinidos
El trozo de código encima de nuestro método main es el mismo que en el ejemplo anterior, el cual ya explicamos. Únicamente ha cambiado el nombre del módulo. En este código lo importante a discutir es el .maxstack 2, el cual indica al JIT a preparar suficiente espacio en memoria para que podamos guardar dos valores. Entonces cargamos la cadena en el stack y la imprimimos. Lo próximo que hacemos es cargar dos enteros en memoria a la vez (usando las instrucciones ldc.i4.s y ldc.i4), usar la instrucción de suma y finalmente mostrar por pantalla el resultado entero que hemos obtenido. La instrucción de suma usada (add) tomará del stack de evaluación dos valores (siempre que sean encontrados), los sumará y guardará el resultado al comienzo del stack (es decir, un ‘push’). Justo después de la instrucción de suma, encontramos otra instrucción llamada Write que escribirá algo por pantalla. Este método requiere que haya un valor guardado en el stack. En este caso requerirá que sea del tipo entero. Si lo encuentra, entonces escribirá su valor por pantalla o en caso contrario levantará un error.
No os liéis con ldc.i4.s y ldc.i4, ambos representan un tipo de dato entero (integer), pero el primero es un único byte y el segundo son cuatro bytes.
Espero que hayáis entendido la forma de usar el stack de evaluación y como funciona. Ahora veremos los tipos de datos del lenguaje ILAsm como una parte fundamental del mismo.
Tipos de Datos ILAsm
Al igual que cuando aprendes cualquier otro lenguaje, hay que hablar sobre sus tipos de datos soportados. Este caso es igual. Tómate un tiempo para observar la tabla de abajo (figura 1.4) para aprender cuales son los tipos de datos más comunes en este lenguaje, pero antes, me gustaría añadir una cosa. No hay consistencia en la definición de los tipos de datos en los diferentes lenguajes de .NET. Por ejemplo para indicar un tipo de dato entero (32 bit) en VB.NET usamos ‘Integer‘ pero en C# y en VC++ no es así, aunque luego ambos casos se representen usando System.Int32. Además, necesitamos tener en cuenta si es un tipo básico con la especificación del Lenguaje común (CLS, Common Language Specification) o no. Como por ejemplo el tipo de dato UInt32 no está reconocido por VB.NET por ello no es básico CLS.
Bien, empecemos por aprender la tabla que nos proporciona los nuevos nombres para los tipos de datos de ILAsm:
| Nombre en IL | Tipo Base en .NET | Significado (Descripción) | Básico CLS |
| Void | Sin valor, solo usado como tipo de retorno | No | |
| Bool | System.Boolean | Booleano | No |
| Char | System.Char | Carácter (16-bit unicode) | No |
| int8 | System.SByte | Entero 1-byte (con signo) | No |
| int16 | System.Int16 | Entero 2-byte (con signo) | No |
| int32 | System.Int32 | Entero 4-byte (con signo) | Si |
| int64 | System.64 | Entero 8-byte (con signo) | Si |
| native int | System.IntPtr | Entero (con signo) | Si |
| unsigned int8 | System.Byte | Entero 1-byte (sin signo) | Si |
| unsigned int16 | System.UInt16 | Entero 2-byte (sin signo) | Si |
| unsigned int32 | System.UInt32 | Entero 4-byte (sin signo) | No |
| unsigned int64 | System.UInt64 | Entero 8-byte (sin signo) | Si |
| native unsigned int | System.UIntPtr | Entero (sin signo) | Si |
| Float32 | System.Single | Coma flotante de 4-byte | No |
| Float64 | System.Double | Coma flotante de 8-byte | No |
| object | System.Object | Tipo objeto | Si |
| & | Puntero administrado | Si | |
| * | System.IntPtr | Puntero no administrado | Si |
| typedef | System.Typed Reference | Tipo especial que contiene datos y explícitamente indica su tipo | Si |
| Array | System.Array | Array | Si |
| string | System.String | Cadena de texto | Si |
Figura 1.4 – Tipos de Datos en ILAsm
También tenemos abreviaciones para los tipos de datos en ILAsm como por ejemplo .i4, .i4.s, .u4, etc como usamos en el ejemplo anterior. Los tipos de datos mencionados en la tabla de arriba muestran los tipos de datos son reconocidos en ILAsm y además menciona cuales son tipos básicos CLS y cuales no. Entonces, teniendo en mente esa información, podremos llamar a cualquier función como esta:
call int32 FuncionEjemplo (string, int32, float64)
Lo cual significa, que la función ‘FuncionEjemplo‘ devuelve un valor del tipo int32 (System.Int32) y toma tres valores como parámetros que son del tipo string (System.String), int32 (System.Int32) y float64 (System.Double) respectivamente. Toma nota que estos son datos básicos en el CLR y en ILAsm. Cuando estés interesado en tratar tipos de datos que no son básicos (definidos por el usuario) entonces el código podrá lucir como esto:
//Código en C# ColorTranslator.ToHtml(Color); //Código en ILAsm call instance string [System.Drawing]System.Drawing.ColorTranslator::ToHtml(valuetype [System.Drawing]System.Drawing.Color)
Toma nota que hemos definido explícitamente los tipos de dato en los parámetros y incluyendo además el espacio de nombre (namespace) donde el tipo de dato reside y una palabra clave que indicará al JIT que estamos referenciando a un tipo de dato no básico (valuetype).
Esto lo dejaré más claro en la próxima entrega del tutorial, donde trataremos más intensamente con los tipos de datos. También veremos una de las bases de cualquier lenguaje: Condiciones y bucles.
[ Introducción al lenguaje ensamblador IL de .NET [Parte 3] –> ]
Pingback: Introducción al lenguaje ensamblador IL de .NET [Parte 1] « El Blog de JuDelCo
Pingback: Introducción al lenguaje ensamblador IL de .NET [Parte 3] « El Blog de JuDelCo