Tout comme les applications développées en .NET, qu'elles soient clientes (Windows Forms, WPF) ou serveurs (ASP.NET, Service Windows, WCF), les applications Silverlight ont aussi le droit d’être déboguées !
Comment analyser un crash ou une fuite mémoire de nos applications riche Internet ? "Tout simplement" (façon de parler) de la même manière que pour toutes les autres... avec WinDbg.
Cela veut donc dire que ce que nous avons vu sur le débogage .NET avec WinDbg et SOS est valable : C’est un luxe de pouvoir s’appuyer sur ses acquis !
Si vous n’êtes pas familier avec WinDbg et SOS, je vous invite à lire mes précédents articles :
Allez zou… Démonstration !
1. Exemple d’application
Prenons un exemple le plus simple possible. Le but est de pouvoir être capable de visualiser nos appels de fonctions et nos objets. Pour ce faire, j’ai créé un nouvelle application Silverlight et modifié le code associé à la page par défaut ("MainPage.xaml.cs") :
- J’utilise une liste d’objets d’un type particulier "Elt" > Ce qui me permettra de les différencier dans WinDbg
- Cette liste est rempli au démarrage et je marque un temps d’arrêt avec un message destiné à l’utilisateur > Ce qui me laissera le temps de lancer WinDbg et m’attacher au processus
Voici le code (mémorisez l’espace de nom)
| namespace ExempleSL { public partial class MainPage : UserControl { private List<Elt> listeElements = new List<Elt>(); public MainPage() { for (int i = 0; i < 10; i++) { listeElements.Add(new Elt() { Name = "elt" + DateTime.Now.Millisecond, Donnees = i }); } MessageBox.Show("Continuer ?"); InitializeComponent(); } public class Elt { public string Name { get; set; } public int Donnees { get; set; } } } } |
C’est parti : lancement de l’application avec CTRL+F5 dans Visual Studio.
Le message apparait : nous pouvons lancer le débogueur pour regarder ce que nous avons en mémoire.
2. S’attacher au processus avec WinDbg
Les applications Silverlight s’exécutent dans le navigateur. Il ne faut donc pas chercher à prendre un dump ou s’attacher à ExempleSL.exe mais plutôt à iexplore.exe.
Lançons WinDbg en tant qu’administrateur et attachons-nous au processus avec "FILE / Attach to process…" ou F6. Prenons iexplore.exe.
Ensuite, comme nous le faisons à l’accoutumé, 3 étapes :
- Renseignement du serveur de symboles
| .sympath SRV*c:\symbols*http://msdl.microsoft.com/download/symbols |
- Chargement de l’extension SOS pour Silverlight
| .load c:\Program Files (x86)\Microsoft Silverlight\3.0.40818.0\sos |
Notez bien que nous avons une version sos.dll par version du Framework dans C:\windows\Microsoft.NET\Framework\vX et un une version sos.dll par version de Silverlight.
3. Deboguer
La liste des commandes disponibles de SOS est donnée par

Pour savoir quel code est en cours d’exécution dans notre application, nous lançons une commande qui s’exécutera sur tous les threads du processus. Cette commande est "!ClrStack". Elle nous permet d’obtenir la pile d’appel d’un thread.
Beaucoup de threads ne sont pas en cours d’exécution de code Silverlight :
Mais le thread 5, l’est :
ESP EIP 02abd4f4 77cd9a94 [NDirectMethodFrameStandalone: 02abd4f4] MS.Internal.XcpImports.MessageBox_ShowCoreNative(IntPtr, System.String, System.String, UInt32, Int32 ByRef) 02abd510 03fd84f2 MS.Internal.XcpImports.MessageBox_ShowCore(System.String, System.String, UInt32) 02abd52c 03fd8449 System.Windows.MessageBox.ShowCore(System.String, System.String, System.Windows.MessageBoxButton) 02abd55c 03fd835c System.Windows.MessageBox.Show(System.String) 02abd56c 03f40376 ExempleSL.MainPage..ctor() 02abd5c8 03f401fe ExempleSL.App.Application_Startup(System.Object, System.Windows.StartupEventArgs) 02abd5e0 03fa69c9 System.Windows.CoreInvokeHandler.InvokeEventHandler(Int32, System.Delegate, System.Object, System.Object) 02abd6a0 03fa3b04 MS.Internal.JoltHelper.FireEvent(IntPtr, IntPtr, Int32, System.String) 02abd884 694917b0 [GCFrame: 02abd884] 02abd940 694917b0 [ContextTransitionFrame: 02abd940] 02abda38 694917b0 [UMThkCallFrame: 02abda38] |
Nous voyons donc noir sur blanc, le "cheminement" d’exécution.
- D’abord nous avons le démarrage de l’application avec "ExempleSL.App.Application_Startup"
- Puis le constructeur de la clase MainPage ("ExempleSL.MainPage..ctor")
- Et enfin, l’affichage de notre message : "System.Windows.MessageBox.Show"
Est-il possible de visualiser nos objets en mémoire ?
Oui, la méthode "!DumpHeap" nous donne tous les objets .NET en mémoire. Le paramètre "-type" nous permet de filtrer sur les objets dont le nom de la classe contient "ExempleSL".
| !DumpHeap -stat -type ExempleSL |

| total 13 objects Statistics: MT Count TotalSize Class Name 039d3dcc 1 24 System.Collections.Generic.List`1[[ExempleSL.MainPage+Elt, ExempleSL]] 039d3868 1 44 ExempleSL.App 039d3c24 1 84 ExempleSL.MainPage 039d3d6c 10 160 ExempleSL.MainPage+Elt |
Nous avons bien
- Notre liste
- 1 objet pour notre application
- 1 objet pour notre page
- Les 10 éléments de la liste
cqfd :-)
Point important pour vos développements natifs ou pour l’utilisation de composants COM sur plateformes x64 :
Un processus 64bits peut charger seulement des dlls 64bits. De la même manière, un processus 32bits peut charger seulement des dlls 32bits.
Ceci peut paraitre trivial comme affirmation, mais cela a son importance car un processus 64bits ne pourra donc pas charger de dll 32bits. L’inverse n’est pas possible non plus.
Voici la référence : http://msdn.microsoft.com/en-us/library/aa384231(VS.85).aspx
Ok, d’accord… Et pour la vérification, comment puis-je savoir si une dll est compilée en 32 ou 64 ?
Simplement avec l’outil DumpBin livré avec Visual Studio. Vous pouvez le lancer directement à partir de la ligne de commande Visual Studio 2008.
Par exemple :
| dumpbin "C:\Windows\System32\inetsrv\asp.dll" /HEADERS |
| dumpbin "C:\Windows\System32\inetsrv\asp.dll" /HEADERS | findstr "(x64)" |
| dumpbin "C:\Windows\SysWOW64\inetsrv\asp.dll" /HEADERS | findstr "(x86)" |
Ca peut servir :-)
Bye,
Sebastien.
Office 2007 apporte une fonctionnalité intéressante de propagation de clés de registre. Le principe est de pouvoir utiliser HKEY_LOCAL_MACHINE pour des clés qui se retrouvent normalement que dans HKEY_CURRENT_USER.
Cette fonctionnalité peut vous être utile, par exemple, dans le cadre d’un déploiement d’un Add-In Office 2007 qui ne peut se faire par définition que pour l’utilisateur courant (c’est à dire dans HKCU). Vous pouvez aussi en avoir besoin dans le cadre d’un paramétrage Office ne s’effectuant que dans la ruche HKCU comme l’affichage du ruban développeur Office.
Voici comment cela fonctionne.
- A chaque lancement d’applications Office, les clés contenus dans "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings" sont vérifiées
- Disons, par exemple, que l’on crée "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\MaPropagation" contenant notre logique de propagation. Nous pouvons ensuite créer des sous-clés "Create" qui copieront toutes les clés contenues de cette sous-clé vers HKCU. De la même façon, des sous-clé "Delete" effaceront toutes les clés correspondantes dans l’arborescence HKCU
Pour être concret, prenons l’exemple suivant :
| Windows Registry Editor Version 5.00 ; Installation ; Utilisation de la fonctionnalité Office 2007 de propagation des clés HKLM vers HKCU ; Exemple pour afficher le ruban developpeur Office [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\MaPropagation] "Count"=dword:00000001 ; "MaPropagation" est le code donnée à mon exemple ; "Count" est notre compteur qui sera propagé dans ; "HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\User Settings\MaPropagation" [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\MaPropagation\Create\Software\Microsoft\Office\12.0\Common\General] "DeveloperTools"=dword:00000001 ; Nous créons le dword "DeveloperTools" avec la valeur 1 dans ; "HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Common\General" |
Détaillons :
Nous mettons dans la base de registre le dword "Count" = 1 dans HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\MaPropagation. "MaPropagation" est le nom que j’ai choisi pour cet exemple.
Lorsque qu’Office va se lancer, il effectue un parcours de toutes les clés contenues dans "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings". Pour chaque clé, il va vérifier si la même clé existe dans "HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\User Settings". Dans notre exemple,
- Si la clé "MaPropagation" n’existe pas. La clé est créée avec le compteur et la propagation est faite
- Si la clé "MaPropagation" existe, le compteur est vérifié et la propagation est faite que si la valeur du compteur est différent de celle contenu dans "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\MaPropagation".
Cette vérification (de la valeur du compteur) permet de ne pas refaire indéfiniment la propagation à chaque lancement d’Office ! Justement, je pense que vous aurez compris que pour refaire un propagation qui modifiera ou supprimera des clés, nous devons modifier le compteur de "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\MaPropagation"
Au final, en quoi consiste cette fameuse propagation ? Très simplement :
- Les clés contenues dans "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\MaPropagation\Create" sont ajoutées/modifiées de "HKEY_CURRENT_USER"
- Les clés contenues dans "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\MaPropagation\Delete" sont supprimées de "HKEY_CURRENT_USER"
Dans notre exemple, nous créons le dword "DeveloperTools" dans "HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Common\General".
Pour résumé, avant tout lancement d’Office, seules les clés HKLM sont présentes. Lorsque un utilisateur lance Office, le mécanisme de propagation opère. C’est donc comme ceci que l’on peut déployer un Add-In VSTO 3 pour tous les utilisateurs Office 2007 (même ceux qui n’ont pas encore de profil dans la base de registre)
A titre d’exemple, voici les .REG qui permettent d’afficher/masquer le ruban développeur Office 2007
Bye,
Sebastien.
Ci-dessous, dans mes premiers articles sur le débogage WinDbg avec SOS, j’aborde le chargement des symboles/SOS, l’examen des piles d’appels et la visualisation des objets :
J’espère que ces billets vous sont utiles !
Afin que ce soit plus interactif, je mets à votre disposition le dump que j’ai pris et utilisé pour illustrer mes explications.
Vous le trouverez ici (66Mo)

Comme exercices, pourquoi pas essayer de trouver par vous-même
- Quels sont les threads qui exécutent des pages Web au moment de la prise du dump ? Combien sont-ils ?
- Quelles sont les pages Web exécutées (les urls) ?
- Et pour le plaisir : une dll est utilisée par le site Web. Elle s’appelle "BlogEngine.Core.dll". Quelle est la version de cette dll et à quelle date a-t-elle été compilée ?
Bon débogage :-)
Sebastien.
>>> To be continued…
Envie d’avoir un vrai moteur de recherche pour votre blog ou site Internet sans avoir en acheter un, le coder et le maintenir ou même l’héberger… Alors lisez bien ce qui suit !
De plus, pour une fois, je ne pense pas que l’on puisse faire plus simple ! Si peut-être un bouton "copy to clipboard" dans l’étape 3 de l’assistant :-) Vous verrez : il faut faire un CTRL+A et CTRL+C :-)

Suivez l’assistant
- en donnant l’url de votre site -
- en choisissant la taille de zone de recherche, les dimensions de la fenetre de résultat ainsi que sa couleur -
- en sélectionnant le HTML généré et le copier dans le presse-papier (c’est ici que l’on aurait pu rajouter un bouton :-) -
Coller ce HTML dans votre site
Le mieux est d’avoir un page maitre, comme cela : une seule insertion… C’est tout ! Ca fonctionne même immédiatement puisque c’est le moteur de recherche qui travaille pour nous ! Je l’ai adopté illico :-)
C’est tellement facile et tellement efficace ! Vraiment IMPRESSIONANT !!!
Qu’en pensez-vous ?
A bientôt,
Sebastien.
Dans le précédent billet, nous parlions des threads. Continuons… et intéressons-nous aux objets en mémoire.
Objets sur la pile d’appels
(pour dump stack objets)
!dso affiche tous les objets .NET contenus dans la pile d’appel du thread courant. Nous pouvons ensuite regarder ceux qui nous intéressent. Voici à quoi cela ressemble :
Deux informations sont importantes ici :
- La deuxième colonne donne l’adresse mémoire de l’objet - sur 64bits s’il vous plait:-)
- La troisième colonne indique le "type" de l’objet ; c’est à dire sa classe
Voici un extrait du résultat de !dso :
RSP/REG Object Name
...
00000000039ae340 000000013f4655d0 System.String
00000000039ae350 000000013f5df210 System.Web.HttpRequest
00000000039ae358 000000013f5e5920 System.Web.UI.ControlCollection
00000000039ae360 000000013f5e56d0 System.Web.UI.HtmlControls.HtmlForm
00000000039ae370 000000013f4656b0 System.String
00000000039ae388 000000013f5e56d0 System.Web.UI.HtmlControls.HtmlForm
00000000039ae3b8 000000013f5e0de8 ASP.search_aspx
...
Pour obtenir plus d’information sur un objet, il suffit de faire double clic puis clic droit sur son adresse mémoire. Cela a le même effet que de faire une sélection de l’adresse puis un CTRL+C. A ce moment là, l’adresse est copiée dans le presse papier. Nous pourrons la coller par la suite avec un clic droit dans la zone de saisie ou un CTRL+V.
Prenons, par exemple, l’objet HttpRequest stocké sur la pile d’appel :
00000000039ae350 000000013f5df210 System.Web.HttpRequest
Analyse d’un objet
(pour dump objet)
!do affiche l’objet ainsi que toute sa structure interne. Nous avons donc directement les valeurs des propriétés qui composent l’objet ou bien leur adresse mémoire. Nous pouvons donc renouveler la même opération sur les adresses mémoires des propriétés/objets qui nous intéressent.
Suivez le guide…
pour l’objet de type System.Web.HttpRequest
Vous avez repéré l'adresse de la propriété
_url ?
pour l’objet
_url de type
System.Uri contenu dans l’objet HttpRequest précédent
Un dernier petit effort pour avoir l’url demandée par cette requête HTTP, voyons voir "m_String" :
pour l’objet
m_String de type
System.String contenu dans l’objet Uri précédent
Enfin, nous y voila
String: http://localhost:80/BlogEngine/search.aspx?q=BlogEngine
Tout simplement fantastique ! :-) Et ce n’est que le début. "!DumpHeap -stat" vous connaissez ?
Bye, Sebastien.
>>> Suite : Debogage .NET avec WinDbg et SOS - Travaux Pratiques
Le débogage .NET diffère du débogage natif dans la mesure ou les objets et les piles d’appels qui nous intéressent ne sont pas visibles/exploitables sans utiliser une extension WinDbg nommée SOS.
Chargement de l’extension
Pour charger SOS dans WinDbg, la syntaxe est la suivante :
Vous pouvez vérifier la liste des extensions chargées avec
Pour information, le déchargement se fait avec
Démarrage
Permet d'obtenir lu numéro de version exact de la CLR chargée dans le processus.

La liste des commandes disponibles de l’extension s’obtient par

Vous trouverez l’aide complète en ligne sur MSDN : SOS Debugging Extension (SOS.dll) - http://msdn.microsoft.com/en-us/library/bb190764.aspx
Afin de visualiser un semblant d’activité, j’utilise TinyGet (un peu brusquement :-)) avec le lancement des deux commandes suivantes (2x50 utilisateurs simultanés effectuant 100 requêtes, les unes à la suite des autres)
| tinyget.exe -srv:localhost -uri:/BlogEngine/ -status:200 -threads:50 -loop:100 |
et
| tinyget.exe -srv:localhost -uri:/BlogEngine/search.aspx?q=BlogEngine -status:200 -threads:50 -loop:100 |
Pendant l’exécution, après avoir lancé WinDbg en tant qu’administrateur, je m’attache au processus W3WP.EXE avec le menu FILE/Attach to process…
Ensuite comme d’habitude :
| Chargement des symboles | .sympath SRV*c:\symbols*http://msdl.microsoft.com/download/symbols/ |
| Demande de chargement | .reload |
| Chargement de SOS | .loadby sos mscorwks |
--
Voyons voir ce que nous avons et ce que nous pouvons obtenir avec WinDbg et SOS. Les questions que l’on peut se poser sont par exemple :
- Combien de threads exécutent des pages ASPX ?
- Quelles sont les pages exécutées ?
- Où en est leur traitement ?
Piles d’appels ou "call stacks"
Vous remarquerez le nombre en bas à droite de la fenêtre de Windbg : il s’agit du numéro du thread sur lequel nous somme positionné

Pour visualiser la pile d’appel de ce thread, la commande est

Loin d’être intuitif n’est pas ? En fait, c’est normal puisque nous visualisons les informations natives de la pile d’appels. Pour obtenir la pile d’appels managée, nous utilisons

Pour information, le changement de thread se fait avec
Où XX est le numéro du thread.
Comme raccourci, pour exécuter une commande sur tous les threads, nous pouvons utiliser
"e" permet d’exécuter une commande (contenu dans un extension - c'est le cas pour SOS)
Donc, si l’on reprend la pile d’appels du thread 20,
capture ci-dessus
System.Web.UI.HtmlTextWriter.WriteUTF8ResourceString(IntPtr, Int32, Int32, Boolean)
ASP.search_aspx.__RenderContent1(System.Web.UI.HtmlTextWriter, System.Web.UI.Control)
System.Web.UI.Control.RenderChildrenInternal(System.Web.UI.HtmlTextWriter, ... ASP.themes_standard_site_master.__Render__control5(System.Web.UI.HtmlTextWriter, ...
System.Web.UI.Control.RenderChildrenInternal(System.Web.UI.HtmlTextWriter, ...
System.Web.UI.HtmlControls.HtmlForm.RenderChildren(System.Web.UI.HtmlTextWriter)
System.Web.UI.HtmlControls.HtmlForm.Render(System.Web.UI.HtmlTextWriter)
System.Web.UI.HtmlControls.HtmlForm.RenderControl(System.Web.UI.HtmlTextWriter)
ASP.themes_standard_site_master.__Render__control1(System.Web.UI.HtmlTextWriter, ...
System.Web.UI.Control.RenderChildrenInternal(System.Web.UI.HtmlTextWriter, ...
System.Web.UI.Control.RenderChildrenInternal(System.Web.UI.HtmlTextWriter, ...
System.Web.UI.Page.Render(System.Web.UI.HtmlTextWriter)
BlogEngine.Core.Web.Controls.BlogBasePage.Render(System.Web.UI.HtmlTextWriter)
System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
System.Web.UI.Page.ProcessRequest()
System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
ASP.search_aspx.ProcessRequest(System.Web.HttpContext)
System.Web.HttpApplication+CallHandlerExecutionStep...
System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)
System.Web.HttpApplication+ApplicationStepManager.ResumeSteps(System.Exception)
System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest ...
System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest)
System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest)
System.Web.Hosting.ISAPIRuntime.ProcessRequest(IntPtr, Int32)
Nous avons le nom de la page éxécutée : "search.aspx”. Elle est donnée par
| ASP.search_aspx.ProcessRequest(System.Web.HttpContext) |
Comme j’ai le code source sous la main, c’est plus facile ensuite de confirmer :-) :
La page maitre est "site.master" correspondant au thème "Standard" :
| ASP.themes_standard_site_master.__Render__control1(System.Web.UI.HtmlTextWriter, System.Web.UI.Control) |
Vérification :-) :
Si l’on décortique la pile d’appels, nous obtenons un peu plus en détails. Nous avons l’enchainement des toutes les fonctions appelées. A noter que la lecture se fait de bas en haut (du plus ancien appel au plus récent).
La pile d’appels nous apprend que l’exécution de la page "search.aspx" a demandé le chargement de la classe "BlogEngine.Core.Web.Controls.BlogBasePage" et l'exécution de sa méthode "Render" : Nous devons être dans le cas d’un classe de base héritée.
| ASP.search_aspx.ProcessRequest(System.Web.HttpContext) |
puis
| BlogEngine.Core.Web.Controls.BlogBasePage.Render(System.Web.UI.HtmlTextWriter) |
Nous avons ensuite, la demande de chargement de la page maitre et des contrôles contenus :
| ASP.themes_standard_site_master.__Render__control1(System.Web.UI.HtmlTextWriter, ... |
Enfin, au moment du débogage, nous étions toujours en dans la phase de génération la page Web :
| System.Web.UI.HtmlTextWriter.WriteUTF8ResourceString(IntPtr, ... |
C’est pas mal pour un début non ? La suite au prochain épisode.
A bientôt, Sebastien.
>>> Suite : Debogage .NET avec WinDbg et SOS - Objects
Ce n’est pas une information de première fraicheur :-) mais compte tenu qu’elle n’est pas très documentée, je la relaie dans cette article.
Depuis ASP.NET 2.0, nous avons l’élément <deployment> dans <system.web> qui permet de définir à un seul endroit (Machine.config) si les applications ASP.NET présentes sur le serveurs sont en production :
| <configuration> <system.web> <deployment retail="true"/> </system.web> </configuration> |
Ce paramétrage a deux effets :
- Rétablir les performances optimales d’ASP.NET (Compilation, cache des ressources AXD, gestion mémoire, timeouts des pages) qui étaient réduites par le mode debug
- Et purement et simplement désactiver le debug=true, la trace ASP.NET et les messages d’erreurs ASP.NET complets
Ceci quelque soient les paramétrages mis dans les Web.config.
Je vous conseille d’appliquer ce paramétrage systématiquement dans la production.
Et justement, par curiosité, avez-vous testé de rechercher si vous aviez du debug="true" sur vos serveurs ?
A
A bientôt,
Sebastien,
-
deployment Element - http://msdn.microsoft.com/en-us/library/ms228298.aspx
When retail is set to true, ASP.NET disables certain configuration settings such as trace output, custom errors, and debug capabilities.
Don’t run production ASP.NET Applications with debug=”true” enabled - http://weblogs.asp.net/scottgu/archive/2006/04/11/Don_1920_t-run-production-ASP.NET-Applications-with-debug_3D001D20_true_1D20_-enabled.aspx
Pour faciliter la prise en main de cet outil légendaire et pour que vous puissiez tester vous même, je suis parti avec le Starter Kit "BlogEngine” (http://www.asp.net/community/projects/) ; Vous pourrez ainsi prendre les dumps, les ouvrir et utiliser WinDbg en suivant les étapes ci-dessous.
J’ai donc copié les sources du Starter Kit dans le répertoire "C:\inetpub\wwwroot" :
Ensuite, dans la console IIS, j’ai créé une application pour ce répertoire en choisissant le pool d’application "Classic .NET AppPool" :

C’est bon, l’application tourne, nous pouvons passer aux choses sérieuses…
Nous retrouvons le processus w3wp.exe avec le gestionnaire de taches, "process explorer" ou la commande suivante :
tasklist /FI "IMAGENAME eq w3wp.exe"

La prise de dumps peut se faire facilement avec adplus. Voici toutes les informations pour ce faire - Billet précédent : Plan d‘action pour la capture des informations lors d’un problème de production IIS
Pour nos tests aujourd’hui, je fais un simple clic droit sur processus dans le gestionnaire de taches puis "Create Dump File"
Ouvrons le dump par un double-clic ou en lançant WinDbg puis un glisser/déplacer du fichier .DMP dans l’interface de WinDbg… Un mot : Magnifique !!
Informations disponibles
Nous avons entre les mains une photographie de la mémoire utilisée par le processus. Au premier coup d’oeil, nous avons les informations suivantes
- La version exacte de Windows : Windows 7, build 7100 en 64bits
- Le jour et l’heure de la prise de dump : jeudi 18 juin à 14h10
- Le système d’exploitation tourne depuis 2 jours et 23 heures (System Uptime)
- Le processus est démarré depuis 13min et 9 secondes
Symboles
L’une des premières actions à faire est de paramétrer le serveur de symboles. En deux mots : Les symboles sont les fichiers .PDB donnant la correspondance entre les adresses mémoires et les noms des fonctions d’une dll ou d’un exécutable. WinDbg peut les utiliser pour nous afficher les piles d’appels avec les noms des fonctions plutôt que des adresses mémoire brutes : Appréciable est un adjectif faible pour décrire cet avantage :-)
Dans la zone de texte, en bas de l’interface, il vous suffit d’entrer
| .sympath SRV*c:\symbols*http://msdl.microsoft.com/download/symbols |
pour spécifier le serveur public de symboles Microsoft : http://msdl.microsoft.com/symbols/. Dans l’exemple ci-dessus, je donne le crépertoire "c:\symbols" comme emplacement pour une copie locale des ces fichiers de symboles. WinDbg va donc pouvoir télécharger et stocker ces fichiers pour une utilisation déconnectée.
Permet de charger les symboles et de les rapatrier en local s’il ne sont pas présents.
Modules
Sans commencer maintenant un débogage poussé, nous pouvons facilement connaitre quelle sont toutes les dlls chargées par le processus et obtenir des informations très précises sur chacune d’elles comme la date de build, le numéro de version, le chemin de la dll, etc…
L’affichage de toutes les dlls se fait par
Nous obtenons les informations sur une dll avec
Ce n’est que le début. Nous continuons dans le prochain billet.
Sebastien.
>>> Suite : Debogage .NET avec WinDbg et SOS - Threads
Voici un petit raccourci bien pratique lorsque vous avez à ouvrir des dumps avec WinDbg :
| Windows Registry Editor Version 5.00 ; Ajout de la fonctionnalité d’ouverture en double clic d’un dump par WinDbg dans le menu contextuel de l'explorateur Windows ; Cette fonctionnalité est ajouté pour les fichiers .DMP [HKEY_CLASSES_ROOT\.dmp] @="SB-DebugDumpFile" [HKEY_CLASSES_ROOT\SB-DebugDumpFile] [HKEY_CLASSES_ROOT\SB-DebugDumpFile\DefaultIcon] @="c:\\debuggers\\windbg.exe" [HKEY_CLASSES_ROOT\SB-DebugDumpFile\Shell] [HKEY_CLASSES_ROOT\SB-DebugDumpFile\Shell\SB-DebugThisDump] @="-> Deboguer ce dump" [HKEY_CLASSES_ROOT\SB-DebugDumpFile\Shell\SB-DebugThisDump\Command] @="\"C:\\debuggers\\windbg\" -z \"%1\"" |
A noter, que j’utilise le chemin "c:\debuggers\windbg.exe" ; N’oubliez pas de le changer si vous avez les outils de débogage dans un autre répertoire.
Maintenant, un double clic sur un dump suffit pour l’ouvrir dans WinDbg !
A bientôt,
Sebastien.
Je fais suite à mon précédent billet sur l’introduction sur le débogage en production afin de vous donner plus de précisions sur la collecte d’informations lors de l’apparition d’un problème en production.
Dans tous les cas se présentant à vous, je vous conseille de récupérer des serveurs Web les logs IIS, les logs HTTPErr et vos logs applicatifs. Vous trouverez les explications dans le billet Données à récolter pour un travail de surveillance ou d'investigation sur un serveur Web. Ces logs nous permettent de vérifier la charge utilisateur, les temps de réponses, les erreurs HTTP : ce sont des informations précieuses à relier avec le comportement observé de l’application. Aussi, si vous avez la possibilité de prendre un log Perfmon, cela constitue un apport d’informations supplémentaires.
D’un point de vue système, je résumerais les problématiques de production en deux comportements distincts :
- Hang = Etat de blocage "deadlock" (avec ou sans 100% CPU) ou d’attente "hang" du processus
- Crash = Arrêt "crash" du processus
La prise de dumps peut se faire par le script Adplus. Voici comment faire : Procédures de prises de dumps pour un serveur Web
D’une manière générale, vous pouvez utiliser Adplus pour les prises de dumps/logs pour tous les processus et pas pour IIS seulement. Les paramètres les plus utiles sont :
| -p | –> | Spécifie le PID du processus |
| -pn | –> | Donne le nom du processus (plutôt que son PID) |
| -o | –> | Renseigne le répertoire de sortie pour la création du dumps |
Ainsi, pour une prise de dumps de plusieurs processus et la création des dumps dans un répertoire particulier, la commande ressemble à
| cscript.exe adplus.vbs -pn w3wp.exe -pn inetinfo.exe -crash -o c:\temp -quiet |
Deux autres paramètres sont utiles :
| -NoDumpOnFirst | –> | Permet de ne pas surcharger le débogueur (et donc le serveur) qui prend un mini dump pour chaque exception de première chance. Logue toutes les exceptions levées |
| -NoDumpOnSecond | –> | Aucune prise de dump mais seulement le remplissage d’un log permettant de visualiser toutes les exceptions levées |
Pour vous donner plus de précisions, en deux mots, il existe deux types d'exceptions : première chance et seconde chance.
- Une exception de première chance peut être interceptée par votre application par un try/catch ou un gestionnaire d’exceptions global
- Si cette exception n’est pas gérée, elle est à nouveau levée mais comme une exception de seconde chance. Dans ce cas, seul un débogueur peut l’intercepter et si aucun débogueur n’est attaché, l’application se ferme (autrement dit : le processus crashe).
Par exemple, pour une prise d’un dump lors du crash de l’application "WpfApplication1.exe", vous pouvez utiliser "cscript.exe adplus.vbs -crash -pn WpfApplication1.exe -o c:\temp -quiet".

Cette commande a pour effet de brancher le débogueur "cdb.exe" au processus WpfApplication1.exe. Le debogueur logue toutes les exceptions dans un fichier pendant l’exécution du processus et si une exception de seconde chance est levée (provoquant l’arrêt du processus), un dump est pris.

Le répertoire de sortie contient les fichiers suivants :
- Process_List.txt - Liste des processus s’éxécutant sur la machine au moment du lancement de la commande
- un ou plusieurs fichiers du type PID-2852__WPFAPPLICATION1.EXE__Date_05-13-2009__Time_15-46-0303.log - Log des exceptions levées par l’application
- un ou plusieurs fichiers du type PID-2852__WPFAPPLICATION1.EXE__2nd_chance_NET_CLR__full_0a70_2009-05-13_15-46-10-421_0b24.dmp - Dump du processus
A noter que le répertoire et les fichiers contiennent la date et l’heure exacte de la prise de dumps. Dans l’exemple précédent, il s’agit du 2009-05-13 pour le 13 mai et 15-46-10 pour 15h46m.
Nous avons donc maintenant toutes les données nécessaires (en particulier les dumps) pour déboguer à tête reposée. Ouvrons le dump avec Windbg :-)
Sebastien.
>>> Suite : Premiers pas avec WinDbg
Joli titre non ? En fait, je souhaite vous faire part de mon expérience sur le débogage .NET en production. "Débogage .NET en production" par opposition au débogage pas à pas dans Visual Studio.
En effet, il n’est pas toujours possible de "dégainer" son Visual Studio dans les environnements comme la recette ou la production. Et même, dans certains cas ce n’est pas adapté car la problématique à déboguer nécessite une certaine charge utilisateurs.
Quels sont les différents environnements ?
Chaque environnement a ses caractéristiques propres ; Pour simplifier, nous pouvons dire que nous avons les types suivants :
- Développement
- Pas de charge utilisateurs
- Problèmes unitaires reproductibles
- Serveurs et réseau différents de la production
- Débogage invasif ou pas à pas autorisé
- Test/Recette
- Pas forcement identique à la production
- Débogage invasif autorisé mais pas utilisable dans tous les cas (exemple : tests de charge)
- Outils de débogage possiblement non autorisés
- Production
- Environnement à forte charge / disponibilité
- Outils de débogage souvent non autorisés
Quelles problématiques pouvons-nous rencontrer ?
Malgré tous les efforts mis en œuvre dans la phase de développement et de tests, il est possible d’observer des comportements anormaux de l’application mise en production. Voici les plus fréquents :
- Apparition de messages d’erreurs aux utilisateurs alors qu’aucune exception n’est loguée (Exceptions non gérées)
- Un état de blocage ou d’attente qui empêche le traitement des requêtes (deadlock ou hang du processus d’exécution W3WP.EXE)
- Attente sur des ressources externes
- Locks
- Deadlocks
- 100% CPU du processus d’exécution W3WP.EXE
- Boucle infini
- Serveur surchargé
- Nombre élevé d’exceptions
- Utilisation du processeur par le Garbage Collector
- Un arrêt du processus d’exécution inexpliqué (crash de W3WP.EXE)
- Exceptions non gérées
- Exceptions internes à la CLR
- Exceptions de composants COM
- Recyclages
Comment procéder au débogage ?
Ces problèmes, n’ont pas forcement été identifiés au préalable lors du développement et de la phase de tests et le plus souvent ne sont pas reproductibles sur une autre plateforme que la production. Comment faire pour investiguer et trouver l’origine du problème ?
Le plan d’action le plus souvent utilisé est en 3 étapes :
- Capture d’informations
- Rétablissement de la production
- Analyse ultérieure
>>> Suite : Plan d‘action pour la capture des informations lors d’un problème de production IIS
Le GAC nous permet de partager des assemblies entre plusieurs applications ASP.NET. La gestion des versions des ces assemblies en est aussi facilitée à condition de bien comprendre la différence entre Assembly version et File version.
Allez hop... Petite explication !
Lors de la mise à jour d’une assembly nous avons le choix entre changer l’Assembly version et la File version

Pour illustrer les deux possibilités, prenons l’exemple de plusieurs sites Web utilisant une même assembly dans le GAC (Assembly V1.0.0.0 / File V1.0.0.0). Les Web.config des applications ASP.NET utilisent :
Possibilité 1 : Modification mineure = Correctif
Nous mettons à jour cette assembly dans la GAC en gardant la même Assembly version mais en changeant la File version (Assembly V1.0.0.0 / File V1.1.0.0)
- Dans la GAC, nous avons maintenant seulement la nouvelle assembly (Assembly V1.0.0.0 / File V1.1.0.0). Cette nouvelle assembly remplace la précédente car nous avons la même Assembly version. En effet, dans la GAC, deux assemblies du même nom ne peuvent pas avoir la même Assembly version
- Les applications en cours d’exécution utilisent toujours la version précédente (Assembly V1.0.0.0 / File V1.0.0.0) car pour elles rien ne leur indique un changement (le web.config n’a pas changé)
- Si l’appDomain redémarre : c'est-à-dire si l’application Web redémarre suite à une modification du Web.config par exemple, la nouvelle version n’est toujours pas chargée car l’ancienne est en mémoire dans le processus W3WP.EXE qui n’a pas redémarré
- Si le processus W3WP.EXE redémarre suite à un recyclage, un crash, alors la CLR va chercher dans le GAC la dll et prend la nouvelle version
Possibilité 2 : Modification majeure = Nouvelle version
Nous mettons à jour cette assembly dans la GAC en changeant le numéro de l’Assembly version (Assembly V2.0.0.0)
- Dans la GAC, nous voyons maintenant les deux assemblies

- Les applications en cours d’exécution utiliseront toujours l’Assembly version 1.0.0.0 tant que le web.config n’est pas modifié pour mentionner le changement de version :
- Comme modifions le Web.config, l’appDomain redémarre (sans redémarrage du processus W3WP.EXE) et prend en charge la nouvelle version
Conclusion en images :-)
| Changement File version |
| Redémarrage W3WP.EXE | Prise en compte de la nouvelle version |
Modification Web.config
| Aucun changement |
| Changement Assembly version |
| Redémarrage W3WP.EXE | Aucun changement |
| Modification Web.config | Prise en compte de la nouvelle version |
J’espère que ces éclaircissements vous aideront dans vos déploiements en production. A bientôt,
Sebastien.
L’outil TinyGet n’a pas vocation remplacer un outil de test de charge blogs.mais il s’avère extrêmement pratique dans certains cas : Je vous conseille de le garder dans vos outils favoris. Il vous permettra de simuler une charge basique sur une application Web en quelques lignes de commandes.
Les avantages sont les suivants :
- Support du multithread pour effectuer des requêtes simultanées
- Support des itérations
- Paramétrage possible pour le type authentification, la version HTTP, les headers, le contenu de la requête, l’utilisation d’un certificat client
- Attente d’un contenu particulier pour la réponse, le code de retour
Exemple de trois threads simultanés effectuant dix requêtes chacun
| tinyget.exe -srv:sbovo02 -uri:/default.aspx -status:200 -threads:3 -loop:10 |
-threads donne le nombre de threads lancés simultanément.
-loop indique le nombre d’itérations
-status représente le code de retour attendu. Si TinyGet n’obtient pas ce code, un message est affiché dans la console
Exemple d’une requête attendant un contenu particulier
| tinyget.exe -srv:sbovo02 -uri:/contenu.aspx -testContainString:"Texte" |
-testContainString permet de mentionner quelle chaine (contenu dans le body de la réponse) nous attendons
Exemple d’une requête avec l’affichage de la trace TinyGet ou les headers de la réponse
| tinyget.exe -srv:sbovo02 -uri:/stylesheet.css -trace |
| tinyget.exe -srv:sbovo02 -uri:/stylesheet.css -headers |
Pour télécharger l’outil et l’aide associée : IIS 6.0 Resource Kit Tools - http://www.iis.net/downloads/default.aspx?tabid=34&g=6&i=1352
Je pense que vous avez bien vu l’utilité de cet outil soit pour simuler une charge soit pour vérifier les réponses d’une application Web.
Bonne utilisation !
Sebastien.
Bonjour,
Dans le même esprit que mon précédent post, voici comment vous pouvez obtenir la liste des mises à jour ou hotfixes Windows en ligne de commande :
Et ainsi, nous pouvons facilement rechercher si une mise à jour particulière a bien été installé :
| systeminfo | findstr KB961367 |
A noter, que "SystemInfo" donne une multitude d’informations supplémentaires qui peuvent vous être utile :
- OS Name
- OS Version
- Processor(s)
- Total Physical Memory
- Available Physical Memory
- Domain
- Logon Server
- etc…
A bientôt,
Sebastien.