Cause di arresto anomalo durante la garbage collection

Ho sofferto per un po ‘di tempo con un crash in un’applicazione C # che utilizza anche una buona parte di moduli C ++ / CLI che sono principalmente wrapper attorno alle librerie native per accedere ai driver dei dispositivi. Il crash non è sempre facilmente riproducibile, ma sono riuscito a raccogliere una mezza dozzina di crash dump che mostrano che il programma si blocca sempre con una violazione di accesso durante una garbage collection. Questo è il callstack nativo e l’ultimo registro eventi:

0:000> k ChildEBP RetAddr 0012d754 79f95a8f mscorwks!WKS::gc_heap::find_first_object+0x62 0012d7dc 79f933bb mscorwks!WKS::gc_heap::mark_through_cards_for_segments+0x493 0012d814 79f92cbf mscorwks!WKS::gc_heap::mark_phase+0xc3 0012d838 79f93245 mscorwks!WKS::gc_heap::gc1+0x62 0012d84c 79f92f5a mscorwks!WKS::gc_heap::garbage_collect+0x253 0012d878 79f94e26 mscorwks!WKS::GCHeap::GarbageCollectGeneration+0x1a9 0012d904 79f926ce mscorwks!WKS::gc_heap::try_allocate_more_space+0x15b 0012d918 79f92769 mscorwks!WKS::gc_heap::allocate_more_space+0x11 0012d938 79e73291 mscorwks!WKS::GCHeap::Alloc+0x3b 0:000> .lastevent Last event: 7e8.88: Access violation - code c0000005 (first/second chance not available) debugger time: Mon Sep 26 11:34:53.646 2011 (UTC + 2:00) 

Quindi permettimi prima di porre la mia domanda e fornire maggiori dettagli di seguito. La mia domanda è: oltre a un danneggiamento dell’heap gestito c’è qualche altra causa per un crash durante la garbage collection ?

Ora, elaborando un po ‘, la ragione per cui lo chiedo è perché sto avendo davvero difficoltà a cercare di identificare il codice che sta corrompendo l’heap gestito e non riesco a trovare un modello per la memoria che è (presumibilmente) sovrascritta.

Ho già provato a commentare tutti i codici C ++ / CLI “pericolosi” (in particolare le parti che usano maniglie bloccate), ma questo non ha aiutato. Nel tentativo di trovare uno schema nella memoria sovrascritto ho guardato il codice smontato al punto dello schianto:

 0:000> u .-a .+a mscorwks!WKS::gc_heap::find_first_object+0x54: 79f935b9 89450c mov dword ptr [ebp+0Ch],eax 79f935bc 8bd0 mov edx,eax 79f935be 8b02 mov eax,dword ptr [edx] 79f935c0 83e0fe and eax,0FFFFFFFEh 79f935c3 f70000000080 test dword ptr [eax],80000000h <<< r eax=00000000 ebx=01c81000 ecx=01c80454 edx=01c82fe0 esi=012f0000 edi=000027e1 eip=79f935c3 esp=0012d738 ebp=0012d754 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246 mscorwks!WKS::gc_heap::find_first_object+0x62: 79f935c3 f70000000080 test dword ptr [eax],80000000h ds:0023:00000000=???????? 

L’arresto si verifica durante il tentativo di dereferenziare il registro EAX che è null. Ora da quello che vedo EAX è stato caricato dal contenuto indicato dal registro EDX, quindi ho guardato l’indirizzo memorizzato lì:

 0:000> dd @edx-10 01c82fd0 06542778 00000000 00000000 01c82494 01c82fe0 00000000 00000000 00000000 00000000 01c82ff0 01b641d0 00000000 00000000 01c82380 

EDIT: ora vedo che la mia analisi era sbagliata, mancava una comprensione delle modalità di indirizzamento x86.

Quindi posso vedere che a partire dall’indirizzo 01c82fed (il valore memorizzato in EDX) i successivi 16 byte sono nulli. Ma guardando un altro crash dump simile vedo quanto segue:

 0:000> dd @edx-10 018defd4 00000000 00000000 00000000 00000000 018defe4 00000000 00000000 018b468c 01742354 018deff4 00e0907f 00000000 00000000 00000000 

Quindi qui i 16 byte prima dell’indirizzo puntati da EDX e il successivo 8 da lì sono nulli. E lo stesso accade negli altri crash dump che ho, non vedo uno schema qui, cioè non sembra che un pezzo di codice stia semplicemente sovrascrivendo questa regione della memoria.

Tornando alla domanda quello che vorrei sapere è se c’è qualche altra spiegazione per il crash oltre a un pezzo di codice che sovrascrive la memoria che non dovrebbe. O qualsiasi consiglio su come procedere, sono davvero perso in questo qui ..

(potrebbero le maniglie bloccate causare un problema? Ne abbiamo parecchie e quello che penso sia divertente è che vedo sempre 137 – non più niente meno maniglie con gchandles nel punto dello schianto, è una strana coincidenza per me ..).

EDIT : dimenticato di menzionare che stiamo usando la versione 3.5 del framework .Net. Vedo segnalazioni di crash simili in .Net 4 quando lo sfondo GC è attivo (da qualche parte c’è una menzione che si tratta di un bug in .Net) ma non penso che questo sia rilevante qui dato che AFAIK non ha GC di sfondo in .Net 3.5.

Non sono sicuro se questo aiuta, ma generalmente non utilizzare i distruttori o lasciare che GC gestisca la memoria non gestita. Usa invece il pattern Dispose e sposta invece tutto il codice del distruttore sui finalizzatori:

 ref class MyClass { UnsafeObject data; MyClass() { data = CreateUnsafeDataObject(); } !MyClass() // IDisposable.Dispose() { DeleteUnsafeDataObject(data); } ~MyClass() // Destructor { } } 

Questo implementerà il modello IDisposable sull’object. Chiama Dispose per cancellare i dati non gestiti, e nel peggiore dei casi avrai una migliore possibilità di capire cosa succede esattamente.

Quindi, sfortunatamente, la mia domanda è stata un po ‘fuorviante dal momento che cercavo spiegazioni alternative oltre a un danneggiamento dell’heap gestito, che alla fine si è rivelato essere il problema (causato da una copia non sicura di una struct non modificata). Il problema è ora risolto e sto postando le mie conclusioni qui in una risposta separata, spero che questo sia ok.

Probabilmente hai un’eccezione in uno dei tuoi finalizzatori. Credo che sia necessario controllarli uno per uno, perché non c’è spazio per errori nella coda di finalizzazione. Nel caso in cui non si disponga di codice non gestito, è meglio non avere alcun finalizzatore, basta chiamare manualmente Dispose.