¿De donde sale este DataRow?

Hace poco gracias al equipo DOT de PlainConcepts, tuve oportunidad de participara en un caso muy interesante, el software de la empresa en cuestión tenía un problema de fuga de memoria porque las referencias de los objetos no eran recolectadas por el GC, así que WinDGB en mano me puse a averiguar porque era.

Una de las primeras cosas que se hace cuando te encuentras un caso como este es intentar que la aplicación que vas a depurar se encuentre en ese caso, ósea que este fugando memoria, en mi caso lo era así que el segundo paso es hacer un dump completo de la aplicación. Esto lo podemos hacer con una herramienta que viene en la carpeta de instalación del “Debugging tools for Windows” que se llama ADPlus.vbs, la sintaxis sería la siguiente:

adplus -hang -pn miprograma.exe

Una vez hecho eso tenemos un dump completo de la aplicación en el estado de fuga de memoria que os comentaba antes, ahora tenemos que ponernos a investigar. Una de las cosas que suelo hacer siempre que me encuentro con un caso de fuga de memoria es hacer un !dumpheap para ver que nos encontramos en el heap (monton) de .NET, que es básicamente donde están todos los objetos que se instancian en el ciclo de vida de una aplicación .NET.

Aquí podemos ver el resultado en WinDBG:

0:003> !dumpheap -stat
total 5003168 objects
Statistics:
      MT    Count    TotalSize Class Name
6b441898        1           12 System.Collections.Generic.GenericEqualityComparer`1[[System.String, mscorlib]]
6b440b48        1           12 System.Security.Permissions.ReflectionPermission
6b43f850        1           12 System.Resources.FastResourceComparer
6b43ccec        1           12 System.__Filters
6b43cc9c        1           12 System.Reflection.Missing
6b43cba4        1           12 System.RuntimeType+TypeCacheQueue
6b43bcb8        1           12 System.Runtime.InteropServices.GCHandle
6b439cf0        1           12 System.RuntimeTypeHandle
6b4382c8        1           12 System.Text.DecoderExceptionFallback
6b438284        1           12 System.Text.EncoderExceptionFallback
6b41c7a8        1           12 System.Security.Permissions.FileDialogPermission
6b41c76c        1           12 System.Security.PolicyManager
6b41abec        1           12 System.Reflection.__Filters
6b41a004        1           12 System.DBNull
61e8508c        1           12 System.Data.IndexField[]
61e7f984        1           12 Bid+BindingCookie
00584a78        1           12 DataRowMemoryLeak.Foo
6ba6bc08        1           16 System.IO.TextReader+SyncTextReader
6b440ca0        1           16 System.Enum+HashEntry
6b43fad4        1           16 System.Resources.ResourceReader+TypeLimitingDeserializationBinder
6b43a0d8        1           16 System.Int64
6b439ef0        1           16 System.Globalization.GlobalizationAssembly
6b417760        1           16 System.Security.Permissions.UIPermission
61e85f8c        1           16 System.Data.DataRowBuilder
6b445bd4        1           20 System.Environment+ResourceHelper
6b441bac        1           20 System.Security.Permissions.EnvironmentPermission
6b438988        1           20 System.Text.StringBuilder
6b437a6c        1           20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
6b437a14        1           20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
6b437988        1           20 System.Text.InternalEncoderBestFitFallback
61e85754        1           20 System.Data.DataRowCollection
61e7f9c0        1           20 Bid+AutoInit
6b4447e8        1           24 System.Collections.BitArray
6b441914        1           24 System.String[][]
6b440e8c        2           24 System.OrdinalComparer
6b43a470        1           24 System.OperatingSystem
6b437d2c        1           24 System.IO.TextWriter+SyncTextWriter
6b4379d4        1           24 System.Text.InternalDecoderBestFitFallback
6b421078        1           24 System.Collections.Generic.List`1[[System.Int32, mscorlib]]
6b4192a0        1           24 System.Collections.Generic.List`1[[System.WeakReference, mscorlib]]
6b417bb0        1           24 System.Collections.Stack
61e85e44        1           24 System.Collections.Generic.List`1[[System.Data.Index, System.Data]]
61e854f8        1           24 System.Collections.Generic.List`1[[System.Data.DataViewListener, System.Data]]
61e8520c        1           24 System.Collections.Generic.List`1[[System.Data.DataView, System.Data]]
61e7da60        1           24 <CrtImplementationDetails>.ModuleUninitializer
6b43ecb4        1           28 System.Text.DecoderNLS
6b438f18        1           28 System.SharedStatics
6b4383a8        1           28 System.IO.Stream+NullStream
6b438188        1           28 System.Text.EncoderNLS
6b437bfc        1           28 Microsoft.Win32.Win32Native+InputRecord
6b441c5c        1           32 System.Text.UnicodeEncoding+Decoder
6b43ec2c        1           32 System.Text.UTF8Encoding+UTF8Decoder
6b43ae5c        1           32 System.Security.PermissionTokenFactory
6b43820c        1           32 System.Threading.IOCompletionCallback
6b438108        1           32 System.Text.UTF8Encoding+UTF8Encoder
6b41cbd8        1           32 System.Security.Util.Tokenizer+StringMaker
6b415650        1           32 System.Runtime.CompilerServices.RuntimeHelpers+CleanupCode
6b4155c4        1           32 System.Runtime.CompilerServices.RuntimeHelpers+TryCode
6b415394        1           32 System.Text.UnicodeEncoding
61e856e4        1           32 System.Data.ConstraintCollection
61e85630        1           32 System.Data.RecordManager
61e7f90c        1           32 Bid+CtrlCB
6b43ac78        1           36 System.Security.Permissions.FileIOPermission
6b43a028        1           36 System.Int64[]
6b41d0e0        3           36 System.Security.Policy.AllMembershipCondition
6b418058        1           36 System.Resources.RuntimeResourceSet
6b43fa50        1           40 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
6b43f694        1           40 System.IO.BinaryReader
6b43d778        2           40 Microsoft.Win32.SafeHandles.SafeFileHandle
6b43adb0        2           40 System.Security.PermissionToken
6b41edfc        1           40 System.Int32[][]
6b439338        1           44 System.AppDomainSetup
6b415be0        1           44 System.Threading.ReaderWriterLock
61e87720        1           44 System.Data.Common.StringStorage
61e875fc        1           44 System.Data.Common.Int32Storage
61e857c4        1           44 System.Data.DataRowCollection+DataRowTree
6b440e18        3           48 System.CultureAwareComparer
6b43d9e4        4           48 System.UInt16
6b43ba44        3           48 System.Text.DecoderReplacementFallback
6b43b9f4        3           48 System.Text.EncoderReplacementFallback
6b43a3f4        2           48 System.Version
6b41d2a0        4           48 System.Security.Permissions.StrongNamePublicKeyBlob
6b43f74c        1           52 System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Resources.ResourceLocator, mscorlib]]
6b415314        1           52 System.Resources.ResourceManager
6b43b8f0        2           56 System.Text.UTF8Encoding
6b438e20        1           56 System.Threading.Thread
6b4164b4        1           56 System.LogLevel[]
61e85674        1           56 System.Data.DataColumnCollection
6b440be4        1           60 System.UInt64[]
6b4242f8        1           60 System.IO.StreamReader+NullStreamReader
6b42420c        1           60 System.IO.StreamReader
6b41ac44        2           64 System.Reflection.TypeFilter
6b4190d8        2           64 System.EventHandler
6b43b674        1           68 System.Globalization.CultureTable
6b439d98        3           72 System.Reflection.Assembly
6b438b90        1           72 System.ExecutionEngineException
6b438b00        1           72 System.StackOverflowException
6b438a70        1           72 System.OutOfMemoryException
6b437c50        2           72 System.IO.__ConsoleStream
6b43786c        1           76 System.Text.SBCSCodePageEncoding
6b41d320        5           80 System.Security.Policy.ZoneMembershipCondition
6b41d230        4           80 System.Security.Policy.PolicyStatement
6b417fdc        1           80 System.Resources.ResourceReader
6b43cd48        3           96 System.Reflection.MemberFilter
6b43c23c        2           96 System.Reflection.Module
6b43a72c        8           96 System.Security.Permissions.SecurityPermission
6b4156d4        3           96 System.Globalization.CompareInfo
6b43cc64        1          100 System.Reflection.MetadataArgs+SkipAddresses
6b43b708        5          100 System.Globalization.CultureTableItem
6b43902c        1          100 System.AppDomain
6b4163c0        1          108 System.SwitchStructure[]
6b437e88        2          112 System.IO.StreamWriter
6b416db0        2          120 System.Collections.Hashtable+SyncHashtable
6b439f44        2          128 System.IO.UnmanagedMemoryStream
6b422314        4          128 System.Collections.ArrayList+SyncArrayList
6b417b4c        7          140 Microsoft.Win32.SafeHandles.SafeRegistryHandle
6b43a510        4          144 System.Security.PermissionSet
6b438c20        2          144 System.Threading.ThreadAbortException
6b41f04c        3          156 System.Security.Policy.PolicyLevel
6b43ae10        5          180 System.Security.Util.TokenBasedSet
6b43aa98       17          204 System.Int32
6b41794c        7          224 Microsoft.Win32.RegistryKey
6b43b7b0        2          256 System.Globalization.NumberFormatInfo
6b438468       22          264 System.Object
61e84fb4        2          296 System.Data.DataColumn
61e82cc8        1          296 System.Data.DataTable
6b43b624        7          336 System.Globalization.CultureTableRecord
6b43fc20        1          352 System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.Resources.ResourceLocator, mscorlib]][]
6b41ce18       10          360 System.Security.Policy.UnionCodeGroup
6b43b448        6          408 System.Globalization.CultureInfo
6b41f0a4        9          432 System.Security.NamedPermissionSet
6b43aea0       18         1008 System.Collections.Hashtable
6b41cd3c       65         1560 System.Security.Policy.StrongNameMembershipCondition
6b43b2bc       12         1676 System.Byte[]
6b4399cc      110         2200 System.RuntimeType
6b43af9c       18         2664 System.Collections.Hashtable+bucket[]
6b43947c       24         3444 System.Char[]
6b41cae8      216         6048 System.Security.SecurityElement
6b43a87c      266         6384 System.Collections.ArrayList
61e85bd0      347         9716 System.Data.RBTree`1+TreePage[[System.Data.DataRow, System.Data]]
00466bf0      235       395780      Free
6b43a9e8      368      4455624 System.Int32[]
6b43884c  1000553     31651760 System.String
61e85d88      347     32089008 System.Data.RBTree`1+Node[[System.Data.DataRow, System.Data]][]
6b414e78      320     37795900 System.Object[]
6b415b88  2999997     47999952 System.WeakReference
61e85048   999999     63999936 System.Data.DataRow
Total 5003168 objects

El caso es que tenía aproximadamente un millón de instancias de la clase System.Data.DataRow, que como bien sabéis representa una fila de un DataTable de ADO.NET, pues resulta que la aplicación por algún motivo seguía teniendo las referencias a esas DataRow.

Ya sabemos cuál es el problema, ahora tenemos que saber desde donde se referencia a esas DataRow y encontrar el trozo de código que lo hace, esto normalmente es lo más complicado porque tiene que conocer el software que estás depurando y en mi caso son clientes externos y no conozco el caso, así que lo que normalmente hago son dos cosas:

· Con el comando !gcroot le paso una dirección de memoria de uno de esos DataRow para ver desde que Thread y que pila se ha referenciado el objeto. Con el identificador del Thread cambio el WinDGB del Thread actual al nuevo thread con el comando “~23s” siendo 23 el identificador del Thread y ahora ejecuto un !clrstack con el que puedo ver la pila administrada del Thread actual. Ahora simplemente tengo que buscar en esas clases y en esos métodos referencias a los DataRows para encontrar la fuente de las referencias. Además puedo utilizar el comando !dso que me permite ver los parámetros de las funciones y las variables locales de todos lo métodos de la pila actual.

0:000> !dso
OS Thread Id: 0x7a0 (0)
ESP/REG  Object   Name
0030ef94 13cff17c Microsoft.Win32.SafeHandles.SafeFileHandle
0030efa4 13cff17c Microsoft.Win32.SafeHandles.SafeFileHandle
0030efd8 13cff3e0 System.Byte[]
0030efdc 13cff190 System.IO.__ConsoleStream
0030effc 13cff1c0 System.IO.StreamReader
0030f000 13cff1c0 System.IO.StreamReader
0030f018 13cff1c0 System.IO.StreamReader
0030f01c 13cff6f8 System.IO.TextReader+SyncTextReader
0030f03c 13cff6f8 System.IO.TextReader+SyncTextReader
0030f060 13cfe1b8 System.Int64
0030f064 1017bd90 System.String    total memory -
0030f068 13cfe1a8 System.WeakReference
0030f06c 0b597af8 System.Data.DataRow
0030f070 0180a954 System.Data.DataRowCollection
0030f074 01866d48 System.Collections.Generic.List`1[[System.WeakReference, mscorlib]]
0030f078 0180a954 System.Data.DataRowCollection
0030f07c 13cfe1ec System.String    total memory - 218038312
0030f080 13cfe1b8 System.Int64
0030f084 1017bd90 System.String    total memory -
0030f088 01866d70 System.Data.DataTable
0030f0a4 01866d3c DataRowMemoryLeak.Foo
0030f0b4 0b597af8 System.Data.DataRow
0030f0b8 0180a954 System.Data.DataRowCollection
0030f0bc 01866d48 System.Collections.Generic.List`1[[System.WeakReference, mscorlib]]
0030f0c0 0180a954 System.Data.DataRowCollection
0030f0c4 01866d70 System.Data.DataTable
0030f0c8 01866d3c DataRowMemoryLeak.Foo
0030f0e8 0b597af8 System.Data.DataRow
0030f0ec 0180a954 System.Data.DataRowCollection
0030f0f0 01866d48 System.Collections.Generic.List`1[[System.WeakReference, mscorlib]]
0030f0f4 0180a954 System.Data.DataRowCollection
0030f0f8 01866d70 System.Data.DataTable
0030f0fc 01866d3c DataRowMemoryLeak.Foo
0030f118 01866d24 System.String    ID
0030f120 0180a954 System.Data.DataRowCollection
0030f12c 01866d08 System.String    Name
0030f13c 01866d24 System.String    ID
0030f148 01866d08 System.String    Name
0030f150 01866d70 System.Data.DataTable
0030f154 01866d3c DataRowMemoryLeak.Foo
0030f158 0b597af8 System.Data.DataRow
0030f15c 01866d70 System.Data.DataTable
0030f160 01866d3c DataRowMemoryLeak.Foo
0030f16c 0180a1dc System.Object[]    (System.String[])
0030f400 0180a1dc System.Object[]    (System.String[])
0:000> !gcroot 088a1630
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
eax: 0030eec4 (invalid object)
Scan Thread 0 OSTHread 7a0
ESP:30eeb4: 0030eec4 (invalid object)
ESP:30f06c:Root:0b597af8(System.Data.DataRow)->
01866d70(System.Data.DataTable)->
0180a954(System.Data.DataRowCollection)->
0180a968(System.Data.DataRowCollection+DataRowTree)->
07f166bc(System.Object[])->
0b033360(System.Data.RBTree`1+TreePage[[System.Data.DataRow, System.Data]])->
088a1630(System.Data.RBTree`1+Node[[System.Data.DataRow, System.Data]][])

· Si todo esto no funciona también puedo hacer otro tipo de análisis de la aplicación. Con el Reflecto, programa que por cierto todo programador de .net debería de tener, podemos agregar la referencia del ensamblado el cual queremos analizar y el ensamblado de la clase la cual queremos saber si nuestro ensamblado referencia. En este caso miEnsamblado.dll y System.Data.dll que contiene System.Data.DataRow y pulsando el botón contrario encima de la clase tengo la opción de Analyze que me permite ver varias opciones.

  1. Depends On
  2. Used By
  3. Exposed By
  4. Instantiated By

image

 

· Como podréis imaginar las dos que me interesan son Used By y Instantiated By, en este caso Instantiated By no tanto porque no soy el responsable directo de crear las instancias de los DataRow, pero Used By me va a permitir ver de los ensamblados que tengo cargados cuales referencia a esta clase.

image

Así que como podéis ver la vida de un depurador no es fácil pero hay una serie de trucos que os pueden ayudar a solucionar la vida.

Espero que os haya gustado y hasta otra.

Saludos. Luis.

Actualización: (16 enero 2008)

Aquí esta la URL del ejemplo usado con WinDGB, http://www.luisguerrero.net/downloads/DataRowMemoryLeak.zip

Para sacar una dirección de memoria de un objeto y después usarlo para por ejemplo !do (DumpObject) o !gcroot tenemos que usar el comando !dumpheap que nos muestra toda la lista de objetos de hay en el heap (montón).net,SOS,Trucos.

Si hacemos un comando como este: !dumpheap –type System.Data.DataRow tenemos lo siguiente:

Address      MD
0b6d6598 77a95048       64
0b6d65f8 77a95048       64
0b6d6658 77a95048       64
0b6d66b8 77a95048       64
0b6d6718 77a95048       64
0b6d6778 77a95048       64
0b6d67d8 77a95048       64
0b6d6838 77a95048       64
0b6d6898 77a95048       64
0b6d68f8 77a95048       64
0b6d6958 77a95048       64
0b6d69b8 77a95048       64
0b6d6a18 77a95048       64
0b6d6a78 77a95048       64
total 1000696 objects
Statistics:
      MT    Count    TotalSize Class Name
77a95f8c        1           16 System.Data.DataRowBuilder
77a95754        1           20 System.Data.DataRowCollection
77a957c4        1           44 System.Data.DataRowCollection+DataRowTree
77a95bd0      347         9716 System.Data.RBTree`1+TreePage[[System.Data.DataRow, System.Data]]
77a95d88      347     32089008 System.Data.RBTree`1+Node[[System.Data.DataRow, System.Data]][]
77a95048   999999     63999936 System.Data.DataRow
Total 1000696 objects

0:004> !do 0b6d6a78
Name: System.Data.DataRow
MethodTable: 77a95048
EEClass: 779adf30
Size: 64(0x40) bytes
 (C:\Windows\assembly\GAC_32\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
77a92cc8  4000720        4 ...em.Data.DataTable  0 instance 01956d70 _table
77a95674  4000721        8 ...aColumnCollection  0 instance 018fa804 _columns
71ebaa98  4000722       18         System.Int32  1 instance       -1 oldRecord
71ebaa98  4000723       1c         System.Int32  1 instance   999964 newRecord
71ebaa98  4000724       20         System.Int32  1 instance       -1 tempRecord
71ebaa98  4000725       24         System.Int32  1 instance   999965 _rowID
77ea7fc8  4000726       28         System.Int32  1 instance        0 _action
71ebea68  4000727       38       System.Boolean  1 instance        0 inChangingEvent
71ebea68  4000728       39       System.Boolean  1 instance        0 inDeletingEvent
71ebea68  4000729       3a       System.Boolean  1 instance        0 inCascade
77a94fb4  400072a        c ...m.Data.DataColumn  0 instance 00000000 _lastChangedColumn
71ebaa98  400072b       2c         System.Int32  1 instance        0 _countColumnChange
77ec3bf4  400072c       10 ...em.Data.DataError  0 instance 00000000 error
71eb8468  400072d       14        System.Object  0 instance 00000000 _element
71ebaa98  400072e       30         System.Int32  1 instance 22676893 _rbTreeNodeId
71ebaa98  4000730       34         System.Int32  1 instance   999965 ObjectID
71ebaa98  400072f      498         System.Int32  1   static   999999 _objectTypeCount

0:004> !dumpmt 77a95048
EEClass: 779adf30
Module: 779a1000
Name: System.Data.DataRow
mdToken: 02000081  (C:\Windows\assembly\GAC_32\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll)
BaseSize: 0x40
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 99

De la primera columna podemos sacar la dirección del objeto que queremos investigar.

Forzar un BSOD en Windows

Hola a todos de nuevo !!

Cualquiera me puede decir que el título del post es un poco raro, y lo es !! :), resulta que quiero forzar un pantallazo azul, pero, ¿porqué?.

Como bien sabéis Windows es perfecto (cási perfecto) y solamente se puede desencadenar un BSOD cuando se produce una operación no valida dentro de la memoria del Kernel. Esto no ocurre todas las veces que uno quisiera.

Y claro imaginad por un momento que queréis hacer un dump completo porque tenéis un memory leak en una aplicación nativa que usa memoria virtual, y queréis explorar el árbol de VAD (Virtual Address Descriptors), que son los identificadores del Kernel de Windows para ver en que se está gastando la memoria, pero claro a esto solo tenemos acceso desde una depuración en modo kernel, !vad solo funciona si WinDBG está en modo kernel.

Pues nos os preocupéis que el driver de vuestro teclado os salvará la vida, tenéis que ir a esta entrada de la base de conocimiento de Microsoft y os explican como activarlo.

http://support.microsoft.com/kb/244139

Y funciona!!

Saludos. Luis.

Primera aplicación de Microsoft Surface

Este fin de semana, y gracias a un proyecto que tenemos ente unos amigos, hemos hecho la primera aplicación de Microsoft Surface de PlainConcepts.com

 

Es un videojuego, porque claro no vamos a hacer una aplicación comercial, (de esas hacemos muchas en el día a día), y el videojuego es para jugar al hockey sobre hielo, os dejo unas capturas de pantalla y un vídeo en mp4.

El videojuego está hecho en WPF (Windows Presentation Foundation) + API de Surface  + Pixel Shaders, y está hecho a 1080P con una resolución de 1920×1200

Flowkey Flowkey2

Flowkey3 Flowkey4

http://www.luisguerrero.net/downloads/flowkey.zip [Video MP4, 13MB]

Personas que han participado en este 12Meses12Proyectos, una oportunidad de hacerse rico, TM:

  • Luis Guerrero
  • Ricardo Acosta
  • Anton Molleda
  • Pedro Laguna
  • Olmo del corral
  • Vicente Cartas

Espero que os guste. Saludos. Luis.