Claudio Brotto

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:

  1. si parte dalla struttura che rappresenta il tipo Program in memoria ...
  2. dalla quale si ottiene l'elenco dei metodi definiti o ridefiniti ...
  3. nel quale si cerca il metodo Add ...
  4. 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

Posted: lug 28 2005, 12.08 by devlizard | with no comments
Filed under: