Fico me perguntando como funciona um depurador? Especialmente aquele que pode ser 'anexado' ao executável já em execução. Entendo que o compilador converte código em linguagem de máquina, mas como o depurador 'sabe' ao que está sendo anexado?
Fico me perguntando como funciona um depurador? Especialmente aquele que pode ser 'anexado' ao executável já em execução. Entendo que o compilador converte código em linguagem de máquina, mas como o depurador 'sabe' ao que está sendo anexado?
Respostas:
Os detalhes de como um depurador funciona dependerão do que você está depurando e do sistema operacional. Para depuração nativa no Windows, você pode encontrar alguns detalhes na API de depuração do MSDN: Win32 .
O usuário informa ao depurador a qual processo conectar, por nome ou por ID do processo. Se for um nome, o depurador procurará o ID do processo e iniciará a sessão de depuração por meio de uma chamada do sistema; no Windows, isso seria DebugActiveProcess .
Uma vez anexado, o depurador entrará em um loop de eventos semelhante a qualquer interface do usuário, mas, em vez de eventos provenientes do sistema de janelas, o sistema operacional gerará eventos com base no que acontece no processo que está sendo depurado - por exemplo, uma exceção. Consulte WaitForDebugEvent .
O depurador é capaz de ler e gravar a memória virtual do processo de destino e até ajustar seus valores de registro por meio de APIs fornecidas pelo sistema operacional. Veja a lista de funções de depuração para Windows.
O depurador é capaz de usar informações de arquivos de símbolos para converter endereços em nomes de variáveis e locais no código-fonte. As informações do arquivo de símbolo são um conjunto separado de APIs e não são parte essencial do sistema operacional. No Windows, isso é feito através do Debug Interface Access SDK .
Se você estiver depurando um ambiente gerenciado (.NET, Java etc.), o processo normalmente parecerá semelhante, mas os detalhes serão diferentes, pois o ambiente da máquina virtual fornece a API de depuração em vez do SO subjacente.
Como eu entendo:
Para pontos de interrupção do software em x86, o depurador substitui o primeiro byte da instrução por CC
( int3
). Isso é feito WriteProcessMemory
no Windows. Quando a CPU chega a essa instrução e executa int3
, isso faz com que a CPU gere uma exceção de depuração. O sistema operacional recebe essa interrupção, percebe que o processo está sendo depurado e notifica o processo do depurador de que o ponto de interrupção foi atingido.
Depois que o ponto de interrupção é atingido e o processo é interrompido, o depurador procura em sua lista de pontos de interrupção e substitui CC
o byte que estava lá originalmente. O depurador define TF
, o Trap Flag em EFLAGS
(modificando o CONTEXT
), e continua o processo. O Trap Flag faz com que a CPU gere automaticamente uma exceção de etapa única ( INT 1
) na próxima instrução.
Quando o processo sendo depurado para na próxima vez, o depurador substitui novamente o primeiro byte da instrução de ponto de interrupção CC
e o processo continua.
Não tenho certeza se é exatamente assim que é implementado por todos os depuradores, mas escrevi um programa Win32 que consegue se depurar usando esse mecanismo. Completamente inútil, mas educacional.
No Linux, a depuração de um processo começa com a chamada do sistema ptrace (2) . Este artigo possui um ótimo tutorial sobre como usar ptrace
para implementar algumas construções simples de depuração.
(2)
diz algo mais (ou menos) do que "ptrace é uma chamada do sistema"?
(2)
é o número da seção manual. Consulte en.wikipedia.org/wiki/Man_page#Manual_sections para obter uma descrição das seções do manual.
ptrace
é uma chamada do sistema.
(2)
diz que podemos digitar man 2 ptrace
e obter a página de manual correta - não importante aqui, porque não há outro ptrace
para desambiguar, mas para comparar man printf
com o man 3 printf
Linux.
Se você estiver em um sistema operacional Windows, um ótimo recurso para isso seria "Depuração de aplicativos para Microsoft .NET e Microsoft Windows", de John Robbins:
(ou até a edição mais antiga: "Depurando aplicativos" )
O livro possui um capítulo sobre como funciona um depurador que inclui código para alguns depuradores simples (mas funcionais).
Como não estou familiarizado com os detalhes da depuração do Unix / Linux, essas coisas podem não se aplicar a todos os outros sistemas operacionais. Mas eu acho que, como introdução a um assunto muito complexo, os conceitos - se não os detalhes e as APIs - devem "portar" para quase todos os sistemas operacionais.
Outra fonte valiosa para entender a depuração é o manual da CPU da Intel (Intel® 64 e IA-32 Architectures Software Developer Manual). No volume 3A, capítulo 16, ele apresentou o suporte de depuração de hardware, como exceções especiais e registros de depuração de hardware. A seguir, é desse capítulo:
Sinalizador T (trap), TSS - gera uma exceção de depuração (#DB) quando é feita uma tentativa de alternar para uma tarefa com o sinalizador T definido em seu TSS.
Não tenho certeza se o Windows ou o Linux usam esse sinalizador ou não, mas é muito interessante ler esse capítulo.
Espero que isso ajude alguém.
Eu acho que existem duas perguntas principais para responder aqui:
1. Como o depurador sabe que ocorreu uma exceção?
Quando ocorre uma exceção em um processo que está sendo depurado, o depurador é notificado pelo sistema operacional antes que qualquer manipulador de exceção de usuário definido no processo de destino tenha a chance de responder à exceção. Se o depurador optar por não manipular essa notificação de exceção (primeira chance), a sequência de despacho da exceção continuará e o segmento de destino terá a chance de manipular a exceção, se desejar. Se a exceção SEH não for tratada pelo processo de destino, o depurador receberá outro evento de depuração, chamado notificação de segunda chance, para informar que ocorreu uma exceção não tratada no processo de destino. Fonte
2. Como o depurador sabe como parar em um ponto de interrupção?
A resposta simplificada é: Quando você coloca um ponto de interrupção no programa, o depurador substitui seu código nesse ponto por uma instrução int3 que é uma interrupção do software . Como efeito, o programa é suspenso e o depurador é chamado.
Meu entendimento é que, quando você compila um aplicativo ou arquivo DLL, tudo o que ele compila contém símbolos representando as funções e as variáveis.
Quando você tem uma compilação de depuração, esses símbolos são muito mais detalhados do que quando é uma compilação de lançamento, permitindo que o depurador forneça mais informações. Quando você anexa o depurador a um processo, ele analisa quais funções estão sendo acessadas no momento e resolve todos os símbolos de depuração disponíveis a partir daqui (como ele sabe como são as partes internas do arquivo compilado, ele pode obter o que pode estar na memória , com conteúdo de ints, floats, strings, etc.). Como o primeiro pôster disse, essas informações e como esses símbolos funcionam dependem muito do ambiente e do idioma.