O que é um depurador e como ele pode me ajudar a diagnosticar problemas?

Aug 19 2014

Esta é uma questão de propósito geral para ajudar novos programadores que têm problemas com um programa, mas não sabem como usar um depurador para diagnosticar a causa do problema.

Esta questão cobre três classes de questões mais específicas:

  • Quando executo meu programa, ele não produz a saída que espero para a entrada que forneci.
  • Quando executo meu programa, ele trava e me fornece um rastreamento de pilha. Eu examinei o rastreamento da pilha , mas ainda não sei a causa do problema porque o rastreamento da pilha não me fornece informações suficientes.
  • Quando executo meu programa, ele trava por causa de uma falha de segmentação (SEGV).

Respostas

68 Raedwald Aug 19 2014 at 20:49

Um depurador é um programa que pode examinar o estado do programa durante a execução do programa. Os meios técnicos que ele usa para fazer isso não são importantes para a compreensão dos princípios básicos de como usar um depurador. Você pode usar um depurador para interromper a execução de seu programa quando ele atinge um local específico em seu código e, em seguida, examinar os valores das variáveis ​​no programa. Você pode usar um depurador para executar seu programa muito lentamente, uma linha de código por vez (chamada de passo único ), enquanto examina os valores de suas variáveis.

Usar um depurador é uma habilidade básica esperada

Um depurador é uma ferramenta muito poderosa para ajudar a diagnosticar problemas com programas. E os depuradores estão disponíveis para todas as linguagens de programação práticas. Portanto, ser capaz de usar um depurador é considerado uma habilidade básica de qualquer programador profissional ou entusiasta. E usando um depurador si mesmo é considerado trabalho básico que você deve fazer a si mesmo antes de pedir ajuda a outros. Como este site é para programadores profissionais e entusiastas, e não um site de helpdesk ou mentoria, se você tiver uma dúvida sobre um problema com um programa específico, mas não tiver usado um depurador, é muito provável que sua pergunta seja encerrada e votada negativamente. Se você persistir com perguntas como essa, eventualmente será impedido de postar mais.

Como um depurador pode ajudá-lo

Usando um depurador, você pode descobrir se uma variável tem o valor errado e onde em seu programa seu valor mudou para o valor errado.

Usando uma única etapa, você também pode descobrir se o fluxo de controle está conforme o esperado. Por exemplo, se um ifbranch foi executado quando você esperava que fosse.

Notas gerais sobre o uso de um depurador

As especificações do uso de um depurador dependem do depurador e, em menor grau, da linguagem de programação que você está usando.

  • Você pode anexar um depurador a um processo que já esteja executando seu programa. Você pode fazer isso se o seu programa travar.

  • Na prática, geralmente é mais fácil executar seu programa sob o controle de um depurador desde o início.

  • Você indica onde seu programa deve parar de executar indicando o arquivo de código-fonte e o número da linha em que a execução deve parar, ou indicando o nome do método / função em que o programa deve parar (se você quiser parar como assim que a execução entrar no método). O meio técnico que o depurador usa para fazer com que seu programa pare é chamado de ponto de interrupção e esse processo é chamado de configuração de ponto de interrupção .

  • A maioria dos depuradores modernos são parte de um IDE e fornecem uma GUI conveniente para examinar o código-fonte e as variáveis ​​de seu programa, com uma interface de apontar e clicar para definir pontos de interrupção, executar seu programa e passo único.

  • Usar um depurador pode ser muito difícil, a menos que os arquivos executáveis ​​ou bytecode do programa incluam informações de símbolos de depuração e referências cruzadas ao código-fonte. Você pode ter que compilar (ou recompilar) seu programa de forma ligeiramente diferente para garantir que as informações estejam presentes. Se o compilador realizar otimizações extensas, essas referências cruzadas podem se tornar confusas. Portanto, você pode ter que recompilar seu programa com as otimizações desativadas .

39 SlugFiller Apr 05 2015 at 19:15

Quero acrescentar que um depurador nem sempre é a solução perfeita e nem sempre deve ser a solução ideal para depuração. Aqui estão alguns casos em que um depurador pode não funcionar para você:

  • A parte do seu programa que falha é realmente grande (modularização deficiente, talvez?) E você não tem certeza de onde começar a examinar o código. Percorrer tudo isso pode consumir muito tempo.
  • Seu programa usa muitos retornos de chamada e outros métodos de controle de fluxo não linear, o que confunde o depurador quando você o percorre.
  • Seu programa é multi-thread. Ou ainda pior, seu problema é causado por uma condição de corrida.
  • O código que contém o bug é executado muitas vezes antes de ser detectado. Isso pode ser particularmente problemático em loops principais, ou pior ainda, em motores de física, onde o problema pode ser numérico. Até mesmo definir um ponto de interrupção, neste caso, simplesmente faria com que você batesse nele várias vezes, sem que o bug aparecesse.
  • Seu programa deve ser executado em tempo real. Este é um grande problema para programas que se conectam à rede. Se você configurar um ponto de interrupção em seu código de rede, a outra extremidade não vai esperar que você passe, simplesmente vai expirar. Programas que dependem do relógio do sistema, por exemplo, jogos com frameskip, também não são muito melhores.
  • Seu programa executa alguma forma de ações destrutivas, como escrever em arquivos ou enviar e-mails, e você gostaria de limitar o número de vezes que precisa executá-lo.
  • Você pode dizer que seu bug é causado por valores incorretos que chegam à função X, mas não sabe de onde vêm esses valores. Ter que executar o programa várias vezes, definir pontos de interrupção cada vez mais para trás, pode ser um grande aborrecimento. Especialmente se a função X for chamada de muitos lugares ao longo do programa.

Em todos esses casos, fazer com que seu programa pare abruptamente pode fazer com que os resultados finais sejam diferentes ou percorrer manualmente em busca da linha onde o bug foi causado é muito incômodo. Isso também pode acontecer se o bug for um comportamento incorreto ou uma falha. Por exemplo, se a corrupção da memória causar um travamento, no momento em que o travamento ocorrer, ela estará muito longe de onde ocorreu a corrupção pela primeira vez e nenhuma informação útil será deixada.

Então quais são as alternativas?

O mais simples é simplesmente registrar e asserções. Adicione registros ao seu programa em vários pontos e compare o que você obtém com o que está esperando. Por exemplo, veja se a função onde você acha que há um bug é chamada em primeiro lugar. Veja se as variáveis ​​no início de um método são o que você pensa que são. Ao contrário dos pontos de interrupção, não há problema em haver muitas linhas de registro nas quais nada de especial acontece. Você pode simplesmente pesquisar no log posteriormente. Depois de atingir uma linha de registro diferente do que você esperava, adicione mais na mesma área. Limite-o mais e mais, até que seja pequeno o suficiente para ser capaz de registrar todas as linhas na área com escuta.

As asserções podem ser usadas para capturar valores incorretos à medida que ocorrem, em vez de quando eles têm um efeito visível para o usuário final. Quanto mais rápido você pegar um valor incorreto, mais próximo estará da linha que o produziu.

Refatorar e testar a unidade. Se o seu programa for muito grande, pode valer a pena testá-lo em uma classe ou função por vez. Forneça entradas e observe as saídas e veja quais não são como você esperava. Ser capaz de reduzir um bug de um programa inteiro a uma única função pode fazer uma grande diferença no tempo de depuração.

Em caso de vazamentos de memória ou perda de memória, use as ferramentas apropriadas que são capazes de analisar e detectar isso em tempo de execução. Ser capaz de detectar onde ocorre a corrupção real é a primeira etapa. Depois disso, você pode usar os logs para voltar ao local onde os valores incorretos foram introduzidos.

Lembre-se de que a depuração é um processo que retrocede. Você tem o resultado final - um bug - e encontra a causa que o precedeu. É sobre trabalhar para trás e, infelizmente, os depuradores apenas avançam. É aqui que um bom registro e análise post-mortem podem fornecer resultados muito melhores.