Há mais de 10 anos eu escrevo e falo sobre debugging. Praticamente todos as palestras, artigos e demonstrações com maior complexidade eu utilizo o WinDBG.

Comecei a escrever um guia em 2008 em um antigo blog, mas infelizmente parei.

Ontem em um evento com duas palestras sobre .NET avançado / internals, vi novamente o WinDBG sendo apresentado e o espanto interesse de diversas pessoas sobre o potencial da ferramenta.

Resolvi então escrever um guia rápido para iniciantes, com o intuito de gerar o interesse e detalhar o caminho para os próximos passos.

O que é WinDBG?

Da fonte oficial,

“The Windows Debugger (WinDbg) can be used to debug kernel-mode and user-mode code, to analyze crash dumps, and to examine the CPU registers while the code executes.”

https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-tools

Talvez as primeiras definições que gerem dúvidas para alguns leitores são estas de código kernel-mode e user-mode. Falamos com certa frequência estes termos, então é importante garantirmos o entendimento.

De forma bem simples, objetiva e direta, aplicações rodam em user-mode, e componentes do sistema operacional rodam em kernel mode.

Portanto, para nossos cenários de investigação de problemas em .NET, vamos realizar o user-mode debugging.

Quando você quiser, entre em mais detalhes através deste link.

Faça o download do WinDBG. Se quiser ir para a versão mais nova (preview neste momento) é interessante, ela fornece uma interface “menos assustadora” e mais intuitiva.

Realizando o debug a partir de um dump

Com o WinDBG você pode realizar o debugging de diversas formas. A primeira e – possivelmente a mais comum para o mundo de aplicações gerenciadas – é através de um dump de memória.

Existem alguns tipos de memory dumps, mas para nosso cenário precisamos extrair este dump de um processo, se tratando portanto de um user dump.

Este dump nada mais é que um snapshot de um processo ou sistema em um determinado momento. Imagine então que determinar o momento correto para extração desta “foto” é essencial para análise dos problemas.

É possível associar esta extração a alguma determinada situação, como por exemplo um uso excessivo de CPU por um determinado tempo. Desta maneira conseguimos garantir que teremos a foto do exato momento de um cenário passível de investigação.

A maneira mais fácil e simples de extrair um dump é através do gerenciador de tarefas. Encontrando o processo desejado, clique com o botão direito e vá até a opção “Criar arquivo de despejo” em português ou “Create dump file” em inglês:

O processo indicará o caminho do dump gerado.

O mais comum é gerar dumps através de ferramentas como procdump.exe (sysinternals) ou adplus.exe (faz parte do pacote de Debugging Tools for Windows).

Eu recomendo através de procdump por ser muito mais “portável”. Faça o download por aqui e siga os exemplos neste mesmo link.

Iniciando a investigação

Considerando que você já extraiu o dump do seu processo (w3wp, iisexpress, etc), você deverá abrir o WinDBG e abrir o dump por lá. Basta ir até “Arquivo” e optar por “Open crash dump” ou “Open dump”, dependendo da versão do seu WinDBG.

A primeira tela que você terá contato deve ser como esta (clique para ampliar):

Esta tela já traz algumas informações importantes, como o ambiente de onde o dump foi extraído, a ferramenta utilizada, momento, etc.

Lá embaixo temos a barra de comando, onde colocaremos tudo que gostaríamos de analisar.

É importante saber que por default o WinDBG não conhece .NET. Para isto, precisamos “carregar” uma extensão extremamente conhecida, a SOS.dll.

Esta extensão disponibiliza alguns comandos e “traduz” a CLR para o WinDBG.

O runtime do .NET Framework (full ou core) traz esta extensão por padrão. No meu caso, fiz o download do runtime da versão do processo para que pudesse trabalhar.

Com o SOS.dll em mãos basta carregar na sessão de debug (meu SOS.dll esta na pasta que extraí do donwload do SDK):

.load C:\Users\81298\Downloads\dotnet-sdk-2.2.301-win-x86\shared\Microsoft.NETCore.App\2.2.6\sos.dll

A partir deste momento posso utilizar alguns comandos conhecidos que estão disponíveis pela extensão SOS.

Quais comandos executar?

Isso depende muito do que você está precisando investigar. Mas para ter ideia do potencial, você pode, por exemplo, visualizar todas as threads, o heap, os objetos na memória, etc.

É importante saber que através de uma ferramenta como está é possível enxergar tudo, absolutamente tudo.

Por exemplo:

Lista todas as threads:

0:018> !threads
ThreadCount:      24
UnstartedThread:  0
BackgroundThread: 22
PendingThread:    0
DeadThread:       1
Hosted Runtime:   no
                                                                         Lock  
       ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception
   0    1 1d94 03855440   202a020 Preemptive  00000000:00000000 037fd060 0     MTA 
   3    2 2014 0a21cd28     2b220 Preemptive  00000000:00000000 037fd060 0     MTA (Finalizer) 
XXXX    3    0 038ea538     39820 Preemptive  00000000:00000000 037fd060 0     Ukn 
   4    5 2b2c 0a234a10   102a220 Preemptive  00000000:00000000 037fd060 0     MTA (Threadpool Worker) 
   5    7 2854 0a27e3a0   202b220 Preemptive  00000000:00000000 037fd060 0     MTA 
   6    8 2210 0a2a3530   202b220 Preemptive  00000000:00000000 037fd060 1     MTA 
   7    9 1960 036fee70   202b220 Preemptive  00000000:00000000 037fd060 0     MTA 
   8   12 2c10 0a2f2f08     2b220 Preemptive  00000000:00000000 037fd060 0     MTA 
   9   13 25b4 0a2fb908     2b220 Preemptive  00000000:00000000 037fd060 0     MTA 
  11   10 2478 0d72ea58     21220 Preemptive  00000000:00000000 037fd060 0     Ukn 
  12   32 35d8 0d7dd818   8029220 Preemptive  00000000:00000000 037fd060 0     MTA (Threadpool Completion Port) 
  13   27 2598 0d902f58     20220 Preemptive  12B4B7E8:00000000 037fd060 0     Ukn 
  14   55 38dc 0d8fdad8     20220 Preemptive  00000000:00000000 037fd060 0     Ukn 
  15   56 3e24 0d904478   1029220 Preemptive  12B4B274:00000000 037fd060 0     MTA (Threadpool Worker) 
  16   53 3938 0d901f80   1029220 Preemptive  12B4AAC8:00000000 037fd060 0     MTA (Threadpool Worker) 
  18   42 3944 0d7de7f0   1029220 Preemptive  12B4B3E0:00000000 037fd060 0     MTA (Threadpool Worker) 
  20    6 3dd8 0d7ddd60   1029220 Preemptive  12B4B13C:00000000 037fd060 0     MTA (Threadpool Worker) 
  21   43 3084 0d7dc840   1029220 Preemptive  12B4AD44:00000000 037fd060 0     MTA (Threadpool Worker) 
  22   46 3ac0 0d7df7c8   1029220 Preemptive  12B4B65C:00000000 037fd060 0     MTA (Threadpool Worker) 
  23   31 3c70 0d7dcd88   1029220 Preemptive  12B4AE70:00000000 037fd060 0     MTA (Threadpool Worker) 
  24   45 3590 0d7db868   1029220 Preemptive  12B4AC18:00000000 037fd060 0     MTA (Threadpool Worker) 
  25   41 36a4 0d7dfd10   1029220 Preemptive  12B4C6A4:00000000 037fd060 0     MTA (Threadpool Worker) 
  26   50 3a34 0d7d8e28   1029220 Preemptive  12B4AFA8:00000000 037fd060 0     MTA (Threadpool Worker) 
  27   49 2c84 0d7de2a8   1029220 Preemptive  12B49900:00000000 037fd060 0     MTA (Threadpool Worker) 

Mostra o stack da thread selecionada:

0:018> !clrstack
OS Thread Id: 0x3944 (18)
Child SP       IP Call Site
0edce25c 77afed0c [HelperMethodFrame: 0edce25c] 
0edce2c8 6541bcca System.String.Concat(System.Object[])
0edce2fc 0cc62eb0 BuggyBits.Controllers.AllProductsController.Index()
0edce34c 0ae9322b LoadSymbols moduleData.Request FAILED 0x80004005
DynamicClass.lambda_method(System.Runtime.CompilerServices.Closure, System.Object, System.Object[])
0edce358 0e05d38e ...

Informações sobre GC e objetos no HEAP:

0:018> !dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
6574a19c        1           12 System.Collections.Generic.GenericEqualityComparer`1[[System.Int32, System.Private.CoreLib]]
6574820c        1           12 System.Collections.Generic.GenericEqualityComparer`1[[System.Int64, System.Private.CoreLib]]
65742f6c        1           12 System.Buffers.TlsOverPerCoreLockedStacksArrayPool`1+PerCoreLockedStacks[[System.Byte, System.Private.CoreLib]]
657428d0        1           12 System.Collections.Generic.GenericEqualityComparer`1[[System.UInt64, System.Private.CoreLib]]
...

Existem diversos outros comandos, veja aqui a lista.

Próximos passos

Se você realiza trabalhos de troubleshooting ou gostaria de entender melhor o funcionamento de baixo nível do seu processo, entre mais a fundo em WinDBG e user mode debugging.

Recomendo muito o Debugging Labs da Tess. Ela não escreve e acho que nem trabalha mais com isto, mas para quem está começando pode dar uma ideia sensacional de como evoluir.

Existem livros interessantes também. Um deles (antigo, mas essencial) é o Advanced .NET Debugging. Não tem problema ser antigo; o mais relevante são as instruções, o método e os comandos relacionados não ao framework .NET, mas sim ao WinDBG.

Fique a vontade para entrar em contato para falar sobre.

Abraços, até a próxima!