c# - ¿Cómo puedo depurar un error interno en.NET Runtime?
(5)
Hay excepciones de .NET que no se pueden atrapar. Consulte: http://msdn.microsoft.com/en-us/magazine/dd419661.aspx .
Estoy intentando depurar algún trabajo que procesa archivos de gran tamaño. El código en sí funciona , pero hay errores esporádicos informados desde .NET Runtime. Por contexto, el procesamiento aquí es un archivo de 1.5GB (cargado en la memoria una sola vez) siendo procesado y lanzado en un bucle, deliberadamente para tratar de reproducir este error impredecible.
Mi fragmento de prueba es básicamente:
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 poco de tiempo y otras cosas arrojadas)
El bucle procesará bien para un número no determinista de iteraciones con éxito completo - sin problemas de ningún tipo; entonces el proceso terminará abruptamente. El manejador de excepciones no recibe ningún golpe. La prueba implica mucho uso de memoria, pero tiene dientes de sierra muy bien durante cada iteración (no hay una pérdida obvia de memoria, y tengo mucho espacio libre - 14 GB de memoria primaria no utilizada en el peor punto del diente de sierra) . El proceso es de 64 bits.
El registro de errores de Windows contiene 3 entradas nuevas que (a través del código de salida 80131506) sugieren un error del motor de ejecución: un pequeño bicho desagradable. Una respuesta relacionada , sugiere un error de GC, con una "solución" para deshabilitar GC concurrente; sin embargo, esta "solución" no evita el problema.
Aclaración: este error de bajo nivel no afecta al evento CurrentDomain.UnhandledException
.
Aclaración: GC.Collect
está allí solo para monitorear la memoria dentado de la sierra, para verificar si hay pérdidas de memoria y para mantener las cosas predecibles; eliminarlo no hace que el problema desaparezca: solo hace que tenga más memoria entre iteraciones y hace que los archivos dmp sean más grandes;
Al agregar más rastreo de consola, he observado fallas durante cada uno de los siguientes:
- durante la deserialización (muchas asignaciones, etc.)
- durante GC (entre un "enfoque" de GC y un "completo" de GC, usando la API de notificación de GC)
- durante la validación (solo para
foreach
algunos de los datos), curiosamente justo después de que un GC "completo" durante la validación
Entonces muchos escenarios diferentes.
Puedo obtener archivos crash-dump (dmp); ¿Cómo puedo investigar esto más a fondo, para ver qué hace el sistema cuando falla tan espectacularmente?
Intenta escribir un manejador genérico de excepciones y comprueba si hay una excepción no controlada que mate tu aplicación.
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);
Si tienes volcados de memoria, te sugiero usar WinDbg para verlos, suponiendo que ya no lo hagas.
!EEStack
ejecutar el comentario !EEStack
(mezcla de pila nativa y administrada), y ve si hay algo que pueda saltar en el seguimiento de la pila. En mi programa de prueba, encontré esta vez como mi rastro de pila donde sucedió un FEEE (estaba corrompiendo deliberadamente el montón):
0:000> !EEStack --------------------------------------------- Thread 0 Current frame: ntdll!NtWaitForSingleObject+0xa Child-SP RetAddr Caller, Callee 00000089879bd3d0 000007fc586610ea KERNELBASE!WaitForSingleObjectEx+0x92, calling ntdll!NtWaitForSingleObject 00000089879bd400 000007fc5869811c KERNELBASE!RaiseException+0x68, calling ntdll!RtlRaiseException [...] 00000089879bec80 000007fc49109cf6 clr!WKS::gc_heap::gc1+0x96, calling clr!WKS::gc_heap::mark_phase 00000089879becd0 000007fc49109c21 clr!WKS::gc_heap::garbage_collect+0x222, calling clr!WKS::gc_heap::gc1 00000089879bed10 000007fc491092f1 clr!WKS::GCHeap::RestartEE+0xa2, calling clr!Thread::ResumeRuntime 00000089879bed60 000007fc4910998d clr!WKS::GCHeap::GarbageCollectGeneration+0xdd, calling clr!WKS::gc_heap::garbage_collect 00000089879bedb0 000007fc4910df9c clr!WKS::GCHeap::Alloc+0x31b, calling clr!WKS::GCHeap::GarbageCollectGeneration 00000089879bee00 000007fc48ff82e1 clr!JIT_NewArr1+0x481
Como esto podría estar relacionado con la corrupción de montón del recolector de elementos no utilizados, probaría el comando !VerifyHeap
. Al menos, puede asegurarse de que el montón está intacto (y su problema se encuentra en otra parte) o descubrir que su problema podría estar en realidad con el GC o algunas rutinas P / Invocar corrompiéndolo.
Si encuentra que el montón está dañado, podría intentar descubrir qué parte del montón está dañada, lo que podría hacer a través de !HeapStat
. Sin embargo, eso podría mostrar todo el montón corrupto desde cierto punto.
Es difícil sugerir otros métodos para analizar esto a través de WinDbg, ya que no tengo ninguna pista real sobre lo que está haciendo su código o cómo está estructurado.
Supongo que si consideras que se trata de un problema con el montón y, por lo tanto, significa que podría tratarse de rarezas de GC, miraría los eventos de CLR GC en Event Tracing para Windows.
Si los minivolcados que está recibiendo no lo están recortando y está usando Windows 7 / 2008R2 o posterior, puede usar Global Flags (gflags.exe) para adjuntar un depurador cuando el proceso termine sin una excepción, si usted está no recibir una notificación WER.
En la pestaña Silent Process Exit
, ingrese el nombre del ejecutable, no la ruta completa (es decir, TestProgram.exe
). Use la siguiente configuración:
- Marque Habilitar supervisión de salida de proceso silenciosa
- Verificar el proceso del monitor de inicio
- Para el proceso de supervisión, use
{path to debugging tools}/cdb.exe -server tcp:port=5005 -g -G -p %e
.
Y aplica la configuración.
Cuando el programa de prueba falla, cdb se conectará y esperará a que se conecte. Inicie WinDbg, escriba Ctrl + R y use la cadena de conexión: tcp:port=5005,server=localhost
.
Puede omitir el uso de la depuración remota y, en su lugar, usar {path to debugging tools}/windbg.exe %e
. Sin embargo, la razón por la que sugerí que sea remota, fue porque WerFault.exe
, que creo que es lo que lee el registro y lanza el proceso del monitor, iniciará el depurador en la sesión 0.
Puede hacer que la sesión 0 sea interactiva y conectarse a la estación de ventana, pero no recuerdo cómo se hizo eso. También es inconveniente, porque tendría que pasar de una sesión a otra si necesita acceder a cualquiera de sus ventanas existentes que tenía abiertas.
Usualmente identifico problemas relacionados con la memoria con Valgrind y gdb.
Si ejecuta sus cosas en Windows, hay muchas buenas alternativas, como verysleepy para callgrind como se sugiere aquí:
¿Hay un buen sustituto de Valgrind para Windows?
Si realmente desea depurar errores internos del tiempo de ejecución de .NET, tiene el problema de que no existe un origen para las bibliotecas de clases ni para la VM.
Como no puede depurar lo que no tiene, sugiero que (además de descompilar las bibliotecas de .NET framework en cuestión con ILSpy y agregarlas a su proyecto, que aún no cubre el vm), podría usar el mono tiempo de ejecución.
Ahí tienes tanto el origen de las bibliotecas de clase como de la VM.
Tal vez su programa funcione bien con mono, entonces su problema se resolverá, al menos mientras solo sea una tarea de procesamiento de una sola vez.
De lo contrario, hay una gran cantidad de preguntas frecuentes sobre la depuración, incluida la compatibilidad con GDB
http://www.mono-project.com/Debugging
Miguel también tiene esta publicación sobre el soporte valgrind:
http://tirania.org/blog/archive/2007/Jun-29.html
Además de eso, si lo dejas ejecutar en Linux, también puedes usar strace para ver qué ocurre en las llamadas de sistema. Si no tiene un uso amplio de winforms o llamadas WinAPI, los programas .NET generalmente funcionan bien en Linux (para problemas relacionados con la sensibilidad a mayúsculas y minúsculas del sistema de archivos, puede montar un sistema de archivos insensible a mayúsculas y / o utilizar MONO_IOMAP ).
Si eres una persona centrada en Windows, esta publicación dice que lo más parecido que tiene Windows es Logger.exe de WinDbg, pero la información de rastreo no es tan extensa.
El código fuente mono está disponible aquí:
http://download.mono-project.com/sources/
Probablemente estés interesado en las fuentes de la última versión mono
http://download.mono-project.com/sources/mono/mono-3.0.3.tar.bz2
Si necesita Framework 4.5, necesitará mono 3, puede encontrar paquetes precompilados aquí
https://www.meebey.net/posts/mono_3.0_preview_debian_ubuntu_packages/
Si desea realizar cambios en el código fuente, esta es la forma de compilarlo:
http://ubuntuforums.org/showthread.php?t=1591370
Tools->Debugging->General->Enable .Net Framework Debugging
+
Tools->IntelliTace-> IntelliTaceEbents And Call Information
+
Tools->IntelliTace-> Set StorIntelliTace Recordings in this directory
y elige un directorio
debería permitirle ingresar al código .net y rastrear cada llamada a una función. Lo probé en un pequeño proyecto de muestra y funciona
después de cada sesión de depuración, supone crear una grabación de la sesión de depuración. es el directorio establecido incluso si CLR muere si no estoy equivocado
esto debería permitirle llegar a la llamada extact antes de que CLR colapsase.