Introducción al lenguaje ensamblador IL de .NET [Parte 3]
17 diciembre 2011 2 comentarios
[ <-- Introducción al lenguaje ensamblador IL de .NET [Parte 2] ]
Declaración de Variables
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)
Figura 1.5 – Variables Locales
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).
Condiciones en ILAsm
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 }
Figura 1.6 – Saltando.il (solamente el método main)
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.
Bucles 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 }
Figura 1.7 – Bucles.il (solamente el método main)
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] –> ]
Pingback: Introducción al lenguaje ensamblador IL de .NET [Parte 4] « El Blog de JuDelCo
Pingback: Introducción al lenguaje ensamblador IL de .NET [Parte 2] « El Blog de JuDelCo