SOS ... che non e' una richiesta di aiuto ...
In questo
caso SOS sta per Son Of
Strike.
Alcuni
debugger (tra gli altri CDB e WinDBG) espongono un meccanismo
di "estensione" tramite il quale DLL di terze parti, purchè ovviamente scritte
in maniera opportuna, possono aggiungere funzionalità a quelle native
del debugger stesso. Una sorta di meccanismo a plug-in,
insomma.
SOS è,
infatti, una dll di estensione ai debugger sopra citati che viene distribuita con l'SDK del .NET Framework e che
consente di visualizzare alcune strutture
interne del Common Language Runtime: informazioni su tipi, GC, thread e via
discorrendo.
Personalmente non sono un amante dei debugger a linea di
comando (come CDB) e non mi trovo spesso ad utilizzare WinDBG (forse perchè mi è
capitato raramente di dover fare debug su una macchina di produzione, dove
VisualStudio non è quasi mai installato, nè tanto meno installabile).
In realtà SOS è utilizzabile in modo estremamente semplice anche da VS.NET.
E'
sufficiente "attaccarsi" ad un processo col debugger in modalità nativa
(almeno), sospendere l'esecuzione, aprire la finestra Immediate
(Debug->Windows->Immediate) e passare (se già non è attiva) alla
modalità immediata digitando immed
al prompt.
Non resta che caricare la dll tramite:
.load sos.dll
(il path completo non dovrebbe
essere necessario, poichè l'installazione di Visual Studio ha messo a posto le variabili di
ambiente in modo opportuno).
SOS mette a disposizione una serie piuttosto
ampia (e ampliata col procedere delle versioni del CLR, anche in modo notevole con
la 2.0) di comandi.
Non ne riporto la lista poiché basta digitare
!help per ottenere l'elenco completo. L'ultima release, che
accompagna la beta 2, consente anche di ottenere alcune righe di informazione su
ogni singolo comando, semplicemente digitando !help
<nomecomando>.
L'utilità di questa "cosa" ?
Poca dal punto di vista meramente pratico (se dobbiamo
sviluppare un sito in ASP.NET o un'applicazione Windows probabilmente riusciamo a
farne a meno senza troppa difficoltà ...).
Molta dal
punto di vista "accademico": perchè, appunto, permette di catturare
informazioni sulle strutture interne al runtime e, quindi, sul reale
funzionamento di alcune delle sue componenti.
Per
rendere la cosa un po' meno astratta, riporto
un esempio che mostra come sia possibile visualizzare il codice x86 emesso
dal Jitter nella compilazione di un metodo.
Innanzitutto, scriviamoci un programma "scratch",
anche banale, come questo:
using System;
using System.Runtime.CompilerServices;
namespace SOS {
class Program {
static void Main() {
Console.Write("Aggiungo 10 a 5. Il risultato è: ");
Console.WriteLine(Add(5, 10).ToString());
Console.ReadLine();
}
[MethodImpl(MethodImplOptions.NoInlining)]
static Int32 Add(Int32 first, Int32 second) {
return first + second;
}
}
}
Il metodo Add, che è quello che andremo ad
esaminare, è volutamente marcato come "non-inlinabile", in modo tale che il
Jitter non ne inserisca il corpo sostituendolo alla chiamata in Main e, di
fatto, evitandone la compilazione ... dedicata.
Compiliamo in Release ed eseguiamo. Attacchiamo il
debugger al processo e sospendiamone l'esecuzione.Non resta che caricare SOS all'interno del processo
ed iniziare ad utilizzarne le funzionalità (.load sos.dll).
Il percorso che dovremo seguire per scoprire il
codice macchina generato dal Jitter per il metodo Add è un po' lungo, benchè
piuttosto lineare:
- si parte dalla struttura che rappresenta il tipo
Program in memoria ...
- dalla quale si ottiene l'elenco dei metodi
definiti o ridefiniti ...
- nel quale si cerca il metodo Add ...
- che
infine si decompila !
(1) Per visualizzare le informazioni su
un tipo al runtime si può utilizzare il comando
!DumpMT <indirizzo MethodTable> . L'indirizzo di memoria della MethodTable relativa al
tipo Program non è ovviamente noto a priori: esistono diversi modi per
ottenerlo, ma il più semplice consiste nel chiedere ad SOS di indicarcelo, a
partire dal nome del tipo e del modulo che lo contiene, tramite
il comando !Name2EE <nome modulo> <nome tipo>.
Quindi:
!Name2EE SOSTest.exe SOS.Program
--------------------------------------
MethodTable: 009350a8
EEClass: 00c233a4
Name: SOS.Program
(2) Poi ci facciamo elencare i metodi definiti da
Sos.Program. Lo switch MD (MethodDesc) indica a SOS la richiesta di un elenco
completo con le informazioni su ciascuna entry della tabella dei
metodi:
!DumpMT -MD 009350a8
EEClass : 00c233a4
Module : 00161f20
Name: SOS.Program
mdToken: 02000002 (D:\Test\.NET\SOSTest\bin\Release\SOSTest.exe)
MethodTable Flags : 80000
Number of IFaces in IFaceMap : 0
Interface Map : 009350f0
Slots in VTable : 7
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
79b9300b 79b93010 None [DEFAULT] [hasThis] String
System.Object.ToString()
79b9301b 79b93020 None [DEFAULT] [hasThis] Boolean
System.Object.Equals(Object)
79b9304b 79b93050 None [DEFAULT] [hasThis] I4 System.Object.GetHashCode()
79b9306b 79b93070 None [DEFAULT] [hasThis] Void System.Object.Finalize()
00c30058 00935080 JIT [DEFAULT] Void SOS.Program.Main()
00c300c8 00935090 JIT [DEFAULT] I4
SOS.Program.Add(I4,I4)
0093509b 009350a0 None [DEFAULT] [hasThis] Void
SOS.Program..ctor()
(3) Le informazioni disponibili sono diverse:
la locazione di memoria che contiene il codice del metodo (o il rimando al thunk
di compilazione), l'indirizzo della relativa MethodDesc, il fatto che il metodo
sia già stato compilato o meno dal Jitter (JIT/None), che sia un metodo statico
o di istanza ([hasThis]). Si può ottenere un dettaglio ulteriore
tramite:
!DumpMD 00935090
Method Name : [DEFAULT] I4 SOS.Program.Add(I4,I4)
MethodTable 9350a8
Module: 161f20
mdToken: 06000002 (D:\Test\.NET\SOSTest\bin\Release\SOSTest.exe)
Flags : 30
Method VA :
00c300c8
(4) In fondo si nota l'indirizzo (Virtual Address)
del codice del metodo in memoria. Non resta che decompilarlo:
!u 00c300c8
Normal JIT generated code
[DEFAULT] I4 SOS.Program.Add(I4,I4)
Begin 00c300c8, size 5
00C300C8 add ecx,edx
00C300CA mov eax,ecx
00C300CC ret
Voila, gioco finito :-)
Se vi
interessa, vi lascio un paio di link con qualche approfondimento e qualche
ulteriore esempio d'uso.
http://blogs.msdn.com/yunjin/archive/2005/05/15/417569.aspx
http://blogs.msdn.com/mvstanton/archive/2004/04/05/108023.aspx