Problèmes de mémoire en .NET: SOS.dll à la rescousse

Objectif de ce billet

Ce billet vise à présenter de façon sommaire la librairie SOS.dll et son utilité dans l’investigation de problèmes de mémoire dans une application .NET.

Présentation

L’utilisation du framework .NET, dans toutes ses versions, est désormais largement répandue. Avec ses belles promesses, comme la gestion de la mémoire automatique, il a conquis de nombreux adeptes réfractaires à cette tâche dans un langage sans Garbage Collector tel que le C++.

Malheureusement, nous avons maintenant des programmes qui consomment de plus en plus de mémoire, les développeurs ne faisant pas autant attention à leur conception de classes, et l’héritage excessif peut conduire à une consommation mémoire quelque peu gloutonne. Et soudain, alors que tout fonctionnait à merveille, c’est le drame : la terrible System.OutOfMemoryException! Ou encore, vous observez une utilisation mémoire qui vous semble extrême par rapport à votre domaine métier et les données que l’application gère.

Et nombre de développeurs sont plutôt désemparés face à de tels problèmes, et se lancent dans l’utilisation d’outils professionnels souvent coûteux, et lourds d’utilisation afin de tenter d’y voir clair. Et, comble de malheur, plus le logiciel fautif à analyser est complexe et consommateur de mémoire, plus l’utilisation de ces outils est lourde!

Heureusement, il y a un petit outil peu connu, d’une très grande légèreté et d’une accessibilité totale, étant directement intégré au Framework .NET, toutes versions confondues : il s’agit de SOS.dll.

Qu’est ce que SOS.dll?

Cette librairie, très judicieusement nommée, ne fait pas référence au fameux code de détresse, mais plutôt à un nom quelque peu poétique : Son Of Strike. Derrière ce nom se cache un petit couteau suisse du Framework .NET, qui pourrait bien vous rendre service. Il s’agit donc d’une librairie présente dans toutes les moutures du Framework .NET, et qui consiste en une extension de débogage complémentaire à un outil de débugger tel que Visual Studio ou encore WinDbg.

Pour avoir l’exhaustivité des fonctions de SOS.dll, avec exemples d’utilisation, vous pouvez visiter le site officiel de Microsoft :
http://msdn.microsoft.com/fr-fr/library/bb190764(v=vs.90).aspx

Son utilité

Elle permet, à partir de votre débuggeur préféré, d’avoir accès en temps réel aux détails d’utilisation de la mémoire de votre application.

Parmi ses fonctions utiles :

  • Avoir le détail d’utilisation des différentes zones mémoire (Gen1, Gen2 et Large Object Heap (LOH),
  • Avoir une vue d’ensemble, par type d’objets, du nombre d’instances et de classes, et de l’utilisation mémoire exacte en octets
  • Descendre jusqu’au niveau de détail des valeurs précises d’une instance d’un objet.
  • Retrouver les références racines qui gardent en mémoire un objet

Avantages de son utilisation:

  • Elle ne nécessite pas l’accès au code source
  • Peut s’utiliser à n’importe quel moment de l’exécution d’une application (au lancement, ou en cours d’utilisation)

Comment l’activer

Avant de pouvoir utiliser les fonctions de la librairie, il faut tout d’abord s’attacher au processus à débugger, que ce soit avec Visual Studio ou alors avec un débuggeur tel que WinDbg.

Visual Studio

Note: Avec Visual Studio, il est impératif, lors du débogage d’une solution, ou encore au moment de s’attacher, d’activer le débogage non managé.

  • Pour une solution en projet : Dans les propriétés du projet, onglet Debug, « Enable unmanaged code debugging »
  • Pour l’attachement à un processus en cours, dans la fenêtre qui liste les processus, Attach to :, bouton Select, et choisir «Native »

Ensuite, faire un break dans le débogage (bouton pause)

Dans la fenêtre Immediate Window, il faut charger l’extension :
!load sos.dll

Si tout se passe bien, la ligne suivante ou semblable apparaît:

extension C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded

Avec WinDbg

Note: L’emploi avec WinDbg permet son utilisation sur un environnement sur lequel on ne peut installer Visual Studio. Les outils de débogage Windows sont beaucoup plus légers et donc peuvent plus facilement être installés sur un environnement serveur, par exemple. De plus, afin de débugger en 64-bits, il est impératif d’utiliser WinDbg.

Après avoir attaché le debugger au processus à débugger, faire un break dans son exécution.

Ensuite, dans la fenêtre de commande, charger l’extension :
.load sos.dll

Normalement, à ce stade, tout est prêt pour l’utilisation de la librairie.

Principales fonctions

Notice d’utilisation : Toutes les informations affichées par les fonctions suivantes dépendent de l’état du GC. Afin d’être certain d’avoir un état des lieux précis, si on est dans un environnement Visual Studio, il suffit de forcer un GC.Collect (par la fenêtre Immediate) avant d’appeler ces. Cela forcera une libération de la mémoire temporaire qui peut être relâchée.

Afficher l’utilisation réelle mémoire

  • !eeheap –gc

Résultat

Number of GC Heaps: 1
generation 0 starts at 0x02381018
generation 1 starts at 0x0238100c
generation 2 starts at 0x02381000
ephemeral segment allocation context: none
segment begin allocated size
02380000 02381000 02443ff4 0x000c2ff4(798708)
Large object heap starts at 0x03381000
segment begin allocated size
03380000 03381000 03386de8 0x00005de8(24040)
Total Size 0xc8ddc(822748)
GC Heap Size 0xc8ddc(822748)

Interprétation : On peut voir, selon les résultats précédents, que notre application consomme 823 kilooctets, avec le détail des segments mémoire définis et leur type.

Afficher le détail d’utilisation mémoire (par classes)

  • !dumpheap -stat

Note : le résultat affiché est abrégé, sinon, c’est très long!!!

Résultat

total 10786 objects
Statistics:
MT Count TotalSize Class Name

048d2d0c 62 10872 System.Int32
048a5e14 46 11024 System.Reflection.CustomAttributeNamedParameter
048bc700 404 11312 System.Security.SecurityElement
048d17a0 84 13928 System.Char
048a5254 335 14740 System.Int16
048d2ba0 645 15480 System.Collections.ArrayList
048d32c0 95 20712 System.Collections.Hashtable+bucket
048a4340 1945 110224 System.Object
048d35e0 80 206516 System.Byte
048d0b70 1685 254076 System.String
Total 10786 objects

Interprétation :

Nous avons donc 1685 instances de String qui consomment 254 kilooctets, entre autres choses. La première colonne fait référence à l’adresse de la Method Table d’une classe, qui est utile pour d’autres appels. Et ce tableau nous montre l’ensemble des objets. Cette seule commande peut donc nous donner un très grand nombre d’informations

Note :

Pour les types objets, la consommation affichée ne comprend que l’objet en lui-même (ses membres de types valeurs et références), et non la consommation mémoire de ses sous-objets. Si un objet possède un dictionnaire, ou une liste, celle-ci ne comptera que pour sa référence (soit 4 ou 8 octets, selon qu’on est en 32 ou en 64 bits).

Autres paramètres d’appels utiles :

  • -stat : Vue sommaire (sinon, on a une ligne pour chaque instance!!!) (à combiner avec les autres paramètres)
  • -type NomType : Permet de filtrer la liste selon un nom partiel de types (ex : -type System.String)
  • -mt Adresse : Permet de chercher avec une adresse de Method Table spécifique@@

Autres fonctions en vrac

  • !objsize Adresse : permet d’avoir l’occupation mémoire d’une instance d’un objet et de tout ce qu’il référence, incluant la taille de chaque sous objet, contrairement à la commande !dumpheap
  • !gcroot Adresse : permet d’avoir la chaine de référence d’une instance précise d’un objet, afin de comprendre son rattachement au root et le fait qu’il ne soit pas libéré
  • !do Adresse : permet de lister le contenu d’un objet, incluant toutes les valeurs de ses membres

Conclusion

Voilà, il ne s’agissait que d’une introduction, à vous de découvrir les nombreuses autres fonctions de ce couteau suisse de l’investigation mémoire!
N’hésitez pas à me contacter si vous avez des questions à ce sujet.

Bon débogage!

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.