Este documento presenta una introducción a la depuración avanzada con WinDbg y Visual Studio 2010. Explica conceptos como el modo kernel vs modo usuario, la arquitectura de memoria de Windows, callstacks y hilos. Luego cubre temas como la depuración de código nativo y .NET con WinDbg usando extensiones como SOS y SOSEX. Finalmente, propone algunos laboratorios prácticos para depurar problemas como bloqueos, crashes y fugas de memoria en aplicaciones .NET.
Depuración Avanzada Con Win Dbg Y Vs 2010 (Extendida)
1. Depuración Avanzada con WinDbg y VS 2010 Pablo Álvarez Doval Debugging & OptimizationTeam Lead (@PlainConcepts) pablod@plainconcepts.com
2. Agenda Depuración Avanzada: Introducción a la Depuración Nativa con WinDbg Depuración de Código .NET con WinDbg Depuración con Visual Studio 2010
3. Arquitectura de Windows Modo usuario vs. modo kernel Aplicaciones, procesos y threads Manejo de memoria de Windows CallStacks
4. Modo usuario vs. modo kernel Windows on Windows wowexec.exe UNIX LSA Shell Lsass.exe Client/Server csrss.exe Notepad notepad.exe Virtual DOS Machine ntvdm.exe Win32 Interix User Mode Kernel Mode ExecutiveServices I/O IPC Memoria Procesos Seguridad WM PNP Controlador Gráfico Object Manager FS Controladores de Dispositivos Microkernel Hardware AbstractionLayer (HAL)
5. Aplicaciones, procesos y threads Unaaplicaciónestáformadaporuno o másprocesos Un procesoes un ejecutable (.exe) queestá en memoria y queestáformadoporuno o más threads (hilos de ejecución) y suspropiosrecursos Piensa en un procesocomo en un contenedor de threads Un threades la unidadbásica de ejecuciónpara la que el sistemaoperativoreservatiempo de procesadorparallevar a cabounatarea Un thread es lo que la CPU ejecuta. Todoproceso vivo ha de tener al menos un thread, y a menudotienevarios
6. Manejo de memoria Win 32 bit (I) MemoriaFísica notepad.exe Windows Memory Manager 0x00000000 0x7FFFFFFF 0x80000000 0xFFFFFFFF 0x7C900000 Paginación (PF) RAM Sistema de memoria Virtual Drivers y aplicacionesalmacenaninformación en direccionesvirtuales, que el Windows Memory Manager convierte a unaposición de memoriafísica
7. Manejo de memoria Win 32 bit (II) sqlsrv.exe Proceso n Proceso 1 Proceso 2 Thread 1 Thread 1 Thread 1 Thread 1 Thread2 Thread2 Thread2 Thread2 … : : : : 2 Gb Thread n Thread n Thread n Thread n 4Gb Kernel 2 Gb
9. Thread Call Stacks Foto de un thread en un instante de tiempo Muestra la historia de llamadas a funciones Cada thread tienesupropio Call Stack Ejemplo: ntdll!KiFastSystemCallRet USER32!NtUserGetMessage+0xc notepad!WinMain+0xe5 notepad!WinMainCRTStartup+0x174 kernel32!BaseProcessStart+0x23
11. CallStacks (II) Cada frame tiene el siguiente callstack: Frame Parámetros Dirección de Retorno Frame Pointer Manejador de Excepciones Variables Locales Registros
12. Depuradores y símbolos Debugging Tools for Windows Otrosdepuradores Diferencias entre depuradores Símbolos y Servidores de Símbolos
13. Debugging Tools for Windows Descargagratuita: http://www.microsoft.com/whdc/devtools/debugging Actualizadascadapocosmeses Contienedepuradores, extensionespara los depuradores, herramientas y unaextensaayuda: windbg.exe, kd.exe, cdb.exe gflags.exe, tlist.exe, etc debugger.chm No necesita ser “instalado” parafuncionar
14. Otros depuradores Visual Studio Genial cuandodepurastupropiocódigo o depurascódigomanejado (.NET) DebugDiag (Debug Diagnostics Tool) Descargagratuita: IIS DiagnosticsToolkithttp://www.microsoft.com/windowsserver2003/iis/diagnostictools/default.mspx
16. Símbolos Los símboloshacenque el call stack sea útil: Sin símbolos: Con símbolos: kernel32!+136aa kernel32!CreateFileW+0x35f
17. Formatos de símbolos Formato Actual: .PDB FormatoAntiguo: .DBG Retail vs. Debug (Free vs. Checked) builds Símbolosprivados vs. públicos
18. Servidores de Símbolos Utiliza el sistema de ficheroscomobase de datosparasímbolos organizadospornombre y un identificadorúnico Estructura de directorios: SymSrvile_name.pdbnique_number___ Ejemplos: Symbolstdll.pdbB5EDCA52tdll.pdb Symbolstdll.pdb80FCC4F2tdll.pdb
20. Laboratorio 1: Escenario Un cliente tiene un problema con su SQL Server 2000, manifestado con errores 17883 en el log del SQL Server Al producirse los errores 17883, SQL Server genera automáticamente un volcado de memoria para su posterior estudio … 2004-05-27 13:01:40.10 server Error: 17883, Severity: 1, State: 0 2004-05-27 13:01:40.10 server Process 59:0 (834) UMS Context 0x125ABD80 appears to be non-yielding on Scheduler 1. …
21. Agenda Depuración Avanzada: Introducción a la Depuración Nativa con WinDbg Depuracion de Código .NET con WinDbg Depuración con Visual Studio 2010
22. Depuración de Código .NET WinDbg es un depurador Nativo Para depurar código .NET hay que usar extensiones: SOS.dll (hasta framework .NET 3.5) CLR.dll (framework 4.0) Extensión MUY recomendable: SOSEX v2, de Steve Johnson
23. Novedades en CLR 4 Ahora se encuentra en CLR.DDL Soporte DML Nuevas extensiones: !ThreadState, !DumpSigElem, !FindRoots, !ListNearObj(lno), !HistRoot, y muchos más… .loadbysosclr
25. Laboratorio 2: Depuración con SOS Para empezar a depurar se tiene que tener claro que algo mal ocurre. En los sucesivos ejemplos vamos a depurar una web en ASP.NET con diferentes tipos de errores cometidos durante su desarrollo. Identificaremos el problema y trataremos de aislarlo.
26. Laboratorio 2: Depuración con SOS Si hacemos una petición a http://localhost/BuggyBits/FeaturedProducts.aspx vemos que la pagina tarda +5 seg en ejecutarse. Si hacemos varias peticiones a la web con varios tabs, vemos que la web se bloquea.
27. Laboratorio 2: Depuración con SOS Para realizar varias peticiones a la vez tinyget -srv:localhost -uri:/BuggyBits/FeaturedProducts.aspx -threads:30 -loop:50 Una vez que tenemos todas la peticiones bloqueadas hacemos un dump del proceso (w3wp.exe) adplus –hang –pn w3wp.exe –quiet Ahora es el momento de empezar con WinDBG
28. Laboratorio 2: Depuración con SOS Pasos a seguir: ~* kb 2000 (pila de todos los threads del proceso) ~* e !clrstack(pila .net de todas los threads)
29. Laboratorio 2: Depuración con SOS En casi todas los threads hay llamadas a esté método System.Threading.Monitor.Enter(System.Object) Menos en un thread que hay una llamada a System.Threading.Thread.SleepInternal(Int32) Es muy posible que haya un deadlock (hay que investigar)
30. Laboratorio 2: Depuración con SOS Necesitamos mostrar todos los objetos que estan esperando a que se libere un bloqueo (salir de una región critica) !syncblk 0:028> !syncblk Index SyncBlockMonitorHeld Recursion Owning Thread Info SyncBlock Owner 7 018b6c2c 19 1 0ed5f8c8 1058 28 066affdc System.Object ----------------------------- Total 53 CCW 2 RCW 3 ComClassFactory 0 Free 27
32. Laboratorio 2: Depuración con SOS Efectivamente todos los threads esperan a que se libere el bloqueo de un objeto de tipo Object, podemos ver el código fuente para verificarlo. Además podemos asegurarnos de que ese objeto es el que usan todos los threads usando el comando !gcrootde sos, que nos permite ver cuales son los gcroots de un objeto, que son básicamente que objetos lo referencian. !gcroot 066affdc
33. Laboratorio 2: Comandos Útiles k : muestra la información de la pila nativa actual b : muestra los tres primero parámetros de las funciones .loadbysosmscorwks(carga sos en WinDbg) !clrstack : muestra la información de la pila administrada !syncblk : muestra información sobre los bloqueos lock(obj) { } !gcroot: muestra cuales son los objetos que referencian a este objeto
35. Laboratorio 3: Crash Navegamos por http://localhost/BuggyBits/Reviews.aspx, pulsamos el botón de Refresh. Pasados unos segundos el proceso w3wp.exe se estrella. En el registro del sistema encontramos una entrada de porque se ha estrellado. Es un problema bastante grave pues puede afectar a otros dominios de aplicación que se encuentren en ese proceso Tenemos que crear un dump completo de la aplicación para ver porque se estrella.
36. Laboratorio 3: Crash Volvemos a navegamos por http://localhost/BuggyBits/Reviews.aspx, pero ahora no pulsamos en el botón de refresh. Hay que iniciar adplus para hacer un dump del proceso cuando se estrelle. adplus -crash -pn w3wp.exe Aparece una ventana nueva en la barra de tareas. Pulsamos refresh y el proceso se estrella
37. Laboratorio 3: Crash El código nativo nos indica que estamos ante el finalizador de la clase, y es aquí donde se ha generado la excepción. La pila administrada no proporciona mucha información. Se sabe que es un NullReferenceExcepcion, gracias a !pe
38. Laboratorio 3: Crash Con el comando !dso, nos permite ver las variables locales de todos los frames de la pila. En la lista hay muchos objetos del tipo NullReferenceExcepcion y podemos encontrar un objeto del tipo Review
40. Laboratorio 3: Crash Obtenemos la información de la excepción a través del comando !pe 0258c2cc, que nos muestra el método que lanzo la excepción. 0:015> !pe 0258c2cc Exceptionobject: 0258c2cc Exceptiontype: System.NullReferenceException Message: Objectreferencenot set toaninstance of anobject. InnerException: <none> StackTrace (generated): SP IP Function 0E5AF860 0E6B0F74 App_Code_etrlku9h!Review.Finalize()+0x14 StackTraceString: <none> HResult: 80004003
41. Laboratorio 3: Crash Ya sabemos que la excepción la ha lanzado el objeto Review ahora podemos saber cual es la línea que lanzo la excepción en caso de que no tengamos el código fuente disponible. SOS incluye un comando !u, que permite desensamblar la memoria y ver el codigo X86 generado por el JIT con información extra a partir de la información de depuración. El comando !u, acepta la dirección de memoria del bloque de código, que la podemos obtener del EIP.
42. Laboratorio 3: Crash ¿Por qué se lanza esta excepción que nadie controla? Cual es la mejor manera de finalizar a un objeto CriticalFinalizerObject
44. Laboratorio 4: Gestión de Memoria En este laboratorio vamos a intentar solucionar un problema de fuga de memoria (memory leak) de una pagina ASP.NET. Lo más importante para resolver este tipo de problemas es saber que efectivamente nuestra aplicación tiene fugas de memoria. La única herramienta que hay para esto es ver la evolución de la memoria a través del tiempo con los contadores de rendimiento
45. Laboratorio 4: Gestión de Memoria Navegamos por http://localhost/BuggyBits/Links.aspx y vemos que ocurre. En principio la aplicación funciona correctamente, tenemos que estresarla de alguna manera para simular un carga alta. tinyget -srv:localhost -uri:/BuggyBits/Links.aspx -loop:4000
46. Laboratorio 4: Gestión de Memoria Ahora si observamos como el proceso reserva mucha memoria. Con los contadores de rendimiento podemos observar cual es el tipo de reserva que hace. En el resultado de los contadores de rendimiento hay que observar el resultado de la memoria de Win32 así como la memoria de .NET
47. Laboratorio 4: Gestión de Memoria Con el proceso (w3wp.exe) en este estado hay que hacer un dump para poder analizar el porque de esta fuga de memoria. adplus -hang -pn w3wp.exe –quiet Es el momento de empezar con WinDbg
48. Laboratorio 4: Gestión de Memoria Hay que ver el estado de la memoria para el proceso !address –summary
50. Laboratorio 4: Gestión de Memoria Se ha visto que casi la mayoría de la memoria del proceso viene de la sección de RegionUsageIsVAD ¿Porque? Examinamos los heaps de GC !eeheap –gc Tenemos dos GC Heaps Comparamos la memoria total del GC Heap con #Bytes in allHeaps
51. Laboratorio 4: Gestión de Memoria Ahora queremos saber cuantos objetos hay en el heap, de que tipo son y cuanta memoria ocupan. !dumpheap –stat ¿Cuantos objetos hay en total en los dos heaps? 140786 El objeto que más aparece en la pila es System.String 44375 instancias 721004504 TotalSize
52. Laboratorio 4: Gestión de Memoria Después aparece memoria libre, StringBuilders y objetos del tipo Link. Estos objeto de tipo Link (recordar la web que estamos depurando) son los que representar un link. !dumpheap -type Link !dumpmt 0e781684 !do 34d96fb0 !objsize 34d96fb0 Observamos que este objeto (Link) tiene dos campos url (StringBuilder) (offset 4) name (string) (offset 8)
53. Laboratorio 4: Gestión de Memoria Observamos que la mayoría de las cadenas estan entre un tamaño de 20000 y 25000 (ensayo y error), así que vamos a ver cuales son esos strings !dumpheap -mt <string MT> -min 20000 -max 25000 Cogemos cualquier objeto y vemos el contenido !do 35458344 0:000> !do 35458344 Name: System.String MethodTable: 5c9808ec EEClass: 5c73d64c Size: 20018(0x4e32) bytes (C:indowsssemblyAC_32scorlib.0.0.0__b77a5c561934e089scorlib.dll) String: http://blogs.msdn.com/tom Fields: MT Field Offset Type VT AttrValueName 5c982b38 4000096 4 System.Int32 1 instance 10001 m_arrayLength 5c982b38 4000097 8 System.Int32 1 instance 25 m_stringLength 5c9815cc 4000098 c System.Char 1 instance 68 m_firstChar 5c9808ec 4000099 10 System.String 0 sharedstaticEmpty >> Domain:Value 01cac948:024f01d0 01cddff0:024f01d0 << 5c98151c 400009a 14 System.Char[] 0 sharedstaticWhitespaceChars >> Domain:Value 01cac948:024f0728 01cddff0:064f0964 <<
54. Laboratorio 4: Gestión de Memoria Resulta que todas las cadenas que se utilizan para generar la lista de string no están siendo recolectadas con el GC, así que tenemos que encontrar que otros objetos hacen referencia a estas cadenas !gcroot 35458344 Parece ser que el objeto está referencia por un tipo Link, pero que este se encuentra en la cola de finalización !finalizequeue Parece que todos los objetos están en la cola de finalización pero no se están recolectando.
55. Laboratorio 4: Gestión de Memoria Hay que encontrar el thread finalizador para ver que está haciendo. El thread finalizador está marcado con Finalizer !threads Cambiamos al thread finalizador ~15s Kb 2000 !clrstack
56. Laboratorio 4: Gestión de Memoria El objeto Link en el finalizador tiene un Thread.Sleepque está haciendo que se bloquee el finalizador durante 5 segundos lo que hace que no se recolecten los objetos con la velocidad deseable. Hacer cualquier operación de bloqueo en el finalizador es extremadamente peligroso pues puede hacer que nuestro thread de finalización se bloquee y no podamos ser capaces de recolectar los objetos no utilizados.
57. Agenda Depuración Avanzada: Introducción a la Depuración Nativa con WinDbg Depuracion de Código .NET con WinDbg Depuración con Visual Studio 2010
58. ¡¿De verdad hemos llegado aquí?! No me creo que hayamos llegado aquí con tiempo suficiente…
59. Algunos Truquillos Bueno, ya que lo hemos conseguido, veamos algunos truquillos: SOS desde el VS.NET Análisis de Volcados de Memoria desde VS2010 Depurador Histórico TasksCallStacks
60. Recursos En Plain Concepts http://www.geeks.ms/blogs/palvarez http://www.geeks.ms/blogs/rcorral http://www.geeks.ms/blogs/luisguerrero En MSDN: http://blogs.msdn.com/tess/ En papel… Microsoft Windows Internals, 5th Ed. [Mark E. Russinovich and David A. Solomon]Microsoft Press. Debugging Applications for Microsoft .NET and Microsoft Windows[John Robbins]Microsoft Press.
61. ¿Preguntas ? Recuerda que en www.codecamp.es podrás encontrar todo el material de las sesiones del CodeCamp
63. Gestión de Memoria en el CLR Gestión de Memoria: El CLR usa un Recolector de Basura(GC) para administrar la memoria Minimiza errores de pérdida de memoria La asignación de memoria (new) es más rápida que en C++, pero la compactación supone una fuerte sobrecarga Para minimizar su impacto se recurre a un sistema generacional
64. GC – Ubicación de Recursos Sig. Obj. El CLR impone que todos los recursos sean ubicados en el ManagedHeap Se inicializa un MH por cada proceso Operador new: Se asegura de que tenga espacio Llama al constructor del objeto Devuelve el valor del Sig.Obj y lo actualiza Si no queda memoria libre, se realiza una recogida de basura o compactación En realidad, esto ocurre solo cuando la Generación 0 (clases mas recientes) esta completa. Espacio Libre Objeto C Objeto B Objeto A
65. Algoritmo de Recogida (I) El GC comprueba si hay memoria que no se este usando (objetos no referenciados). Si la hay realiza la liberación. Si el MH se llena y no se puede liberar (todos los objetos contiene referencias) salta un OutOfMemoryException Cada aplicación tiene sus raíces (roots). Estas son las posibles localizaciones de las posiciones de memoria que referencian a objetos en el MH (registros de la CPU, punteros a objetos estáticos y globales, variables locales, parámetros…). Las raíces se almacenan en el JIT y en el CLR. Cuando el GC se inicia, asume que todos los objetos en el heap son basura. Comienza a recorrer las raíces, construyendo recursivamente un grafo con todos los nodos alcanzables desde estas.
66. Algoritmo de Recogida (II) Si durante la comprobación de todas las raíces el GC se encuentra con un objeto que ya marco previamente, no lo volverá a seguir, por dos razones: Como optimización Para evitar caer en bucles infinitos, como seria el caso de las listas circulares. Al finalizar, el grafo contiene todos los objetos, que de un modo u otro son accesibles desde la aplicación. Todos los objetos que no aparezcan en el grafo se consideran basura. El GC recorre el heap y va compactando todos los objetos que no son basura, tratando la basura como espacio libre. El GC recompone las direcciones de memoria de los objetos en las raíces.
67. Algoritmo de Recogida (III) Raíces (Referencias Fuertes) Espacio Libre Objeto J Punteros Estáticos Punteros Globales Objeto I Objeto H Objeto G Punteros de la pila del hilo Sig. Obj. Objeto F Objeto E Objeto D Punteros en los Registros de la CPU Objeto C Objeto B Objeto A
68. Algoritmo de Recogida (IV) Raíces (Referencias Fuertes) Espacio Libre Objeto J Punteros Estáticos Punteros Globales Objeto I Objeto H Objeto G Punteros de la pila del hilo Sig. Obj. Objeto F Objeto E Objeto D Punteros en los Registros de la CPU Objeto C Objeto B Objeto A
69. Algoritmo de Recogida (V) Sig. Obj. Raíces (Referencias Fuertes) Espacio Libre Punteros Estáticos Punteros Globales Punteros de la pila del hilo Objeto H Objeto F Punteros en los Registros de la CPU Objeto D Objeto C Objeto B
70. GC - Generaciones (I) Son un mecanismo de optimización del GC. Un Recolector de basura generacional se basa en las siguientes suposiciones: Cuanto mas joven sea un objeto, mas pequeño será su ciclo de vida Cuanto mas viejo sea un objeto, mas largo será su ciclo de vida Los objetos mas jóvenes tienden a tener relaciones mas fuertes entre ellos, y son frecuentemente accedidos al mismo tiempo Compactar un fragmento del heap es mas rápido que compactar todo el heap
71. GC - Generaciones (II) Cuando se inicializa el GC, los nuevos objetos que se van ubicando en el MH son de generación 0. Esta generación comprende a los objetos mas jóvenes, que aun no han sido analizados por el GC. La próxima vez que ocurra una recogida de basura, los miembros de la generacion 0 que sobrevivan a esta se compactaran y quedaran en la parte de abajo del heap, pasando a ser la generación 1, mientras que los nuevos objetos que entren serán la nueva generación 0, y así sucesivamente. Actualmente, el CLR implementa un GC de 3 generaciones (0, 1 y 2).
72. GC - Generaciones (III) Objeto O Objeto J Objeto N Objeto I Objeto M Objeto H Objeto L Objeto G Objeto K Objeto F Objeto H Objeto E Objeto F Objeto D Objeto D Objeto C Objeto C Objeto B Objeto B Objeto A Gen. 0 Gen. 0 Gen. 1
73. Resumen del GC El GC del CLR es multi-generacional Implicaciones a nivel de rendimiento: ¡Las reservas son ultra rápidas! Principio de localidadfiable El CLR se aprovecha de lascachés L1 y L2 Coste de lasrecolecciones: Recolección de Gen. 0 similar a un fallo de página De 0 a 10 ms.para la generación 0 De 10 a 30 ms.para la generación 1 GCs diferentespara workstation y servidor
79. Problemas de la Finalización Los objetosfinalizables se promueven a generacionesmásviejas Incrementa el consumo de memoria Los objetosreferidos de modoindirectotambiénseránpromovidos y, portanto, no recolectados Se ralentiza la reserva de memoria, porque se debenmantenerpunteros en la lista de finalizacióntambién Afecta al rendimiento del GC No controlamos el momento en el que el método de finalizaciónseráinvocado No tenemosgarantíasobre el orden en que se finalizarán los objetos
80. Problema con Excepciones: El finalizador se invocaaunquesalteunaexcepción en la construcción del objeto: De modoquenuncadebemoshacerasuncionessobre el estado del objeto en el Finalizador Finalización class TempFile { String filename = null; public FileStreamfs; public TempFile(String filename) { // Aquipodríasaltarunaexcepción fs= new FileStream(filename, FileMode.Create); this.filename= filename; } ~TempFile() { if (filename != null) File.Delete(filename); } }
81. Finalización String filename; public FileStreamfs; public TempFile(String filename) { try { // Puedesaltarunaexcepcionaqui! fs = new FileStream(filename, FileMode.Create); this.filename= filename; } catch { // Si algova mal, pedimos al GC que no invoque al finalizadorGC.SuppressFinalize(this); throw; } } ~TempFile() { File.Delete(filename); } }
84. ¿Porquéusarhilos? La Ley de Moore... La malditaLey de Moore Escalamos en base a más CPUs Para permitirque la interfaz de usuarioresponda a los eventos de Windows mientras se hace un trabajo Para permitir el modelo de programaciónasíncrona de E/S (APM)
85. ¿Por qué NO usar hilos? Porque es difícil hacerlo bien No, en serio.. ¡Lo es! Produce Heisenbugsmuyfacilmente La sincronización de los hilosescomplicada En muchasocasiones no lo necesitamos Los hilos no son baratos
86. Hilos Unidadmínima de ejecución Los hilos no salen gratis Ocupan 1MB en el VAS para el stack + 12KB en el kernel Cuando se crea un nuevohilo, todaslas DLLs en el proceso padre son llamadasparanotificar de la creación Lo mismosucedecuando un hilova a morir Los cambios de conextoalmacenan los registros (700 bytes en x86, 1240 en x64, aúnmás en IA64) y emplean un spinlock, lo que consume tiempo
90. Anexo – Tipos de Bugs Heisenbugs (“Principio de Incertidumbre de Heisemberg”): Desaparece o altera su comportamiento al intentar depurarlo. Bohrbugs (“Modelo atómico de Bohr”): No cambia su comportamiento nunca. Siempre reproducible. Mandelbugs (“Conjunto de Mandelbrot”): Las causas son tan complejas que su comportamiento parece caótico. Schroedingbugs (“El Gato de Schrödinger”): El bug no se manifiesta hasta que alguien, leyendo el código o usando el programa de forma inusual, lo descubre. Desde ese momento, deja de funcionar para todo el mundo Stotle (“Aristoteles”): La aplicación esta bien, pero el conjunto de datos de prueba no.