Debogage .NET avec WinDbg et SOS - Threads
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 :
.loadby sos mscorwks |
Vous pouvez vérifier la liste des extensions chargées avec
.chain |
Pour information, le déchargement se fait avec
.unload sos |
Démarrage
!EEVersion |
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
!help |
Vous trouverez l’aide complète en ligne sur MSDN : SOS Debugging Extension (SOS.dll) - https://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*https://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
kp |
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
!ClrStack |
Pour information, le changement de thread se fait avec
~XXs |
Où XX est le numéro du thread.
Comme raccourci, pour exécuter une commande sur tous les threads, nous pouvons utiliser
~*kp |
~*e !ClrStack |
"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) |
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