Come determinare se un oggetto .Net è già stato finalizzato

Introduzione

Eccoci al mio secondo post. Vediamo insieme una parte di un problema che sto analizzando in questi giorni. Il problema è piuttosto complesso, ma in questo post ci concentriamo su una sottoparte.

Ho un dump relativo ad un crash di un processo e il problema, come al solito in questi casi, è quello di capire che cosa ha portato al crash. Useremo la debugger extension sos.dll insieme al debugger WinDbg, che fa parte dei Debugging Tools for Windows. Useremo anche .NET Reflector per leggere il “codice sorgente” dagli assembly della .NET Framework.

Il problema

Aperto il dump in Windbg e caricata l’estensione sos.dll tramite il comando .loadby, possiamo vedere gli oggetti .NET in uso nello stack corrente tramite il comando !DumpStackObjects. Omettiamo i dettagli di tutto questo perchè non sono strettamente correlati al problema.

Fra gli oggetti sul call stack trovo un oggetto di tipo ConnectionPointCookie (System.Windows.Forms.AxHost+ConnectionPointCookie):

 0:003>  !do 18573028
Name: System.Windows.Forms.AxHost+ConnectionPointCookie
MethodTable: 7b247000
EEClass: 7b00d37c
Size: 20(0x14) bytes
 (C:\WINDOWS\assembly\GAC_MSIL\System.Windows.Forms\2.0.0.0__b77a5c561934e089\System.Windows.Forms.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
7b247118  40012b1        4 ...+IConnectionPoint  0 instance 00000000 connectionPoint
79332d70  40012b2        8         System.Int32  1 instance        0 cookie
79332d70  40012b3        c         System.Int32  1 instance        1 threadId

 

Guardando i valori dei campi cookie e threadId, e guardando il codice della classe ConnectionPointCookie tramite .NET Reflector, risulta chiaro che questo oggetto si trova in uno stato non valido. E, sempre facendo riferimento al codice di questa classe, le cause possono essere 2:

  • è stato eseguito il metodo Disconnect()
  • l’oggetto è già stato finalizzato (potete notare che ConnectionPointCookie ha il metodo Finalize())

Cerchiamo di capire, con le informazioni che possiamo trovare nel dump, se l’oggetto si trova in questo stato perchè è già stato finalizzato, oppure perchè è stato eseguito il metodo Disconnect().

Debugging

Per prima cosa, vediamo in che generation del GC si trova il nostro oggetto:

 0:003>  !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x1860cbd0
generation 1 starts at 0x18571000
generation 2 starts at 0x01671000
...

Dunque il nostro oggetto, il cui indirizzo è 0x18573028, si trova in generation 1. Questo ci serve per il prossimo passo, che consiste nel curiosare nella coda di finalizzazione.

La coda di finalizzazione contiene i puntatori agli oggetti che implementano il metdo Finalize(). Quando un oggetto .NET è pronto per la Garbage Collection, il suo metodo Finalize() viene eseguito prima che la memoria venga liberata; a quel punto l’oggetto viene tolto dalla coda di finalizzazione.

Quindi l’idea è semplice: se il nostro ConnectionPointCookie con indirizzo 0x18573028 si trova nella Finalization Queue significa che non è stato ancora finalizzato.

 

 0:003>  !finalizequeue
SyncBlocks to be cleaned up: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 12 finalizable objects (13af1018->13af1048)
generation 1 has 290 finalizable objects (13af0b90->13af1018)
generation 2 has 9954 finalizable objects (13ae7008->13af0b90)
Ready for finalization 0 objects (13af1048->13af1048)
...

Andiamo a vedere, a questo punto, se il nostro oggetto 0x18573028 si trova fra i 290 oggetti di generation 1 che si trovano nella finalization queue. A questo scopo possiamo usare il comando s (Search in Memory) del debugger:

 0:003> s -d 13af0b90 13af1018 18573028 
13af0bf4  18573028 18572c5c 18572f84 1857bb44  (0W.\,W../W.D.W.

Il fatto che il puntatore al ConnectionPointCookie si trova nella coda di finalizzazione significa che Finalize() non è ancora stato chiamato.

Possiamo quindi concludere che l’oggetto si trova in uno stato non valido perchè ConnectionPointCookie.Disconnect() è già stato chiamato.