Un poco de windbg aplicado…

Ayer Rodrigo planteó un problema curioso en su blog, dios que rabia me dio no tener las herramientas instaladas en el portatil que tenía en casa!!!  sólo tenía ie y una tarjeta 3G para navegar por la red, a si que ni si quiera pude bajármelas :_)


Pero esta mañana me he puesto con ello en cuanto he llegado a la oficina 😛


Ahí va la aproximación que yo he seguido para intentar atajar el problema.


(Nota: Por si alguien lo intenta resolver… tened en cuenta que las direcciones de memoria vana  variar siempre y tenéis que buscar las que os apliquen a vosotros)


Empezando con el problema


Como es Rodrigo quien dice que la aplicación pierde memoria, pues me lo creo y listo, así que asumo que una vez la ejecute va a empezar a crecer la memoria. Hay un montón de herramientas para ver cómo crece la memoria, que tipo de memoria es la que crece, etcétera… una de las alternativas es adjuntar un depurador al proceso y pararlo cada X tiempo para ver como esta la memoria del proceso.


Herramientas


Como depurador he utilizado el WinDBG, diponible en las debugging tools for windows


Depurando el proceso


Compilamos y ejecutamos el ejemplo que nos pone Rodrigo.


Arrancamos WinDBG y Vamos a File .. Attach To Process y escogemos el proceso que acabamos de arrancar (buscad el pid adecuado).


image 


En la ventana de command del depurador vemos información del proceso, esta parado esperando a que introduzcamos alguna orden.


image


Por ahora no vamos a hacer nada… vamos a volver a activar el proceso para que siga corriendo un rato y empiece a cargarse la memoria, de modo que en la ventana de comandos, introducimos el comando g y damos a intro


image


Al de un minuto (por ejemplo) pulsamos ctrl + break … asi pararemos el proceso e inspeccionaremos su estado actual. Dado que Rodrigo nos ha dicho que es un problema de memory leak… vamos a tiro hecho y buscamos información sobre objetos que se acumulan en el managed heap (normalmente no es tan fácil y hay que invertir tiempo en determinar a que tipo de problema de memoria te enfrentas)


Obteniendo la información de referencia


Lo primero que vamos a hacer es cargar la SOS.dll. Esta librería es una extensión del depurador que nos va a permitir movernos por estructuras de datos manejadas por el CLR, de modo que nos facilita la depuración de código .net. Para cargarla ejecutamos este comando


.loadby sos mscorwks


Una vez esta cargada la extensión podemos usar sus comandos. Vamos a ver cuales son los objetos que hay en el managed heap


!dumpheap -stat


De la salida del comando nos quedamos con las últimas líneas (mayor número de objetos en memoria) y guardamos los números para poder compararlos en la próxima parada


790fd8c4     5632       345980 System.String
003ec570       18       4064392      Free
7912d8f8     2514      4291592 System.Object[]
79104c38   771803    12348848 System.WeakReference


Una vez apuntado, volvemos a ejecutar el comando g y dejamos que el proceso corra durante otro rato, para ver si varían los números.


…. ( minutos musicales mientras el proceso corre ) …


Volvemos a pararlo de nuevo con ctrl + break y volvemos a mostrar los objetos en el managed heap como antes


790fd8c4     5632       345980 System.String
003ec570       19      8258724      Free
7912d8f8     3678      8516160 System.Object[]
79104c38  1308060     20928960 System.WeakReference

Vemos que los Strings se han conservado, pero las WeakReferences han crecido ( 12348848 vs 20928960 ). Es recomendable hacer este proceso unas cuantas veces para tener mas muestras y poder establecer una tendencia 🙂


Una vez vemos que las weakreferences crecen, hemos de saber porqué…. a si que entramos en modo diagnostico diferencial de house…


house> ¿por qué no se liberan?


chase> será lupus?


cameron> no creo.. puede ser que alguien mantenga una referencia a ellos y por eso no los recoja el GC


house> ok.. analitica completa, TAC, rajadlo y mira las referencias que apuntan a los objetos weakreference


Para mirar las referencias, primero necesitamos saber la dirección en memoria de los objetos. Tomemos la salida del comando !dumpheap -stat, si nos fijamos en la fila relativa a las wekreferences


79104c38  1308060     20928960 System.WeakReference


El primer valor es la MethodTable del tipo, ahora que la conocemos, podemos ejecutar un comando que vuelca todos los objetos de una determinada method table


!dumpheap -mt  79104c38 


(el resultado hara scroll en pantalla varias veces, podéis pararlo con Ctr+break) Ahí tenemos todos los objetos del tipo, vamos a ver quién les esta referenciando y manteniéndolos vivos. La salida del comando tiene el siguiente formato: Dir.Objeto Dir.MethodTable Tamaño


Tomamos unas cuantas direcciones de objeto y ejecutamos el siguiente comando sobre ellas


!GCRoot dir del Objeto


Nos muestra la relación de referencias que mantienen vivo al objeto. Vemos al volcar unas cuantas que hay System.Collections.Generic.List que las referencia


0:003> !GCRoot 02497570
Note: Roots found on stacks may be false positives. Run “!help gcroot” for
more info.
Scan Thread 0 OSTHread 179c
Scan Thread 2 OSTHread 1624
DOMAIN(003E4AC0):HANDLE(Pinned):a13f0:Root:02df3030(System.Object[])->
01df4300(System.Collections.Generic.List`1[[System.WeakReference, mscorlib]])->
035d52e0(System.Object[])->
02497570(System.WeakReference)


Para seguir asegurando…


Volcamos la genericList y vemos su tamaño


!do 01df4300


79102290  40009c8        c         System.Int32  1 instance  1308060 _size


dejamos correr un rato al proceso ( g ) y volvemos a comprobar el tamaño del objeto ( ejecutamos el mismo comando )


79102290  40009c8        c         System.Int32  1 instance  1524568 _size

BINGO!!!  va creciendo woooooohoooooo


Si estuviésemos en un proyecto grande habría que localizar el assembly y volcar el código para ver que pasa…. como estamos en un ejemplo pequeño es fácil… no hay ningun array en nuestro código.. a si que con ayuda de reflector miramos la clase TraceSwitch… que nos lleva a Switch… y al siguiente código (resumido)


   lock (switches)

   {

       switches.Add(new WeakReference(this));

   }

El array switches crece indefinidamente y no se liberan las weakReferences (se liberan los strings a los que apuntan las weakreferences pero no estas en sí)

y ahora qué?

Pues he estado mirando por la web y no puedo decir aun si es un bug o si hay alguna consideración que nos hayamos saltado a la hora de trabajar con el TraceSwitch, en cuanto lo sepa os lo cuento

<update 28/4/08>
Tenemos respuesta del grupo de producto, es un bug corregido para la próxima major relase de .net 🙂
</update 28/4/08>

Happy Hacking!

David Salgado

Publicado por

3 comentarios en “Un poco de windbg aplicado…”

  1. “Como es Rodrigo quien dice que la aplicación pierde memoria, pues me lo creo y listo”

    Hay que ver cómo os cubrís los de Bilbao, el resto del post sobraba… No obstante, yo diría que es una feature.

    Vamos, que realmente es un bug “by design”, se me ocurren alternativas para solucionar este problema en concreto pero implican cambios de concepto que conducirían a pérdidas mayores en otros casos.

    Creo que me sigues, lo dejo ahí planteado para que lo remates ya que te has puesto con ello a muerte, yo tampoco tengo las herramientas ahora mismo instaladas… 😉

    Abrazo!

  2. Según los ejemplos de la documentación, por algún motivo pone que se crea un TraceSwitch por aplicación. ¿Será por eso? POrque normalmente ese comentario sobraría en un ejemplo.

  3. Ummmm…. interesante lo que comentas Anonimo… es cierto lo que comentas.

    Pero sería mucho mejor haber hecho la clase singleton si querian evitar que se creasen múltiples instancias… no ponerlo en un recondito comentario en los ejemplos de la documentación…

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *