logo
Contato | Sobre...        
rebarba rebarba

Rodrigo Strauss :: Blog

follow us in feedly

Usando o WinDbg para entender um software multithread

É necessário que você conheça pelos menos as funções básicas do WinDbg para entender esse post. Escrevi dois artigos sobre WinDbg que podem ajudar (parte 1 e parte 2)

Como já disse no post anterior, comecei o ano com o pé direito, voltando a trabalhar com Visual C++. E essa mudança de emprego e as festas de fim de ano são as minhas desculpas para o grande intervalo nos posts. E feliz 2005 para todos!

Mas esse tempo não foi de todo perdido. Encontrei alguns problemas com o RSS do blog e resolvi. Agora o RSS está verificado e deve funcionar com qualquer NewsReader.

Vamos ao que interessa. A empresa onde eu trabalho produz software de alto desempenho para o mercado financeiro. Os softwares se comunicam de diversas maneiras, sockets, File Mapping, MSMQ, etc. Muitas vezes existem comunicações entre as threads do mesmo processo, e essas comunicações são feitas de diversas maneiras, filas, mensagens inter thread, etc. Lendo o código é possível entender o que cada ThreadFunc faz, mas o mais fácil mesmo é colocar o software para rodar e analizar as comunicações entre as threads. Vamos ver como o WinDbg ajuda nisso.

Com o executável já rodando e sob o controle do WinDbg, vamos dar um break e analizar como as coisas acontecem. Primeiro vamos listar as threads do processo usando o comando "~":

0:027> ~
   0  Id: 890.bf8 Suspend: 1 Teb: 7ffde000 Unfrozen
   1  Id: 890.cd8 Suspend: 1 Teb: 7ffdd000 Unfrozen
   2  Id: 890.da0 Suspend: 1 Teb: 7ffdc000 Unfrozen
...
  41  Id: 890.95c Suspend: 1 Teb: 7ff91000 Unfrozen
  42  Id: 890.2a4 Suspend: 1 Teb: 7ff90000 Unfrozen
  43  Id: 890.de4 Suspend: 1 Teb: 7ff8d000 Unfrozen

Bom, temos 44 threads. Um processo pode ter diversos thread pools, cada um para uma determinada tarefa. Podemos ter um thread pool para recepção de comunicação TCP/IP, outro pool para acesso ao banco de dados e outro ainda para acessar o MSMQ. Primeiro temos que saber o que cada thread está esperando.

(Está se perguntando o que é um thread pool? É quando você colocar diversas threads para atender solicitações em paralelo. Cada vez que é recebida uma solicitação pelo processo, a thread que gerencia o pool sinaliza uma das threads para que ela trate essa solicitação. Isso faz com que uma solicitação não trave outra, já que elas estarão rodando em threads diferentes. Uma thread de um pool quase sempre fica esperando uma sinalização (evento, semáforo, etc) usando WaitForMultipleObjects)

O comando "~", além de listar as threads do processo, também é usado como um modificador de comandos, fazendo com que o comando em uso seja direcionado para uma ou mais threads especificadas. Vamos testar isso com o comando "k". Esse comando faz um dump da pilha da thread atual. Vamos usá-lo:

0:027> k
ChildEBP RetAddr  
02f3ffc8 77f77fe8 ntdll!DbgBreakPoint
02f3fff4 00000000 ntdll!DbgUiRemoteBreakin+0x36

Como nós forçamos um breakpoint com CTRL+BREAK, nós estamos na thread de debug. Primeiro vamos utilizar o comando "~" para ver algumas informações da thread, e depois veremos como o comando "~" pode modificar o comando "k":

0:027> ~35
  35  Id: 890.84 Suspend: 1 Teb: 7ff98000 Unfrozen
      Start: kernel32!BaseThreadStartThunk (77e4a99b) 
      Priority: 0  Priority class: 32

0:027> ~35 k
ChildEBP RetAddr  
0374fda8 77f4372d SharedUserData!SystemCallStub+0x4
0374fdac 77e41bfa ntdll!NtWaitForMultipleObjects+0xc
0374fe54 77e4b0e4 kernel32!WaitForMultipleObjectsEx+0x11a
0374fe6c 0041d3bc kernel32!WaitForMultipleObjects+0x17
0374fee8 004300eb XXX!CInterThreadMessageQueue::WaitForMessageEx+0x8c 
0374ffb8 77e4a990 XXX!CResponseThread::ResponseThreadProc+0x9b
0374ffec 00000000 kernel32!BaseThreadStart+0x34

Agora temos um dump da pilha da thread 35. Mas como queremos estudar o que faz cada thread, com esse comando teríamos que ver thread por thread. Agora usaremos o suporte a curingas do comando "~".

0:027> ~* k
...
   1  Id: 890.cd8 Suspend: 1 Teb: 7ffdd000 Unfrozen
ChildEBP RetAddr  
00bbfea0 77f4372d SharedUserData!SystemCallStub+0x4
00bbfea4 77f6c86c ntdll!NtWaitForMultipleObjects+0xc
00bbff48 77f6d7f5 ntdll!EtwpWaitForMultipleObjectsEx+0xf7
00bbffb8 77e4a990 ntdll!EtwpEventPump+0x27d
00bbffec 00000000 kernel32!BaseThreadStart+0x34

   2  Id: 890.da0 Suspend: 1 Teb: 7ffdc000 Unfrozen
ChildEBP RetAddr  
014dfe20 77f4313f SharedUserData!SystemCallStub+0x4
014dfe24 77c57b85 ntdll!NtReplyWaitReceivePortEx+0xc
014dff8c 77c60829 RPCRT4!LRPC_ADDRESS::ReceiveLotsaCalls+0x193
014dff90 77c60771 RPCRT4!RecvLotsaCallsWrapper+0x9
014dffb0 77c60857 RPCRT4!BaseCachedThreadRoutine+0x9c
014dffb8 77e4a990 RPCRT4!ThreadStartRoutine+0x17
014dffec 00000000 kernel32!BaseThreadStart+0x34

   3  Id: 890.c88 Suspend: 1 Teb: 7ffdb000 Unfrozen
ChildEBP RetAddr  
015dff10 77f4262b SharedUserData!SystemCallStub+0x4
015dff14 77e418ea ntdll!NtDelayExecution+0xc
015dff7c 77e416ee kernel32!SleepEx+0x68
015dff88 77162501 kernel32!Sleep+0xb
015dff94 771625ea ole32!CROIDTable::WorkerThreadLoop+0x12
015dff9c 77160000 ole32!CRpcThread::WorkerLoop+0x1e
015dffac 77162653 ole32!_imp__InstallApplication  (ole32+0x0)
015dffb8 77e4a990 ole32!CRpcThreadCache::RpcWorkerThreadEntry+0x1f
015dffec 00000000 kernel32!BaseThreadStart+0x34

...

   5  Id: 890.f38 Suspend: 1 Teb: 7ffd9000 Unfrozen
ChildEBP RetAddr  
01a2fc38 77f4372d SharedUserData!SystemCallStub+0x4
01a2fc3c 77e41bfa ntdll!NtWaitForMultipleObjects+0xc
01a2fce4 77e4b0e4 kernel32!WaitForMultipleObjectsEx+0x11a
01a2fcfc 0041d3bc kernel32!WaitForMultipleObjects+0x17
01a2fd78 004171bd XXX!CInterThreadMessageQueue<>::WaitForMessageEx+0x8c 
01a2ffb8 77e4a990 XXX!CExecutionThread::ExecutionThreadProc+0x1bd 
01a2ffec 00000000 kernel32!BaseThreadStart+0x34

   6  Id: 890.f08 Suspend: 1 Teb: 7ffd8000 Unfrozen
ChildEBP RetAddr  
01b2fc38 77f4372d SharedUserData!SystemCallStub+0x4
01b2fc3c 77e41bfa ntdll!NtWaitForMultipleObjects+0xc
01b2fce4 77e4b0e4 kernel32!WaitForMultipleObjectsEx+0x11a
01b2fcfc 0041d3bc kernel32!WaitForMultipleObjects+0x17
01b2fd78 004171bd XXX!CInterThreadMessageQueue<>::WaitForMessageEx+0x8c 
01b2ffb8 77e4a990 XXX!CExecutionThread::ExecutionThreadProc+0x1bd 
01b2ffec 00000000 kernel32!BaseThreadStart+0x34

   7  Id: 890.71c Suspend: 1 Teb: 7ffd7000 Unfrozen
ChildEBP RetAddr  
01c2fc38 77f4372d SharedUserData!SystemCallStub+0x4
01c2fc3c 77e41bfa ntdll!NtWaitForMultipleObjects+0xc
01c2fce4 77e4b0e4 kernel32!WaitForMultipleObjectsEx+0x11a
01c2fcfc 0041d3bc kernel32!WaitForMultipleObjects+0x17
01c2fd78 004171bd XXX!CInterThreadMessageQueue<>::WaitForMessageEx+0x8c 
01c2ffb8 77e4a990 XXX!CExecutionThread::ExecutionThreadProc+0x1bd 
01c2ffec 00000000 kernel32!BaseThreadStart+0x34

   8  Id: 890.38c Suspend: 1 Teb: 7ffd6000 Unfrozen
ChildEBP RetAddr  
01d2fc38 77f4372d SharedUserData!SystemCallStub+0x4
01d2fc3c 77e41bfa ntdll!NtWaitForMultipleObjects+0xc
01d2fce4 77e4b0e4 kernel32!WaitForMultipleObjectsEx+0x11a
01d2fcfc 0041d3bc kernel32!WaitForMultipleObjects+0x17
01d2fd78 004171bd XXX!CInterThreadMessageQueue<>::WaitForMessageEx+0x8c 
01d2ffb8 77e4a990 XXX!CExecutionThread::ExecutionThreadProc+0x1bd 
01d2ffec 00000000 kernel32!BaseThreadStart+0x34


...

  29  Id: 890.c4c Suspend: 1 Teb: 7ff9d000 Unfrozen
ChildEBP RetAddr  
0324fe64 77f4372d SharedUserData!SystemCallStub+0x4
0324fe68 77e41bfa ntdll!NtWaitForMultipleObjects+0xc
0324ff10 77e4b0e4 kernel32!WaitForMultipleObjectsEx+0x11a
0324ff28 00455d59 kernel32!WaitForMultipleObjects+0x17
0324ffb8 77e4a990 XXX!ReceiverThreadProc+0x49 
0324ffec 00000000 kernel32!BaseThreadStart+0x34

  30  Id: 890.8ac Suspend: 1 Teb: 7ff9c000 Unfrozen
ChildEBP RetAddr  
0334fe80 77f4372d SharedUserData!SystemCallStub+0x4
0334fe84 77e41bfa ntdll!NtWaitForMultipleObjects+0xc
0334ff2c 77e4b0e4 kernel32!WaitForMultipleObjectsEx+0x11a
0334ff44 00455b68 kernel32!WaitForMultipleObjects+0x17
0334ffb8 77e4a990 XXX!ListenerThreadProc+0x48 
0334ffec 00000000 kernel32!BaseThreadStart+0x34

  31  Id: 890.f44 Suspend: 1 Teb: 7ff9b000 Unfrozen
ChildEBP RetAddr  
0344fe78 77f4372d SharedUserData!SystemCallStub+0x4
0344fe7c 77e41bfa ntdll!NtWaitForMultipleObjects+0xc
0344ff24 77e4b0e4 kernel32!WaitForMultipleObjectsEx+0x11a
0344ff3c 00455a2c kernel32!WaitForMultipleObjects+0x17
0344ffb8 77e4a990 XXX!PublisherThreadProc+0x4c 
0344ffec 00000000 kernel32!BaseThreadStart+0x34

Podemos ver que as threads em vermelho são parte de um pool (note que a pilha das threads é a mesma, chamando as mesmas funções). Agora vamos fazer debug dessas threads do pool, colocando um breakpoint bem no retorno do WaitForXXX. Repare que quando fazemos o dump da pilha, temos o endereço de retorno da função:

   8  Id: 890.38c Suspend: 1 Teb: 7ffd6000 Unfrozen
ChildEBP RetAddr  
01d2fc38 77f4372d SharedUserData!SystemCallStub+0x4
01d2fc3c 77e41bfa ntdll!NtWaitForMultipleObjects+0xc
01d2fce4 77e4b0e4 kernel32!WaitForMultipleObjectsEx+0x11a
01d2fcfc 0041d3bc kernel32!WaitForMultipleObjects+0x17
01d2fd78 004171bd XXX!CInterThreadMessageQueue<>::WaitForMessageEx+0x8c 
01d2ffb8 77e4a990 XXX!CExecutionThread::ExecutionThreadProc+0x1bd 
01d2ffec 00000000 kernel32!BaseThreadStart+0x34

Esse é o endereço que será executado logo após o retorno da função. Então, se colocarmos um breakpoint nesse endereço, o programa parará logo que a thread retornar do Wait. Vamos colocar o breakpoint e usar o comando "ln" (que mostra o symbol mais próximo de uma posição de memória) para ver como o RetAddr está realmente na função que queremos:

0:027> ln 0041d3bc
(0041d330)   XXX!CInterThreadMessageQueue::WaitForMessageEx+0x8c

0:027> bp 0041d3bc

Pronto. Agora quando chegar uma solicitação para uma das threads, o programa parará no breakpoint e poderemos ver o que a thread faz.

Uma última dica: você pode também colocar o breakpoint somente em uma thread, caso não tenha interesse em fazer debug de todas as threads do pool (até porque se o programa estiver recebendo diversas solicitações em várias threads vai ficar difícil fazer o debug). Para isso use o modificador "~" e especifique o ID da thread antes de colocar o breakpoint:

0:027> ~8 bp 0041d3bc
0:027> bl
...  0:~008 XXX!CInterThreadMessageQueue<>::WaitForMessageEx+0x8c 

Em 03/01/2005 18:36, por Rodrigo Strauss


  
 
 
Comentários
Wanderley Caloni Jr | website | e-mail | em 04/01/2005 | #
Essa do breakpoint por thread é extremamente útil! =D
Algo a dizer?
Nome:


Site:


E-mail:


Escreva o número vinte e seis:


 Não mostre meu e-mail no site, não serve pra nada mesmo...

Comentário





Os comentários devem ser sobre assuntos relativos ao post, eu provavelmente apagarei comentários totalmente offtopic. Se quiser me enviar uma mensagem, use o formulário de contato. E não esqueça: isso é um site pessoal e eu me reservo o direito de apagar qualquer comentário ofensivo ou inapropriado.
rebarba rebarba
  ::::