Język pośredni .NET

Język pośredni (IL, Intermediate Language) to niskopoziomowy język programowania będący pośrednią wersją między kodem źródłowym a kodem maszynowym. Po skompilowaniu kodu napisanego w języku wysokiego poziomu .NET (np. C#, Visual Basic, F#) otrzymujemy plik binarny zawierający instrukcje IL oraz metadane. Kod IL jest niezależny od procesora i systemu operacyjnego, ale wymaga środowiska .NET (CLR, Common Language Runtime) do uruchomienia. Podczas wykonania CLR kompiluje IL do kodu natywnego, który następnie wykonuje procesor [1, 2].

Analiza kodu pośredniego może dać głębszy wgląd w działanie aplikacji .NET. Kod IL jest czytelny, a jego analiza nie jest skomplikowana. Dysponując samym plikiem wykonywalnym .NET można zajrzeć do jego środka i zobaczyć definicje klas, metod, pola oraz manifest assembly, nawet bez dostępu do pierwotnego kodu źródłowego [3]. Podstawowym narzędziem służącym do analizy i dostarczonym przez Microsoft podczas instalacji Visual Studio jest Ildasm.exe [4]. Aby uruchomić program w menu start należy wpisać Developer Command Prompt for VS 2022. A następnie w konsoli ildasm.

Za jego pomocą można zobaczyć jak przekształcony zostanie poniższy kod aplikacji konsolowej napisanej w C# na język pośredni.

namespace IldasmAnalysis
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

Poniżej znajduje się dokładna analiza kodu IL na podstawie oficjalnej dokumentacji, do której należy zajrzeć podczas analizy bardziej skomplikowanych przypadków [5]:

.method private hidebysig static void  Main(string[] args) cil managed

.method — zaczyna definicję metody.
private — metoda jest widoczna tylko w obrębie zestawu/typu (tu: wewnętrzna dla klasy).
hidebysig — przeciążenia są rozróżniane po sygnaturze (typach parametrów).
static — metoda nie wymaga instancji (wywoływana na typie).
void Main(string[] args) — podpis metody.
cil managed — ciało jest w IL, a kod wykonuje CLR (zarządzany).

{
  .entrypoint

.entrypoint — to punkt startowy programu (metoda, od której zaczyna się wykonanie).

.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( 01 00 01 00 00 )

Atrybut kompilatora: NullableContextAttribute. Informuje narzędzia/kompilator o kontekście typów nullowalnych w metodzie.

// Code size       13 (0xd)

Rozmiar ciała metody: 13 bajtów (0x0d w hex).

.maxstack  8

Maksymalna głębokość stosu ewaluacyjnego potrzebna do wykonania metody.

IL_0000:  nop

nop — wypełnia miejsce, jeśli kody operacyjne są poprawiane. Nie jest wykonywana żadna znacząca operacja, chociaż można użyć cyklu przetwarzania.

IL_0001:  ldstr      "Hello, World!"

ldstr — wypycha nowe odwołanie do obiektu do literału ciągu przechowywanego w metadanych.
Stos po tej instrukcji: top → „Hello, World!”.

IL_0006:  call       void [System.Console]System.Console::WriteLine(string)

call — wywołuje metodę wskazaną przez deskryptor metody przekazywanej.
Stos przed: top → „Hello, World!”
Stos po: pusty (WriteLine nic nie zwraca — void).

IL_000b:  nop

nop — wypełnia miejsce, jeśli kody operacyjne są poprawiane. Nie jest wykonywana żadna znacząca operacja, chociaż można użyć cyklu przetwarzania.

IL_000c:  ret

ret — zwraca wartość z bieżącej metody, wypychając wartość zwracaną (jeśli istnieje) ze stosu oceny wywoływanego do stosu oceny obiektu wywołującego.

} // end of method Program::Main

Koniec definicji metody.

[1] https://pvs-studio.com/en/blog/terms/7006/

[2] https://learn.microsoft.com/en-us/dotnet/standard/managed-code

[3] https://www.spyshelter.com/exe/net-ildasm-exe/

[4] https://learn.microsoft.com/en-us/dotnet/framework/tools/ildasm-exe-il-disassembler

[5] https://learn.microsoft.com/pl-pl/dotnet/api/system.reflection.emit.opcodes?view=net-8.0

Dodaj komentarz