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.

EEVersion

 

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

!help

!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…

AttahToProcess

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é

ThreadID

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

kp

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

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 :-) : Vérification

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 :-) : 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