Linguaggio intermedio comune - Common Intermediate Language

Common Intermediate Language ( CIL ), precedentemente denominato Microsoft Intermediate Language ( MSIL ) o Intermediate Language ( IL ), è il set di istruzioni binarie del linguaggio intermedio definito nella specifica CLI ( Common Language Infrastructure ). Le istruzioni CIL vengono eseguite da un ambiente di runtime compatibile con CLI come Common Language Runtime . Linguaggi che indirizzano la compilazione CLI a CIL. CIL è orientato agli oggetti , basato sullo stack bytecode . Tempi di esecuzione tipicamente just-in-time istruzioni di compilazione CIL in codice nativo .

CIL era originariamente noto come Microsoft Intermediate Language (MSIL) durante le versioni beta dei linguaggi .NET. A causa della standardizzazione di C # e CLI, il bytecode è ora ufficialmente noto come CIL. Le definizioni dei virus di Windows Defender continuano a fare riferimento ai file binari compilati con esso come MSIL.

Informazione Generale

Durante la compilazione dei linguaggi di programmazione CLI , il codice sorgente viene tradotto in codice CIL anziché in codice oggetto specifico della piattaforma o del processore . CIL è un set di istruzioni indipendente dalla piattaforma e dalla CPU che può essere eseguito in qualsiasi ambiente che supporta Common Language Infrastructure, come il runtime .NET su Windows o il runtime Mono multipiattaforma . In teoria, questo elimina la necessità di distribuire diversi file eseguibili per diverse piattaforme e tipi di CPU. Il codice CIL viene verificato per la sicurezza durante il runtime, fornendo maggiore sicurezza e affidabilità rispetto ai file eseguibili compilati in modo nativo.

Il processo di esecuzione è simile a questo:

  1. Il codice sorgente viene convertito in bytecode CIL e viene creato un assembly CLI .
  2. All'esecuzione di un assembly CIL, il suo codice viene passato attraverso il compilatore JIT del runtime per generare codice nativo. È anche possibile utilizzare la compilazione anticipata, che elimina questo passaggio, ma a scapito della portabilità dei file eseguibili.
  3. Il processore del computer esegue il codice nativo.

Istruzioni

Il bytecode CIL contiene istruzioni per i seguenti gruppi di attività:

Modello computazionale

Il Common Intermediate Language è orientato agli oggetti e basato sullo stack , il che significa che i parametri e i risultati delle istruzioni sono conservati su un unico stack invece che in diversi registri o altre posizioni di memoria, come nella maggior parte dei linguaggi di programmazione .

Codice che aggiunge due numeri in linguaggio assembly x86 , dove eax ed edx specificano due diversi registri generici :

add eax, edx

Codice in un linguaggio intermedio (IL), dove 0 è eax e 1 è 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

In quest'ultimo esempio, i valori dei due registri, eax ed edx, vengono prima inseriti nello stack. Quando viene chiamata l'istruzione di aggiunta, gli operandi vengono "estratti", o recuperati, e il risultato viene "inserito", o memorizzato, nello stack. Il valore risultante viene quindi estratto dallo stack e memorizzato in eax.

Concetti orientati agli oggetti

CIL è progettato per essere orientato agli oggetti. È possibile creare oggetti, chiamare metodi e utilizzare altri tipi di membri, come i campi.

Ogni metodo deve (con alcune eccezioni) risiedere in una classe. Così fa questo metodo statico:

.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;
    }
}

Il metodo Add non richiede la dichiarazione di alcuna istanza di Foo perché è dichiarato statico e può quindi essere utilizzato in questo modo in C #:

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

In CIL sarebbe simile a questo:

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

Classi di istanza

Una classe di istanza contiene almeno un costruttore e alcuni membri di istanza . La classe seguente ha una serie di metodi che rappresentano le azioni di un oggetto 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 */ }
}

Creazione di oggetti

In C # le istanze di classe vengono create in questo modo:

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

E queste affermazioni sono più o meno le stesse di queste istruzioni in 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);

Invocare metodi di istanza

I metodi di istanza vengono richiamati in C # come quello che segue:

myCar.Move(3);

Come invocato in CIL:

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

Metadati

La Common Language Infrastructure (CLI) registra le informazioni sulle classi compilate come metadati . Come la libreria dei tipi nel Component Object Model , ciò consente alle applicazioni di supportare e rilevare le interfacce, le classi, i tipi, i metodi ei campi nell'assembly. Il processo di lettura di tali metadati è chiamato " riflessione ".

I metadati possono essere dati sotto forma di "attributi". Gli attributi possono essere personalizzati estendendo la Attribute classe. Questa è una caratteristica potente. Consente al creatore della classe di adornarla con informazioni aggiuntive che i consumatori della classe possono utilizzare in vari modi significativi, a seconda del dominio dell'applicazione.

Esempio

Di seguito è riportato un programma Hello, World di base scritto in CIL. Verrà visualizzata la stringa "Hello, world!".

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

Il codice seguente è più complesso per numero di codici operativi.

Questo codice può anche essere confrontato con il codice corrispondente nell'articolo sul bytecode 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:;
    }
}

Nella sintassi CIL è simile a questo:

.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
}

Questa è solo una rappresentazione di come CIL appare vicino al livello della macchina virtuale (VM). Quando vengono compilati, i metodi vengono archiviati in tabelle e le istruzioni vengono archiviate come byte all'interno dell'assembly, che è un PE ( Portable Executable ).

Generazione

Un assembly e le istruzioni CIL vengono generati da un compilatore o da un'utilità chiamata IL Assembler ( ILAsm ) fornita con l'ambiente di esecuzione.

Il CIL assemblato può anche essere nuovamente disassemblato nel codice utilizzando IL Disassembler (ILDASM). Esistono altri strumenti come .NET Reflector che possono decompilare CIL in un linguaggio di alto livello (ad esempio C # o Visual Basic ). Questo rende CIL un obiettivo molto facile per il reverse engineering. Questa caratteristica è condivisa con il bytecode Java . Tuttavia, ci sono strumenti che possono offuscare il codice e farlo in modo che il codice non possa essere facilmente leggibile ma comunque eseguibile.

Esecuzione

Compilazione just-in-time

La compilazione just-in-time (JIT) comporta la trasformazione del codice byte in codice immediatamente eseguibile dalla CPU. La conversione viene eseguita gradualmente durante l'esecuzione del programma. La compilazione JIT fornisce ottimizzazione specifica dell'ambiente, sicurezza del tipo di runtime e verifica dell'assembly. A tale scopo, il compilatore JIT esamina i metadati dell'assembly per eventuali accessi illegali e gestisce le violazioni in modo appropriato.

Compilazione anticipata

Gli ambienti di esecuzione compatibili con CLI offrono anche la possibilità di eseguire una compilazione anticipata (AOT) di un assembly per renderlo più veloce rimuovendo il processo JIT in fase di esecuzione.

In .NET Framework è presente uno strumento speciale chiamato Native Image Generator (NGEN) che esegue l'AOT. Un approccio diverso per AOT è CoreRT che consente la compilazione di codice .Net Core su un singolo eseguibile senza dipendenza da un runtime. In Mono c'è anche un'opzione per fare un AOT.

Istruzioni per il puntatore - C ++ / CLI

Una notevole differenza dal bytecode di Java è che CIL viene fornito con ldind, stind, ldloca e molte istruzioni di chiamata che sono sufficienti per la manipolazione di puntatori dati / funzione necessaria per compilare codice C / C ++ in 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();
}

Il codice corrispondente in CIL può essere reso come segue:

.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

Guarda anche

Riferimenti

Ulteriore lettura

link esterno