Come posso eseguire il debug di un errore interno in .NET Runtime?

Sto cercando di eseguire il debug di un lavoro che elabora file di grandi dimensioni. Il codice stesso funziona , ma ci sono errori sporadici segnalati dallo stesso Runtime .NET. Per il contesto, l’elaborazione qui è un file da 1,5 GB (caricato in memoria una sola volta) che viene elaborato e rilasciato in un ciclo, deliberatamente per cercare di riprodurre questo errore altrimenti imprevedibile.

Il mio frammento di test è fondamentalmente:

try { byte[] data =File.ReadAllBytes(path); for(int i = 0 ; i < 500 ; i++) { ProcessTheData(data); // deserialize and validate // force collection, for tidiness GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); } } catch(Exception ex) { Console.WriteLine(ex.Message); // some more logging; StackTrace, recursive InnerException, etc } 

(con un po ‘di tempismo e altre cose gettate dentro)

Il ciclo procederà bene per un numero non deterministico di iterazioni con pieno successo – nessun problema di sorta; allora il processo terminerà bruscamente. Il gestore di eccezioni non viene colpito. Il test richiede molto uso della memoria, ma segna molto bene durante ogni iterazione (non c’è una perdita di memoria ovvia, e ho un sacco di margine – 14 GB di memoria primaria inutilizzata nel punto peggiore del dente di sega) . Il processo è a 64 bit.

Il registro degli errori di Windows contiene 3 nuove voci, che (tramite il codice di uscita 80131506) suggeriscono un errore di Execution Engine, una piccola ctriggers critica. Una risposta correlata , suggerisce un errore GC, con una “correzione” per disabilitare il GC concorrente; tuttavia questa “correzione” non impedisce il problema.

Chiarimento: questo errore di basso livello non ha colpito l’evento CurrentDomain.UnhandledException .

Chiarimento: il GC.Collect serve solo a monitorare la memoria della sega, a verificare la presenza di perdite di memoria ea mantenere prevedibili le cose; rimuovendolo non si risolve il problema: si limita a tenere più memoria tra le iterazioni e rende più grandi i file dmp; p

Aggiungendo ulteriori traccie della console, ho osservato l’errore durante ciascuno di:

  • durante la deserializzazione (molte allocazioni, ecc.)
  • durante GC (tra un “approccio” GC e un GC “completo”, utilizzando l’API di notifica GC)
  • durante la convalida (solo foreach su alcuni dei dati) – curiosamente solo dopo un GC “completo” durante la convalida

Quindi molti scenari diversi.

Posso ottenere i file di crash-dump (dmp); come posso indagare ulteriormente, per vedere cosa sta facendo il sistema quando fallisce in modo così spettacolare?

Se hai dei dump di memoria, ti suggerirei di usare WinDbg per guardarli, supponendo che non lo stai già facendo.

Cercando di eseguire il commento !EEStack ( !EEStack mista nativa e stack gestito), e vedere se c’è qualcosa che potrebbe saltare nella traccia dello stack. Nel mio programma di test, ho trovato questo una delle volte come traccia dello stack in cui si verificava un FEEE (stavo volutamente rovinando l’heap):

 0: 000>! EEStack
 ---------------------------------------------
 Thread 0
 Frame corrente: ntdll! NtWaitForSingleObject + 0xa
 Caller RetAddr Child-SP, Callee
 00000089879bd3d0 000007fc586610ea KERNELBASE! WaitForSingleObjectEx + 0x92, chiamata ntdll! NtWaitForSingleObject
 00000089879bd400 000007fc5869811c KERNELBASE! RaiseException + 0x68, chiamata ntdll! RtlRaiseException
 [...]
 00000089879bec80 000007fc49109cf6 clr! WKS :: gc_heap :: gc1 + 0x96, chiamando clr! WKS :: gc_heap :: mark_phase
 00000089879becd0 000007fc49109c21 clr! WKS :: gc_heap :: garbage_collect + 0x222, chiamando clr! WKS :: gc_heap :: gc1
 00000089879bed10 000007fc491092f1 clr! WKS :: GCHeap :: RestartEE + 0xa2, chiamata clr! Thread :: ResumeRuntime
 00000089879bed60 000007fc4910998d clr! WKS :: GCHeap :: GarbageCollectGeneration + 0xdd, chiamando clr! WKS :: gc_heap :: garbage_collect
 00000089879bedb0 000007fc4910df9c clr! WKS :: GCHeap :: Alloc + 0x31b, chiamando clr! WKS :: GCHeap :: GarbageCollectGeneration
 00000089879bee00 000007fc48ff82e1 clr! JIT_NewArr1 + 0x481

Poiché questo potrebbe essere correlato al danneggiamento dell’heap dal garbage collector, proverei il comando !VerifyHeap . Almeno potresti assicurarti che l’heap sia intatto (e il tuo problema si trova altrove) o scoprire che il tuo problema potrebbe essere effettivamente con il GC o alcune routine P / Invoke che lo corrompono.

Se si scopre che l’heap è corrotto, potrei provare a scoprire quanta parte dell’heap è corrotta, cosa che potresti essere in grado di fare tramite !HeapStat . Questo potrebbe solo mostrare l’intero heap corrotto da un certo punto, però.

È difficile suggerire altri metodi per analizzare questo tramite WinDbg, dal momento che non ho idea di cosa stia facendo il tuo codice o di come sia strutturato.

Suppongo che se si scopre che si tratta di un problema con l’heap e quindi potrebbe significare che potrebbe essere una stranezza in GC, guarderei gli eventi GC CLR in Event Tracing per Windows.


Se i minidump che stai ottenendo non lo stanno tagliando e stai usando Windows 7 / 2008R2 o successivo, puoi usare Global Flags (gflags.exe) per colbind un debugger quando il processo termina senza eccezioni, se sei non ricevere una notifica WER.

Nella scheda Silent Process Exit , immettere il nome dell’eseguibile, non il percorso completo (es. TestProgram.exe ). Usa le seguenti impostazioni:

  • Selezionare Abilita monitoraggio uscita processo silenzioso
  • Controllare il processo di avvio del monitor
  • Per il processo di monitoraggio, utilizzare {path to debugging tools}\cdb.exe -server tcp:port=5005 -g -G -p %e .

E applica le impostazioni.

Quando il programma di test si arresta in modo anomalo, cdb si collegherà e attenderà la connessione. Avviare WinDbg, digitare Ctrl + R e utilizzare la stringa di connessione: tcp:port=5005,server=localhost .

Potresti essere in grado di saltare usando il debug remoto e invece usare {path to debugging tools}\windbg.exe %e . Tuttavia, il motivo per cui ho suggerito il telecomando, invece, è perché WerFault.exe , che credo sia quello che legge il registro e avvia il processo di monitoraggio, avvierà il debugger nella Sessione 0.

È ansible rendere la sessione 0 intertriggers e connettersi alla stazione della finestra, ma non riesco a ricordare come ciò sia stato fatto. È anche scomodo, perché dovresti passare da una sessione all’altra se è necessario accedere a una delle windows esistenti che hai aperto.

Tools->Debugging->General->Enable .Net Framework Debugging

+

Tools->IntelliTace-> IntelliTaceEbents And Call Information

+

Tools->IntelliTace-> Set StorIntelliTace Recordings in this directory

e scegli una directory

dovrebbe consentire di eseguire il comando INTO .net e tracciare ogni singola chiamata di funzione. L’ho provato su un piccolo progetto di esempio e funziona

dopo ogni sessione di debug suppone di creare una registrazione della sessione di debug. è la directory impostata anche se CLR muore se non sbaglio

questo dovrebbe consentire di arrivare alla chiamata estinta prima che il CLR collassasse.

Prova a scrivere un gestore di eccezioni generico e vedi se c’è un’eccezione non gestita che uccide la tua app.

  AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyExceptionHandler); static void MyExceptionHandler(object sender, UnhandledExceptionEventArgs e) { Console.WriteLine(e.ExceptionObject.ToString()); Console.WriteLine("Press Enter to continue"); Console.ReadLine(); Environment.Exit(1); 

Solitamente invesco i problemi relativi alla memoria con Valgrind e gdb.

Se gestisci le tue cose su Windows, ci sono molte buone alternative come verysleepy per callgrind come suggerito qui:
C’è un buon sostituto di Valgrind per Windows?

Se si desidera veramente eseguire il debug degli errori interni del runtime .NET, si ha il problema che non esiste alcuna fonte né per le librerie di classi né per la VM.

Dal momento che non è ansible eseguire il debug di ciò che non si possiede, suggerisco che (oltre a decompilare le librerie framework .NET in questione con ILSpy e aggiungendole al progetto, che comunque non copre il vm) si potrebbe usare il runtime mono.
Lì avete sia l’origine delle librerie di classi che della VM.
Forse il tuo programma funziona bene con mono, quindi il tuo problema verrebbe risolto, almeno finché si tratta solo di un’attività di elaborazione singola.

In caso contrario, vi è una vasta FAQ sul debug, incluso il supporto GDB
http://www.mono-project.com/Debugging

Miguel ha anche questo post riguardante il supporto valgrind:
http://tirania.org/blog/archive/2007/Jun-29.html

In aggiunta a ciò, se lo lasci girare su Linux, puoi anche usare strace , per vedere cosa succede nelle syscalls. Se non si dispone di un ampio utilizzo di winform o di chiamate WinAPI, i programmi .NET di solito funzionano bene su Linux (per problemi relativi alla distinzione tra maiuscole e minuscole del file system, è ansible eseguire il loop di un file system senza distinzione tra maiuscole e minuscole e / o utilizzare MONO_IOMAP ).

Se sei persona centrata su Windows, questo post dice che la cosa più vicina a Windows è il Logger.exe di WinDbg, ma le informazioni di ltrace non sono così ampie.

Il codice sorgente Mono è disponibile qui:
http://download.mono-project.com/sources/

Probabilmente ti interessano le fonti dell’ultima versione mono
http://download.mono-project.com/sources/mono/mono-3.0.3.tar.bz2

Se hai bisogno del framework 4.5, avrai bisogno di mono 3, qui puoi trovare pacchetti precompilati
https://www.meebey.net/posts/mono_3.0_preview_debian_ubuntu_packages/

Se vuoi apportare modifiche al codice sorgente, ecco come compilarlo:
http://ubuntuforums.org/showthread.php?t=1591370

Esistono eccezioni .NET che non possono essere catturate. Controlla: http://msdn.microsoft.com/en-us/magazine/dd419661.aspx .