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:
- O código-fonte é convertido em bytecode CIL e um assembly CLI é criado.
- 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.
- O processador do computador executa o código nativo.
Instruções
Bytecode CIL tem instruções para os seguintes grupos de tarefas:
- Carregar e armazenar
- Aritmética
- Conversão de tipo
- Criação e manipulação de objetos
- Gerenciamento de pilha de operandos (push / pop)
- Transferência de controle (ramificação)
- Invocação e retorno de método
- Lançar exceções
- Simultaneidade baseada em monitor
- Manipulação de ponteiros de dados e função necessária para código C ++ / CLI e C # inseguro
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 = ¶m;
*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 = ¶m
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
- Bock, Jason (2002). Programação CIL: Under the Hood of .NET . Apress. ISBN 978-1590590416 .