Rodrigo Strauss :: Blog
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





Essa do breakpoint por thread é extremamente útil! =D