Lenguaje intermedio común - Common Intermediate Language

Common Intermediate Language ( CIL ), anteriormente llamado Microsoft Intermediate Language ( MSIL ) o Intermediate Language ( IL ), es el conjunto de instrucciones binarias de lenguaje intermedio definido dentro de la especificación Common Language Infrastructure (CLI). Las instrucciones CIL se ejecutan mediante un entorno de ejecución compatible con CLI, como Common Language Runtime . Los lenguajes que apuntan a la CLI se compilan en CIL. CIL es orientado a objetos , basado en la pila de código de bytes . Tiempos de ejecución típicamente justo a tiempo de las instrucciones de compilación CIL en código nativo .

CIL se conocía originalmente como Microsoft Intermediate Language (MSIL) durante las versiones beta de los lenguajes .NET. Debido a la estandarización de C # y la CLI, el código de bytes ahora se conoce oficialmente como CIL. Las definiciones de virus de Windows Defender continúan haciendo referencia a binarios compilados con él como MSIL.

Información general

Durante la compilación de lenguajes de programación CLI , el código fuente se traduce a código CIL en lugar de a código objeto específico de la plataforma o del procesador . CIL es un conjunto de instrucciones independientes de la CPU y la plataforma que se puede ejecutar en cualquier entorno que admita Common Language Infrastructure, como el tiempo de ejecución .NET en Windows o el tiempo de ejecución Mono multiplataforma . En teoría, esto elimina la necesidad de distribuir diferentes archivos ejecutables para diferentes plataformas y tipos de CPU. El código CIL se verifica por seguridad durante el tiempo de ejecución, lo que proporciona una mayor seguridad y confiabilidad que los archivos ejecutables compilados de forma nativa.

El proceso de ejecución se ve así:

  1. El código fuente se convierte en CIL código de bytes y un montaje de CLI se crea.
  2. Tras la ejecución de un ensamblado CIL, su código se pasa a través del compilador JIT del tiempo de ejecución para generar código nativo. También se puede utilizar la compilación anticipada, lo que elimina este paso, pero a costa de la portabilidad del archivo ejecutable.
  3. El procesador de la computadora ejecuta el código nativo.

Instrucciones

El código de bytes CIL tiene instrucciones para los siguientes grupos de tareas:

Modelo computacional

El Common Intermediate Language está orientado a objetos y basado en pilas , lo que significa que los parámetros de instrucción y los resultados se mantienen en una sola pila en lugar de en varios registros u otras ubicaciones de memoria, como en la mayoría de los lenguajes de programación .

Código que agrega dos números en lenguaje ensamblador x86 , donde eax y edx especifican dos registros de propósito general diferentes :

add eax, edx

Código en un lenguaje intermedio (IL), donde 0 es eax y 1 es edx:

ldloc.0    // push local variable 0 onto stack
ldloc.1    // push local variable 1 onto stack
add        // pop and add the top two stack items then push the result onto the stack
stloc.0    // pop and store the top stack item to local variable 0

En el último ejemplo, los valores de los dos registros, eax y edx, se insertan primero en la pila. Cuando se llama a la instrucción de adición, los operandos se "extraen" o se recuperan, y el resultado se "empuja" o se almacena en la pila. El valor resultante se extrae de la pila y se almacena en eax.

Conceptos orientados a objetos

CIL está diseñado para estar orientado a objetos. Puede crear objetos, llamar a métodos y utilizar otros tipos de miembros, como campos.

Cada método necesita (con algunas excepciones) residir en una clase. También lo hace este método estático:

.class public Foo {
    .method public static int32 Add(int32, int32) cil managed {
        .maxstack 2
        ldarg.0 // load the first argument;
        ldarg.1 // load the second argument;
        add     // add them;
        ret     // return the result;
    }
}

El método Add no requiere que se declare ninguna instancia de Foo porque se declara como estático, y luego se puede usar así en C #:

int r = Foo.Add(2, 3);    // 5

En CIL se vería así:

ldc.i4.2
ldc.i4.3
call int32 Foo::Add(int32, int32)
stloc.0

Clases de instancia

Una clase de instancia contiene al menos un constructor y algunos miembros de instancia . La siguiente clase tiene un conjunto de métodos que representan acciones de un objeto Car.

.class public Car {
    .method public specialname rtspecialname instance void .ctor(int32, int32) cil managed {
        /* Constructor */
    }

    .method public void Move(int32) cil managed { /* Omitting implementation */ }
    .method public void TurnRight() cil managed { /* Omitting implementation */ }
    .method public void TurnLeft() cil managed { /* Omitting implementation */ }
    .method public void Brake() cil managed { /* Omitting implementation */ }
}

Creando objetos

En la clase C #, las instancias se crean así:

Car myCar = new Car(1, 4); 
Car yourCar = new Car(1, 3);

Y esas declaraciones son aproximadamente las mismas que estas instrucciones en CIL:

ldc.i4.1
ldc.i4.4
newobj instance void Car::.ctor(int, int)
stloc.0    // myCar = new Car(1, 4);
ldc.i4.1
ldc.i4.3
newobj instance void Car::.ctor(int, int)
stloc.1    // yourCar = new Car(1, 3);

Invocar métodos de instancia

Los métodos de instancia se invocan en C # como el siguiente:

myCar.Move(3);

Como se invoca en CIL:

ldloc.0    // Load the object "myCar" on the stack
ldc.i4.3
call instance void Car::Move(int32)

Metadatos

El Common Language Infrastructure (CLI) registra información sobre las clases compiladas como metadatos . Al igual que la biblioteca de tipos en el modelo de objetos componentes , esto permite que las aplicaciones admitan y descubran las interfaces, clases, tipos, métodos y campos en el ensamblaje. El proceso de lectura de tales metadatos se denomina " reflexión ".

Los metadatos pueden ser datos en forma de "atributos". Los atributos se pueden personalizar ampliando la Attribute clase. Esta es una característica poderosa. Permite al creador de la clase la capacidad de adornarla con información adicional que los consumidores de la clase pueden usar de varias formas significativas, según el dominio de la aplicación.

Ejemplo

A continuación se muestra un programa básico de Hello, World escrito en CIL. Mostrará la cadena "¡Hola, mundo!".

.assembly Hello {}
.assembly extern mscorlib {}
.method static void Main()
{
    .entrypoint
    .maxstack 1
    ldstr "Hello, world!"
    call void [mscorlib]System.Console::WriteLine(string)
    ret
}

El siguiente código es más complejo en número de códigos de operación.

Este código también se puede comparar con el código correspondiente en el artículo sobre el código de bytes de Java .

static void Main(string[] args)
{
    for (int i = 2; i < 1000; i++)
    {
        for (int j = 2; j < i; j++)
        {
             if (i % j == 0)
                 goto outer;
        }
        Console.WriteLine(i);
        outer:;
    }
}

En la sintaxis CIL se ve así:

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack  2
    .locals init (int32 V_0,
                  int32 V_1)

              ldc.i4.2
              stloc.0
              br.s       IL_001f
    IL_0004:  ldc.i4.2
              stloc.1
              br.s       IL_0011
    IL_0008:  ldloc.0
              ldloc.1
              rem
              brfalse.s  IL_001b
              ldloc.1
              ldc.i4.1
              add
              stloc.1
    IL_0011:  ldloc.1
              ldloc.0
              blt.s      IL_0008
              ldloc.0
              call       void [mscorlib]System.Console::WriteLine(int32)
    IL_001b:  ldloc.0
              ldc.i4.1
              add
              stloc.0
    IL_001f:  ldloc.0
              ldc.i4     0x3e8
              blt.s      IL_0004
              ret
}

Esta es solo una representación de cómo se ve CIL cerca del nivel de la máquina virtual (VM). Cuando se compilan, los métodos se almacenan en tablas y las instrucciones se almacenan como bytes dentro del ensamblado, que es un ejecutable portátil (PE).

Generacion

Un ensamblado y las instrucciones de CIL son generados por un compilador o una utilidad llamada IL Assembler ( ILAsm ) que se envía con el entorno de ejecución.

El CIL ensamblado también se puede desensamblar en código nuevamente usando IL Disassembler (ILDASM). Existen otras herramientas como .NET Reflector que pueden descompilar CIL en un lenguaje de alto nivel (por ejemplo, C # o Visual Basic ). Esto hace que CIL sea un objetivo muy fácil para la ingeniería inversa. Este rasgo se comparte con el código de bytes de Java . Sin embargo, existen herramientas que pueden ofuscar el código y hacerlo de modo que el código no se pueda leer fácilmente, pero aún se pueda ejecutar.

Ejecución

Recopilación justo a tiempo

La compilación Just-In-Time (JIT) implica convertir el código de bytes en código inmediatamente ejecutable por la CPU. La conversión se realiza de forma gradual durante la ejecución del programa. La compilación JIT proporciona optimización específica del entorno, seguridad del tipo de tiempo de ejecución y verificación del ensamblaje. Para lograr esto, el compilador JIT examina los metadatos del ensamblado en busca de accesos ilegales y maneja las violaciones de manera apropiada.

Recopilación anticipada

Los entornos de ejecución compatibles con CLI también vienen con la opción de realizar una compilación anticipada (AOT) de un ensamblado para que se ejecute más rápido al eliminar el proceso JIT en tiempo de ejecución.

En .NET Framework hay una herramienta especial llamada Native Image Generator (NGEN) que realiza el AOT. Un enfoque diferente para AOT es CoreRT que permite la compilación de código .Net Core en un solo ejecutable sin dependencia de un tiempo de ejecución. En Mono también hay una opción para hacer un AOT.

Instrucciones de puntero - C ++ / CLI

Una diferencia notable con el código de bytes de Java es que CIL viene con ldind, stind, ldloca y muchas instrucciones de llamada que son suficientes para la manipulación de punteros de datos / función necesaria para compilar código C / C ++ en CIL.

class A {
   public: virtual void __stdcall meth() {}
};
void test_pointer_operations(int param) {
	int k = 0;
	int * ptr = &k;
	*ptr = 1;
	ptr = &param;
	*ptr = 2;
	A a;
	A * ptra = &a;
	ptra->meth();
}

El código correspondiente en CIL se puede representar de la siguiente manera:

.method assembly static void modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) 
        test_pointer_operations(int32 param) cil managed
{
  .vtentry 1 : 1
  // Code size       44 (0x2c)
  .maxstack  2
  .locals ([0] int32* ptr,
           [1] valuetype A* V_1,
           [2] valuetype A* a,
           [3] int32 k)
// k = 0;
  IL_0000:  ldc.i4.0 
  IL_0001:  stloc.3
// ptr = &k;
  IL_0002:  ldloca.s   k // load local's address instruction
  IL_0004:  stloc.0
// *ptr = 1;
  IL_0005:  ldloc.0
  IL_0006:  ldc.i4.1
  IL_0007:  stind.i4 // indirection instruction
// ptr = &param
  IL_0008:  ldarga.s   param // load parameter's address instruction
  IL_000a:  stloc.0
// *ptr = 2
  IL_000b:  ldloc.0
  IL_000c:  ldc.i4.2
  IL_000d:  stind.i4
// a = new A;
  IL_000e:  ldloca.s   a
  IL_0010:  call       valuetype A* modopt([mscorlib]System.Runtime.CompilerServices.CallConvThiscall) 'A.{ctor}'(valuetype A* modopt([mscorlib]System.Runtime.CompilerServices.IsConst) modopt([mscorlib]System.Runtime.CompilerServices.IsConst))
  IL_0015:  pop
// ptra = &a;
  IL_0016:  ldloca.s   a
  IL_0018:  stloc.1
// ptra->meth();
  IL_0019:  ldloc.1
  IL_001a:  dup
  IL_001b:  ldind.i4 // reading the VMT for virtual call
  IL_001c:  ldind.i4
  IL_001d:  calli      unmanaged stdcall void modopt([mscorlib]System.Runtime.CompilerServices.CallConvStdcall)(native int)
  IL_0022:  ret
} // end of method 'Global Functions'::test_pointer_operations

Ver también

Referencias

Otras lecturas

enlaces externos