Merry Christmas 2011
25 diciembre 2011 Deja un comentario
Programación, Música, Arte y chorradas varias!
18 diciembre 2011 1 comentario
[ <-- Introducción al lenguaje ensamblador IL de .NET [Parte 3] ]
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 }
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.
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 }
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!
17 diciembre 2011 2 comentarios
[ <-- Introducción al lenguaje ensamblador IL de .NET [Parte 2] ]
Las variables es uno de los pilares de cualquier lenguaje de programación y por tanto, ILAsm nos permite declararlas y usarlas. Aunque no es una tarea tan sencilla como en los lenguajes de alto nivel, usando la directiva .locals podremos definir variables. Esta directiva debería normalmente usarse al principio de cualquier método, aunque puedes poner la declaración en cualquier sitio, obviamente, antes de usarlas. Aquí tenemos un ejemplo en el que declaramos una variable, guardamos un valor en ella y luego la usamos para imprimir su valor.
.locals init (int32, string) ldc.i4 34 stloc.0 ldstr "Texto de ejemplo para probar la variable local" stloc.1 ldloc.0 call void [mscorlib]System.Console::WriteLine(int32) ldloc.1 call void [mscorlib]System.Console::WriteLine(string)
Hemos declarado dos variables usando la directiva .locals, una del tipo int32 y la otra del tipo string. Luego hemos cargado el número 34 del tipo int32 en memoria y se la hemos asignado a la variable local 0, la cual es nuestra primera variable local. Toma nota que en ILAsm los índices empiezan por cero. Luego hemos cargado en memoria una segunda variable y se la hemos asignado a esa segunda variable local. Finalmente hemos mostrado por pantalla ambas variables. Os preguntareis para qué sirve la instrucción ldloc, pues bien, puede ser usada para cargar cualquier tipo de dato en memoria (enteros, coma flotante, objetos, etc…).
En este caso no hemos usado nombres para nuestras variables. Ya que son locales y no tenemos intención de utilizarlas fuera de nuestro método. Eso no significa que no se puedan declarar variables por su nombre. Para declarar esas variables, simplemente hazlo al igual que en otro lenguaje de alto nivel como por ejemplo .locals init (int32 x, int32 y). Después podrás leer o guardar valores en esas variables usando las mismas instrucciones, pero usando sus nombres como ‘stloc x‘ y ‘ldloc y‘. Aun así, aunque tengan sus propios nombres, podrás seguir accediendo a ellas mediante su índice. Nota: A lo largo de todos los tutoriales usaré variables sin nombre, para que te acostumbres a pensar en qué indice están guardadas tus variables.
Bien, ahora tienes una idea de cómo manipular variables y stacks. Por favor revisa los códigos si aún no tienes muy claro algún concepto y que, a partir de ahora, estaremos manipulando ambos con bastante frecuencia. Por ello es necesario que te familiarices con este procedimiento (definir/inicializar, guardar y leer sus valores).
Las condiciones son otros de los pilares de cualquier lenguaje de programación. En los lenguajes de bajo nivel, como cualquier otro lenguaje ensamblador, las condiciones están hechas usando saltos (o ramificaciones). Tomemos un ejemplo de esto:
br SaltarAqui //También puedes usar br.s si lo deseas //El código que haya aquí será saltado después de la instrucción anterior SaltarAqui: //En cambio, estas instrucciones serán ejecutadas
Compara esa instrucción con la instrucción ‘goto‘ de un lenguaje de alto nivel. ¿ Funciona igual verdad ? La instrucción saltará el punto de ejecución a la etiqueta indicada. Aquí, en ILAsm, en vez de usar goto usaremos br. Es instrucción también puede ser usada utilizando br.s si estás seguro de que el destino está entre los -128 y +127 bytes anteriores-posteriores a este, ya que usará un int8 en vez de un int32 para el offset. El ejemplo de arriba ha sido una ramificación (salto) incondicional ya que no hubo ninguna condición anterior de la instrucción que indicase lo contrario. Tomemos pues un ejemplo en el que sí contemplaremos un ejemplo con un salto condicional:
//Saltando.il .method static void main() cil managed { .maxstack 2 .entrypoint //Cogemos el primer valor del usuario ldstr "Introduce el Primer número: " call void [mscorlib]System.Console::WriteLine (string) call string [mscorlib]System.Console::ReadLine () call int32 [mscorlib]System.Int32::Parse(string) //Cogemos el segundo valor del usuario ldstr "Introduce el Segundo número: " call void [mscorlib]System.Console::WriteLine (string) call string [mscorlib]System.Console::ReadLine () call int32 [mscorlib]System.Int32::Parse(string) ble Menor ldstr "El segundo número es menor que el primero." call void [mscorlib]System.Console::WriteLine (string) br Salida Menor: ldstr "El primer número es menor que el segundo." call void [mscorlib]System.Console::WriteLine (string) Salida: ret }
El código de arriba toma dos valores del usuario y entonces consulta cual de ellos es el más pequeño. La instrucción la cual requiere nuestra atención es “ble Menor“, la cual indica al JIT a mirar si el primer valor en el stack es menor o igual que el segundo, si así es el caso, entonces saltará a la etiqueta ‘Menor‘. En el caso contrario la ejecución del programa continuará por la siguiente instrucción, sin realizar el salto, sin embargo luego se encontrará con un salto incondicional, ya que si no estuviera ahí, el programa continuaría y ejecutaría las instrucciones contenidas en la etiqueta ‘Menor‘. Por ello hemos creado una nueva etiqueta llamada ‘Salida‘ y hemos llamado a la instrucción ‘br Salida‘ la cual obligará al programa a saltar a la etiqueta ‘Salida‘ y ejecutar la instrucción ret, para finalizar el método.
Las otras instrucciones condicionales que podemos encontrar son beq (==), bne (!= ), bge (>= ), bgt (>), ble (<= ), blt (<), además de brfalse y brtrue (para comprobar si el primer elemento del stack es cero o no respectivamente). Puedes usar cualquiera de ellas para ejecutar alguna parte de tu código y evitar (saltar) otro. Como dije antes, no tenemos la facilidad que tenemos en los lenguajes de alto nivel, por ello todo debe de ser realizado por ti mismo si tienes pensado escribir o modificar código en ILAsm.
Los bucles son otra parte fundamental de un lenguaje de programación. Un bucle no es otra cosa que la repetición del mismo bloque de código una vez y otra vez. Para ello la repetición del bloque dependerá de una variable a la que llamaremos índice del bucle. En el siguiente fragmento de código mostraré un ejemplo de cómo funciona un bucle básico, así que dedicale un poco de tiempo a entender cómo funcionan los bucles:
.method static void main() cil managed { //Definimos dos variables locales variables .locals init (int32, int32) .maxstack 2 .entrypoint ldc.i4 4 stloc.0 //Tope, el límite del bucle. Haremos 5 repeticiones ldc.i4 0 stloc.1 //Inicializamos el índice a cero Comienzo: //Comprobamos si el contador del índice ha superado el límite ldloc.1 ldloc.0 bgt Salida //Si la segunda variable supera a la primera, entonces saltamos ldloc.1 call void [mscorlib]System.Console::WriteLine(int32) //Incrementamos el contador ldc.i4 1 ldloc.1 add stloc.1 br Comienzo Salida: ret }
En C#, este código se vería de esta manera:
for (temp = 0; temp < 5; temp++) System.Console.WriteLine (temp);
Bien, examinemos el código. Primero, hemos declarado dos variables locales e inicializado la primera con un valor de 4 y la segunda con valor 0. El bucle real empieza con la etiqueta ‘Comienzo‘, desde donde primero comprobaremos si el contador del indice del bucle (la variable nº2, ldloc 1) sobrepasa el límite impuesto por nuestra primera variable (ldloc 0), si ese fuera el caso, el programa saltaría a la etiqueta ‘Salida‘ con lo cual nuestro programa terminaría. Si no es el caso, entonces el valor será escrito por pantalla e incrementaremos ese valor para luego saltar de nuevo a la etiqueta ‘Comienzo‘ para comprobar de nuevo si el contador ha excedido o no el límite impuesto. Así funcionan los bucles en ILAsm (y en la mayoría de los otros lenguajes ensamblador).
En la próxima entrega veremos cómo definir nuestros propios métodos y utilizarlos, además de ver los dos tipos de parámetros soportados. Por valor y por referencia.
[ Introducción al lenguaje ensamblador IL de .NET [Parte 4] –> ]
16 diciembre 2011 2 comentarios
[ <-- Introducción al lenguaje ensamblador IL de .NET [Parte 1] ]
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 }
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.
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 |
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] –> ]
15 diciembre 2011 1 comentario
Lo que pretendo con esto es realizar una serie de tutoriales consecutivos sobre este tema, por lo que los próximos tutoriales se basaran en los anteriores. No pretendo que lleguéis a ser maestros escribiendo código ILAsm, más bien que obtengáis la base necesaria para que podáis desenvolveros sin mucha dificultad con el y sigáis aprendiendo por vuestra cuenta.
Con estos tutoriales aprenderéis la base del lenguaje ensamblador IL (tambien llamado CIL, o Common Intermediate Language) para que podáis debuguear código en bajo nivel .NET ya compilado (desde cualquier lenguaje de alto nivel, como VB .NET, C# y otros) y así corregir fallos, añadir, quitar o cambiar características y más. Es decir, podrás editar libremente cualquier programa o librería una vez compilada de .NET. Además, usando estos conocimientos, tendrás la base para poder empezar a escribir tu propio compilador para crear un nuevo lenguaje .NET (aunque no vamos a llegar tan lejos).
Siempre que compilas tu código en .NET, independientemente del lenguaje que estés usando, será convertido al Lenguaje Intermedio (IL, Intermediate Language) el cual es también conocido como Lenguaje Intermedio de Microsoft (MSIL) o Lenguaje Intermedio Común (CIL). Puedes pensar que IL es como el Byte Code generado por el lenguaje Java. Si estás interesado en entender cómo .NET funciona con sus tipos de datos y cómo el código que escribes es convertido a código IL, entonces estos tutoriales sobre ILAsm te van a resultar muy útiles dándote grandes ventajas.
Entre estas ventajas encontramos entender qué es lo que devuelve el compilador .NET cuando compila, por lo tanto, podrás examinar el codigo traducido por el compilador y realizar los cambios que sean necesarios (aunque no es necesario en la mayoría de los casos). Además, puedes cambiar el código IL para hacer cambios que tu lenguaje de alto nivel no permitiría para aumentar el rendimiento de tu código, y también te ayudará a debuggear tu código en bajo nivel. Y como dije antes, si estás planeando escribir un compilador para .NET, entonces necesitarás entender el código ILAsm.
IL es el formato binario el cual el compilador devuelve una vez termina su tarea, es decir, no puede ser leido por humanos, pero al igual que otros binarios (ejecutables) tienen su lenguaje ensamblador, IL tiene tambien el suyo propio conocido como lenguaje ensamblador IL (ILAsm). El lenguaje ensamblador IL (ILAsm a partir de ahora) tiene sus propias instrucciones de la misma manera que cualquier otro lenguaje ensamblador nativo pueda tener. Por ejemplo, para sumar dos números, tienes la instrucción ‘add’, para restar dos números, tienes la instrucción ‘sub’, etc. Es obvio que el compilador en tiempo de ejecución de .NET (el JIT) no puede ejecutar código ILAsm directamente. Si tu tienes escrito código en ILAsm entonces tendrás primero que compilarlo a código IL y entonces el JIT podrá ejecutar el código.
NOTA: Por favor ten en cuenta que IL y ILAsm son cosas distintas. Siempre que hablaremos sobre IL, nos referiremos al código binario devuelto por el compilador de .NET mientras que cuando referenciemos a ILAsm, nos referiremos al lenguaje ensamblador de este código el cual no está en su forma binaria.
NOTA2: Ten en cuenta que espero que estés bastante familiarizado con .NET (incluyendo algún lenguaje de alto nivel como C# o VB.NET). En estos tutoriales no profundizaremos en todos los detalles, sino únicamente en aquellos que realmente necesitan ser explicados. Si algo te confunde, por favor házmelo saber para aclarártelo.
Bien, empecemos con el propósito de estos tutoriales, la introducción al lenguaje ensamblador IL. Ten en cuenta que puedes escribir código en ILAsm en cualquier editor de texto (como notepad) y luego usar la linea de comandos (cmd) para compilar el código. El compilador en este caso es el ILAsm.exe, el cual está distribuido junto con el SDK de .NET Framework. ILAsm es un lenguaje algo duro de aprender para aquellos que siempre han programado en lenguajes de alto nivel, pero para aquellos que han programado en C o en C++ no les debería resultar demasiado complicado. En ILAsm necesitamos hacer todas las cosas manualmente, como añadir un valor al stack, administrar la memoria, etc. Piensa en ILAsm de la misma manera que cualquier otro lenguaje ensamblador con la diferencia de que en otro lenguaje estarás tratando con ejecutables nativos para Windows y con este lenguaje (ILAsm) estarás tratando con ejecutables .NET y además, este lenguaje es un poco más fácil (que otros lenguajes ensamblador) y además está orientado a objetos.
Empecemos con este lenguaje con nuestro primer programa de ejemplo que escribirá una frase en pantalla (en la consola). Es una tradición que en el comienzo de cada lenguaje se use un “hola mundo” así que vamos a hacer lo mismo:
//Ejemplo.IL //Un simple programa que imprime una cadena de texto por consola .assembly extern mscorlib {} .assembly Ejemplo { .ver 1:0:1:0 } .module ejemplo.exe .method static void main() cil managed { .maxstack 1 .entrypoint ldstr "Hola mundo! Also, The Game !!!" call void [mscorlib]System.Console::WriteLine (string) ret }
Una vez escrito el codigo de arriba (o figura 1.1) en un simple editor de texto como notepad, guárdalo como “Ejemplo.il“. Ahora realizaremos nuestra primera compilación y ejecutaremos nuestro codigo. Una vez hecho esto entraremos en detalles y explicaré qué es lo que hace cada instrucción. Para compilar el código, escribe lo siguiente en una ventana de comandos (cmd):
ILAsm.exe es una herramienta utilizada por linea de comando la cual está incluida con el Framework SDK de .NET y puede ser encontrada en la carpeta “<carpeta_windows>\Microsoft.NET\Framework\<version>“. Si quieres puedes incluir esta ruta a la variable de entorno Path de tu SO (por ello no he tenido que escribir la ruta completa en la figura 1.2 como podéis ver). Cuando has terminado de compilar tu archivo .IL, tendrás listo tu ejecutable (.exe) con el mismo nombre que tu archivo .IL. Puedes especificar el nombre de salida utilizando “/OutPut=<filename>” (sin comillas). Para ejecutar tu recien compilado programa, simplemente escribe el nombre de tu progama y presiona Enter. La salida del programa aparecerá ante ti. Bien, ahora nos tomaremos un tiempo para entender que es lo que hemos escrito en el código. Ten en mente que me estoy refiriendo al código de la figura 1.1
Leyendo las lineas de arriba, ahora deberías tener una idea de como escribir código en ILAsm, además, deberías darte cuenta de que ILAsm no es como un lenguaje .NET de alto nivel (como C#, VB, etc). De todas formas, todo el codigo que tu escribas, lo harás siguiendo una estructura de este estilo (o con pequeños cambios, cuando trabajemos con clases).
Para la siguiente parte del tutorial hablaremos sobre el stack de evaluación y los tipos de datos más comunes que nos encontraremos.
[ Introducción al lenguaje ensamblador IL de .NET [Parte 2] –> ]
Últimos comentarios