Esercitazione su Ildasm.exe

In questa esercitazione viene presentato lo strumento Microsoft Intermediate Language Disassembler (Ildasm.exe), incluso in .NET Framework SDK. Lo strumento Ildasm.exe esegue l'analisi di tutti gli assembly EXE e DLL di .NET Framework e ne visualizza le informazioni in un formato leggibile dall'utente, visualizzando non solo il codice MSIL (Microsoft Intermediate Language) ma anche gli spazi dei nomi e i tipi, incluse le relative interfacce. È possibile utilizzare Ildasm.exe per esaminare assembly .NET Framework nativi, quali Mscorlib.dll, nonché assembly di .NET Framework forniti da altri oppure creati personalmente. Si tratta di uno strumento indispensabile per la maggior parte degli sviluppatori che utilizzano .NET Framework.

Per eseguire l'esercitazione, utilizzare la versione Visual C# dell'esempio WordCount incluso in SDK. È possibile utilizzare anche la versione Visual Basic, ma in questo caso il linguaggio MSIL generato risulterà diverso e alcune immagini non coincideranno. WordCount si trova nella directory <FrameworkSDK>\Samples\Applications\WordCount\. Per generare ed eseguire l'esempio seguire le istruzioni descritte nel file Readme.htm. In questa esercitazione Ildasm.exe verrà utilizzato per esaminare l'assembly WordCount.exe.

Per iniziare, generare l'esempio WordCount e caricarlo in Ildasm.exe utilizzando la seguente riga di comando:

ildasm WordCount.exe

Verrà visualizzata la finestra di Ildasm.exe, come illustrato nella figura riportata di seguito.

Nella struttura visualizzata nella finestra di Ildasm.exe sono visibili le informazioni del manifesto assembly contenute nel file WordCount.exe e i tipi delle quattro classi globali: App, ArgParser, WordCountArgParser e WordCounter.

Per visualizzare ulteriori informazioni relative a un tipo fare doppio clic su di esso nella struttura. Nella figura riportata di seguito è stata espanso il tipo della classe WordCounter.

Nella figura precedente sono visibili tutti i membri del tipo WordCounter. Nella tabella riportata di seguito sono elencati e spiegati i diversi simboli grafici.

Simbolo Significato
Ulteriori informazioni
Spazio dei nomi
Classe
Interfaccia
Classe Value
Enumerazione
Metodo
Metodo Static
Campo
Campo Static
Evento
Proprietà
Manifesto o elemento contenente informazioni sulla classe

Se si fa doppio clic sulla voce .class public auto ansi beforefieldinit vengono visualizzate le seguenti informazioni:

Nella figura precedente risulta chiaro che il tipo WordCounter è derivato dal tipo System.Object.

Il tipo WordCounter contiene un altro tipo, chiamato WordOccurrence. È possibile espandere il tipo WordOccurrence per visualizzarne i membri, come illustrato nella figura riportata di seguito.

Dalla struttura si nota che il tipo WordOccurrence implementa l'interfaccia System.IComparable e in particolare il metodo CompareTo. Nel resto di questa esercitazione, tuttavia, verrà ignorato il tipo WordOccurrence per porre l'attenzione sul tipo WordCounter.

Il tipo WordCounter contiene cinque campi private: totalBytes, totalChars, totalLines, totalWords e wordCounter. Il primi quattro campi sono del tipo int64, mentre il campo wordCounter è un riferimento a un tipo System.Collections.SortedList.

Dopo i campi si possono vedere i metodi. Il primo metodo, .ctor, è un costruttore. Questo particolare tipo ha un solo costruttore, mentre altri tipi possono avere numerosi costruttori, ciascuno con una firma differente. Il costruttore WordCounter restituisce void, come tutti i costruttori, e non accetta parametri. Se si fa doppio clic sul metodo costruttore viene visualizzata una nuova finestra con il codice MSIL contenuto nel metodo, come illustrato nella figura riportata di seguito.

Il codice MSIL risulta molto semplice da leggere e da comprendere. Per ulteriori dettagli, vedere la sezione CIL Instruction Set Specification del file Partition III CIL.doc nella cartella <FrameworkSDK>\Tool Developers Guide\Docs\. Nella parte superiore si nota che questo costruttore richiede 50 byte di codice MSIL. Questo numero non fornisce un'idea chiara della quantità di codice nativo emessa dal compilatore JIT, poiché tale dimensione dipende dalla CPU dell'host e dal compilatore utilizzato per la generazione del codice.

La compilazione di Common Language Runtime è basata sullo stack. Durante l'esecuzione di qualsiasi operazione, perciò, il codice MSIL esegue come prima cosa il push degli operandi su uno stack virtuale, quindi esegue l'operatore. L'operatore estrae gli operandi dallo stack, esegue l'operazione richiesta, quindi pone il risultato nello stack. Lo stack virtuale non vi può contenere più di otto operandi. Per identificare l'esatto numero di operandi di cui è stato eseguito il push nello stack virtuale guardare l'attributo .maxstack visualizzato immediatamente prima del codice MSIL.

Verranno ora esaminate le prime istruzioni MSIL, riportate nelle righe seguenti:

IL_0000: ldarg.0 ; Load the object's 'this' pointer on the stack
IL_0001: ldc.i4.0 ; Load the constant 4-byte value of 0 on the stack
IL_0002: conv.i8 ; Convert the 4-byte 0 to an 8-byte 0
IL_0003: stfld int64 WordCounter::totalLines

Con l'istruzione IL_0000 viene caricato nello stack virtuale il primo parametro che era stato passato al metodo. Ad ogni istanza del metodo viene sempre passato l'indirizzo di memoria dell'oggetto. Questo argomento è chiamato Argomento zero e non è mai mostrato in modo esplicito nella firma del metodo. Pertanto, il metodo .ctor riceve sempre un argomento, anche quando sembra non riceverne alcuno. Con l'istruzione IL_0000, quindi, viene caricato nello stack virtuale il puntatore a questo oggetto.

Con l'istruzione IL_0001 viene caricato nello stack virtuale un valore costante a 4 byte di zero.

Con l'istruzione IL_0002 viene convertito il valore che si trova in cima allo stack, ovvero lo zero a 4 byte, in uno zero a 8 byte e il valore convertito viene posto in cima allo stack.

Lo stack contiene ora due operandi: lo zero a 8 byte e il puntatore all'oggetto. Con l'istruzione IL_0003 il valore che si trova in cima allo stack, ovvero lo zero a 8 byte, viene archiviato nel campo totalLines dell'oggetto identificato nello stack. Per questa operazione vengono utilizzati entrambi gli operandi.

Questa sequenza di istruzioni MSIL viene ripetuta per i campi totalChars, totalBytes e totalWords.

L'inizializzazione del campo wordCounter inizia con l'istruzione IL_0020, come mostrato di seguito:

IL_0020: ldarg.0
IL_0021: newobj instance void [mscorlib]System.Collections.SortedList::.ctor()
IL_0026: stfld class [mscorlib]System.Collections.SortedList WordCounter::wordCounter

Con l'istruzione IL_0020 viene eseguito il push del puntatore this di WordCounter nello stack virtuale. Questo operando non è utilizzato dall'istruzione newobj ma verrà utilizzato dall'istruzione stfld (IL_0026).

Con l'istruzione IL_0021 viene ordinato al runtime di creare un nuovo oggetto System.Collections.SortedList e di chiamare il relativo costruttore senza argomenti. Quando viene restituito newobj l'indirizzo dell'oggetto SortedList è nello stack. A questo punto per mezzo dell'istruzione stfld (IL_0026) viene archiviato il puntatore all'oggetto SortedList nel campo wordCounter dell'oggetto WordCounter.

Al termine dell'inizializzazione di tutti i campi dell'oggetto WordCounter, tramite l'istruzione IL_002b, viene eseguito il push del puntatore this nello stack virtuale, mentre con la riga IL_002c viene chiamato il costruttore nel tipo di base (System.Object).

L'ultima istruzione (IL_0031) è, naturalmente, l'istruzione di return che consente al costruttore WordCounter di ritornare al codice chiamante. I costruttori devono restituire void; in questo modo non verrà posto nulla nello stack prima del ritorno del costruttore.

Verrà ora analizzato un altro esempio. Fare doppio clic sul metodo GetWordsByOccurranceEnumerator per visualizzare il relativo codice MSIL, illustrato nella figura riportata di seguito.

Si può notare che la dimensione del codice di questo metodo è pari a 69 byte e che il metodo richiede quattro slot nello stack virtuale. Questo metodo, inoltre, presenta tre variabili locali: una è di tipo System.Collection.SortedList mentre le altre due sono di tipo System.Collections.IDictionaryEnumerator. I nomi delle variabili menzionati nel codice sorgente vengono emessi nel codice MSIL solo se l'assembly viene compilato con l'opzione /debug. Se non viene specificata l'opzione /debug, per le variabili verranno utilizzati i nomi V_0, V_1 e V_2 invece dei nomi sl, de e CS$00000003$00000000.

Questo metodo come prima cosa esegue l'istruzione newobj, che crea un nuovo oggetto System.Collections.SortedList e chiama il costruttore predefinito di tale oggetto. Quando viene restituito newobj, l'indirizzo dell'oggetto creato è nello stack virtuale. Con l'istruzione stloc.0 (IL_0005) questo valore viene archiviato nella variabile locale 0, oppure sl (V_0 se non è stata specificata l'opzione /debug). Quest'ultima variabile è del tipo System.Collections.SortedList.

Con le istruzioni IL_0006 e IL_0007 il puntatore this dell'oggetto WordCounter (contenuto nell'Argomento zero passato al metodo) viene caricato nello stack e viene chiamato il metodo GetWordsAlphabeticallyEnumerator. Quando viene restituita l'istruzione call, l'indirizzo dell'enumeratore è nello stack. L'istruzione stloc.1 (IL_000c) consente di salvare questo indirizzo nella variabile locale 1, oppure de (V_1 se non è stata specificata l'opzione /debug). Quest'ultima variabile è del tipo System.Collections.IDictionaryEnumerator.

L'istruzione br.s (IL_000d) determina una diramazione non condizionale alla condizione di test IL dell'istruzione while. Se la condizione di test IL inizia all'istruzione IL_0032, alla riga IL_0032 viene eseguito il push nello stack dell'indirizzo di de (o V_1), ovvero dell'oggetto IDictionaryEnumerator, mentre alla riga IL_0033 viene chiamato il metodo MoveNext di tale oggetto. Se il metodo MoveNext restituisce il valore true significa che esiste una voce da inserire nell'enumerazione. In questo caso l'istruzione brtrue.s passa all'istruzione alla riga IL_000f.

Con le istruzioni IL_000f e IL_0010 viene eseguito il push nello stack degli indirizzi degli oggetti in sl (o V_0) e de (o V_1). Viene quindi chiamato il metodo della proprietà get_Value dell'oggetto IdictionaryEnumerator per ottenere il numero delle occorrenze della voce corrente. Questo numero è un valore a 32 bit archiviato in un oggetto System.Int32. Viene quindi eseguito il cast di questo oggetto Int32 su un tipo di valore int. Eseguire il cast di un tipo di riferimento su un tipo di valore richiede l'istruzione unbox sulla riga IL_0016. Quando viene restituito unbox, l'indirizzo del valore di cui non è stato eseguito il boxing si trova sullo stack. Con l'istruzione ldind.i4 (IL_001b) viene caricato nello stack un valore a 4 byte che punta all'indirizzo attualmente nello stack. In altre parole, viene messo nello stack l'intero a 4 byte unboxed.

Con l'istruzione IL_001c viene eseguito il push nello stack del valore di sl (o V_1), ovvero dell'indirizzo dell'oggetto IDictionaryEnumerator, e viene chiamato il relativo metodo della relativa proprietà get_Key. Quando get_Key restituisce un valore, l'indirizzo dell'oggetto System.Object è nello stack. Poiché nel codice è specificato che il dizionario contiene stringhe, il compilatore esegue il cast di questo Object su una String utilizzando l'istruzione castclass alla riga IL_0022.

Le istruzioni che seguono (dalla riga IL_0027 alla riga IL_002d) consentono di creare un nuovo oggetto WordOccurrence e di passare l'indirizzo dell'oggetto al metodo Add dell'oggetto SortedLists.

Con l'istruzione IL_0032 viene nuovamente valutata la condizione di test dell'istruzione while. Se MoveNext restituisce il valore true verrà eseguito un altro ciclo. Se MoveNext restituisce il valore false il ciclo viene interrrotto e si riprende all'istruzione IL_003a. Con le istruzioni dalla riga IL_003a alla riga IL_0040 viene chiamato il metodo GetEnumerator dell'oggetto SortLists. Il valore restituito è un oggetto System.Collections.IDictionaryEnumerator, che viene lasciato nello stack per diventare il valore restituito di GetWordsByOccurrenceEnumerator.