Linguagem intermediária comum - Common Intermediate Language

Common Intermediate Language ( CIL ), anteriormente denominado Microsoft Intermediate Language ( MSIL ) ou Intermediate Language ( IL ), é o conjunto de instruções binárias de idioma intermediário definido na especificação Common Language Infrastructure (CLI). As instruções CIL são executadas por um ambiente de tempo de execução compatível com CLI, como o Common Language Runtime . As linguagens que visam a CLI compilam para CIL. CIL é orientada a objectos , baseada em pilha código de bytes . Os tempos de execução normalmente compilam instruções CIL em código nativo just-in-time .

CIL era originalmente conhecido como Microsoft Intermediate Language (MSIL) durante as versões beta das linguagens .NET. Devido à padronização do C # e da CLI, o bytecode agora é oficialmente conhecido como CIL. As definições de vírus do Windows Defender continuam a se referir aos binários compilados com ele como MSIL.

Informação geral

Durante a compilação das linguagens de programação CLI , o código-fonte é traduzido em código CIL em vez de em código - objeto específico de plataforma ou processador . CIL é um conjunto de instruções independente de CPU e plataforma que pode ser executado em qualquer ambiente que suporte a Common Language Infrastructure, como o runtime .NET no Windows ou o runtime Mono de plataforma cruzada . Em teoria, isso elimina a necessidade de distribuir diferentes arquivos executáveis ​​para diferentes plataformas e tipos de CPU. O código CIL é verificado quanto à segurança durante o tempo de execução, proporcionando melhor segurança e confiabilidade do que os arquivos executáveis ​​compilados nativamente.

O processo de execução é semelhante a este:

  1. O código-fonte é convertido em bytecode CIL e um assembly CLI é criado.
  2. Na execução de um assembly CIL, seu código é passado pelo compilador JIT do tempo de execução para gerar o código nativo. A compilação antecipada também pode ser usada, o que elimina essa etapa, mas à custa da portabilidade do arquivo executável.
  3. O processador do computador executa o código nativo.

Instruções

Bytecode CIL tem instruções para os seguintes grupos de tarefas:

Modelo computacional

A Common Intermediate Language é orientada a objetos e baseada em pilha , o que significa que os parâmetros de instrução e os resultados são mantidos em uma única pilha, em vez de em vários registradores ou outros locais de memória, como na maioria das linguagens de programação .

Código que adiciona dois números em linguagem assembly x86 , em que eax e edx especificam dois registros de uso geral diferentes :

add eax, edx

Código em uma linguagem intermediária (IL), em que 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

No último exemplo, os valores dos dois registradores, eax e edx, são primeiro colocados na pilha. Quando a instrução add é chamada, os operandos são "removidos" ou recuperados e o resultado é "enviado" ou armazenado na pilha. O valor resultante é então retirado da pilha e armazenado em eax.

Conceitos orientados a objetos

CIL é projetado para ser orientado a objetos. Você pode criar objetos, chamar métodos e usar outros tipos de membros, como campos.

Todo método precisa (com algumas exceções) residir em uma classe. O mesmo acontece com 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;
    }
}

O método Add não requer que nenhuma instância de Foo seja declarada porque ele é declarado como estático e pode então ser usado assim em C #:

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

Em CIL seria assim:

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

Classes de instância

Uma classe de instância contém pelo menos um construtor e alguns membros de instância . A classe a seguir possui um conjunto de métodos que representam ações de um 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 */ }
}

Criação de objetos

Na classe C #, as instâncias são criadas assim:

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

E essas declarações são aproximadamente as mesmas que estas instruções em 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 instância

Os métodos de instância são chamados em C # como o seguinte:

myCar.Move(3);

Conforme invocado em CIL:

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

Metadados

O Common Language Infrastructure (CLI) registra informações sobre classes compiladas como metadados . Como a biblioteca de tipos no Component Object Model , isso permite que os aplicativos ofereçam suporte e descubram as interfaces, classes, tipos, métodos e campos no assembly. O processo de leitura de tais metadados é denominado " reflexão ".

Os metadados podem ser dados na forma de "atributos". Os atributos podem ser personalizados estendendo a Attribute classe. Este é um recurso poderoso. Ele permite ao criador da classe a capacidade de adorná-la com informações extras que os consumidores da classe podem usar de várias maneiras significativas, dependendo do domínio do aplicativo.

Exemplo

Abaixo está um programa básico Hello, World escrito em CIL. Ele exibirá a string "Olá, mundo!".

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

O código a seguir é mais complexo em número de opcodes.

Esse código também pode ser comparado com o código correspondente no artigo sobre 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:;
    }
}

Na sintaxe CIL, é assim:

.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 é apenas uma representação de como CIL se parece perto do nível da máquina virtual (VM). Quando compilados, os métodos são armazenados em tabelas e as instruções são armazenadas como bytes dentro do assembly, que é um Portable Executable (PE).

Geração

Um assembly CIL e instruções são gerados por um compilador ou um utilitário chamado IL Assembler ( ILAsm ) que é enviado com o ambiente de execução.

O CIL montado também pode ser desmontado em código novamente usando o IL Disassembler (ILDASM). Existem outras ferramentas, como o .NET Reflector, que podem descompilar o CIL em uma linguagem de alto nível (por exemplo, C # ou Visual Basic ). Isso torna o CIL um alvo muito fácil para a engenharia reversa. Esta característica é compartilhada com o bytecode Java . No entanto, existem ferramentas que podem ofuscar o código e fazer isso de forma que o código não seja facilmente legível, mas ainda assim seja executável.

Execução

Compilação just-in-time

A compilação just -in-time (JIT) envolve transformar o código de byte em código imediatamente executável pela CPU. A conversão é realizada gradativamente durante a execução do programa. A compilação JIT fornece otimização específica do ambiente, segurança de tipo de tempo de execução e verificação de montagem. Para fazer isso, o compilador JIT examina os metadados do assembly em busca de acessos ilegais e trata as violações de maneira adequada.

Compilação antecipada

Ambientes de execução compatíveis com CLI também vêm com a opção de fazer uma compilação antecipada (AOT) de um assembly para torná-lo mais rápido, removendo o processo JIT no tempo de execução.

No .NET Framework, há uma ferramenta especial chamada Native Image Generator (NGEN) que executa o AOT. Uma abordagem diferente para AOT é o CoreRT, que permite a compilação do código .Net Core para um único executável sem dependência de um tempo de execução. No Mono , também existe a opção de fazer um AOT.

Instruções do ponteiro - C ++ / CLI

Uma diferença notável do bytecode do Java é que CIL vem com ldind, stind, ldloca e muitas instruções de chamada que são suficientes para a manipulação de ponteiros de dados / função necessária para compilar o código C / C ++ em 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();
}

O código correspondente em CIL pode ser renderizado como este:

.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

Veja também

Referências

Leitura adicional

links externos