Para depurar meus programas, uso principalmente os seguintes métodos:
Use printf (ou equivalente em outros idiomas) para verificar o valor de uma variável após uma instrução específica ou para verificar se o programa digita uma instrução condicional ou um loop.
Use watch / breakpoints ao usar IDEs.
Eu sou capaz de resolver os problemas usando os métodos acima. Mas notei que existem dois tipos de compilações - depuração e lançamento. Pelo nome, eu posso entender que a compilação de depuração seria útil na depuração. Também li que a compilação de depuração armazena algo chamado informações e informações da tabela de símbolos.
Como posso usar a compilação de depuração para depuração? Existem outras técnicas de depuração que devo aprender?
Respostas:
Aprenda seu depurador
É realmente útil entender o depurador, seja ele com base em texto, IDE completo ou alguma mistura dos mesmos. Como você não fornece muitos detalhes, descreverei o caso geral:
1) Pontos de interrupção
Além de apenas parar em uma linha de código, muitos depuradores permitem especificar a quebra quando uma condição surgir (por exemplo, "x> 5"), após várias passagens pelo código ou quando alguma memória mudar de valor. Isso é muito útil para entender como seu código entra em um estado ruim, por exemplo, observar quando um ponteiro se torna nulo em vez de detectar a falha quando é desreferenciado.
2) Percorrendo o código
Você pode entrar em funções, linha por linha ao longo do código, pular linhas ('definir próxima instrução') e depois 'subir' as funções. É uma maneira realmente poderosa de seguir a execução do código para verificar se ele faz o que você pensa que faz :-)
3) Avaliando expressões
Assim, você pode colocar variáveis em uma lista de observação / janela e ver o valor dos mesmos mudar quando você atinge um ponto de interrupção ou percorre o código, mas geralmente também é possível fazer avaliações de expressões complexas, por exemplo, "x + y / 5" será avaliado. Alguns depuradores também permitem que você faça chamadas de função nas listas de observação. Você pode fazer coisas como "time ()", "MyFunction (...)", "time ()" e obter o tempo de duração de sua função.
4) Exceções e manipulação de sinal
Portanto, se o seu idioma suportar exceções e / ou sinais, você poderá configurar o depurador para saber como reagir a isso. Alguns depuradores permitem que você decifre o código no ponto em que a exceção está prestes a ocorrer, em vez de depois que ela não foi capturada. Isso é útil para rastrear problemas estranhos, como os erros "Arquivo não encontrado", porque o programa está sendo executado como uma conta de usuário diferente.
5) Anexando a um processo / núcleo
Às vezes, você precisa usar o depurador para saltar para um processo existente que está dando errado. Se você tiver um código-fonte por perto e os símbolos de depuração estiverem intactos, você poderá mergulhar como se tivesse iniciado no depurador em primeiro lugar. Isso também é semelhante para os core dumps, exceto que você geralmente não pode continuar a depuração nesses (o processo já terminou).
Configuração de compilação
Há várias variações de compilação que você pode fazer ativando ou desativando recursos como símbolos de depuração, otimizações e outros sinalizadores do compilador:
1) Depuração
Tradicionalmente, essa é uma construção simples, sem características especiais, o que facilita a depuração e a previsibilidade. Isso varia um pouco de plataforma, mas pode haver algum espaço extra, por exemplo, alocações e tamanhos de buffer, a fim de garantir a confiabilidade. Geralmente, um símbolo do compilador como DEBUG ou Condicional ("Debug") estará presente para que o código específico da depuração seja recebido. Geralmente, essa compilação é enviada com os símbolos de nível de função intactos, especialmente se a confiabilidade e / ou a repetibilidade são uma preocupação.
2) Versão / versão otimizada
A ativação de otimizações do compilador permite que alguns recursos de geração de código de baixo nível no compilador criem código mais rápido ou menor com base em suposições sobre seu código. Os aumentos de velocidade possíveis são irrelevantes se a escolha do algoritmo for ruim, mas para cálculos intensivos isso pode fazer a diferença suficiente através da eliminação de subexpressão comum e desenrolamento de loop, etc. Às vezes, as suposições feitas pelo otimizador são incompatíveis com seu código e, portanto, o otimizador tem que ser dobrado um entalhe. Os erros do compilador no código otimizado também foram um problema no passado.
3) Construção instrumentada / com perfil
Seu código é construído com código de instrumentação específico para medir o número de vezes que uma função é chamada e quanto tempo é gasto nessa função. Esse código gerado pelo compilador é gravado no final do processo de análise. Às vezes, é mais fácil usar uma ferramenta de software especializada para isso - veja abaixo. Esse tipo de construção nunca é enviado.
4) Construção segura / verificada
Todas as 'válvulas de segurança' são ativadas através de símbolos do pré-processador ou configurações do compilador. Por exemplo, macros ASSERT verificam parâmetros de função, iteradores verificam coleções não modificadas, canários são colocados na pilha para detectar corrupção, alocações de heap são preenchidas com valores sentinela (0xdeadbeef é memorável) para detectar corrupção de heap. Para clientes que têm problemas persistentes que só podem ser reproduzidos em seus sites, isso é uma coisa útil.
5) Compilação de recursos
Se você tem clientes diferentes com requisitos diferentes para o seu produto de software, é comum criar uma compilação para cada cliente que exercita as diferentes partes durante o teste. Por exemplo, um cliente deseja a funcionalidade offline e outro deseja apenas online. É importante testar nos dois sentidos se o código for criado de maneira diferente.
Log e rastreamento
Portanto, escrevemos algumas declarações úteis para printf () e escrevemos informações abrangentes e estruturadas sobre os traços nos arquivos de dados. Essas informações podem ser extraídas para entender o comportamento / características do tempo de execução do seu software. Se o seu código não falha, ou leva algum tempo para ser reproduzido, é útil ter uma imagem de, por exemplo, lista de threads, suas transições de estado, alocações de memória, tamanhos de pool, memória livre, número de identificadores de arquivo, etc. depende realmente do tamanho, da complexidade e dos requisitos de desempenho do seu aplicativo, mas como exemplo, os desenvolvedores de jogos desejam garantir que não haja 'picos' no uso da CPU ou da memória enquanto um jogo estiver em andamento, pois isso provavelmente afetará a taxa de quadros. Algumas dessas informações são mantidas pelo sistema, algumas pelas bibliotecas e o restante pelo código.
Outras ferramentas
Nem sempre é necessário criar uma compilação diferente para cobrir esses cenários: alguns aspectos podem ser escolhidos em tempo de execução através da configuração do processo (truques do Registro do Windows), disponibilizando bibliotecas alternativas com maior prioridade para as bibliotecas padrão, por exemplo, efence no seu caminho do carregador ou usando um ICE de software ou depurador especializado para analisar o seu software quanto às características de tempo de execução (por exemplo, Intel v-Tune). Alguns deles custam muito dinheiro, outros são ferramentas gratuitas para o Xcode do dtrace.
fonte
Uma compilação de depuração é criada com símbolos de depuração no executável. Basicamente, o que isso significa é que toda linha de código-fonte está vinculada ao código de máquina que foi gerado a partir dele, e todos os nomes de variáveis e funções são mantidos. No GCC, isso é feito com o
-g
sinalizador quando você compila arquivos de origem. Outra coisa que geralmente é feita é desativar a otimização, porque o compilador faz alguns truques legais que agilizam o programa, mas tornam a depuração impossível. No GCC, isso é feito com-O0
.A ferramenta mais útil que eu já usei para depuração é o gdb . É uma versão em texto dos pontos de interrupção do IDE que você mencionou. Você pode fazer muito mais com o gdb do que com um IDE. De fato, alguns IDEs são apenas um invólucro em torno do gdb, mas alguns recursos são perdidos. Você pode assistir a locais de memória, montagem de impressão, quadros de pilha de impressão, manipulação de sinal de alteração e muito mais. Se você é sério sobre depuração, eu aprenderia gdb ou algum programa equivalente de depuração baseado em texto.
Outra coisa que muitas vezes acho útil é o valgrind . Ele encontra vazamentos de memória e monitora se a memória foi inicializada ou não. Você o usa com sua compilação de depuração, porque então você obtém números de linhas onde coisas interessantes acontecem.
fonte
gdb
é programável, os IDEs normalmente não são. Não preciso de nenhum "poder" na ponta dos dedos quando um computador pode fazer todo o trabalho por mim enquanto tomo meu chá.bt
ingdb
) na maioria dos casos forneceria informações mais do que suficientes para uma hipótese 0 (se esse não for o caso, seu código estará bem abaixo do limite de qualidade abaixo do padrão) . Eu tenho usado depuradores desde o VMS, tentei todos os sabores possíveis, incluindo alguns animais exóticos extremos, como depuradores do tempo, mas até agora não consegui encontrar nenhum realmente útil. Você está testando suas suposições de maneira interativa - então está perdendo seu tempo. Estou testando minhas suposições em lote (podem ser muitas em paralelo), rapidamente e com muito pouco esforço.Existe uma técnica de depuração extremamente poderosa, ainda não mencionada nas outras respostas. Está disponível gratuitamente para alguns ambientes de desenvolvimento ou pode ser adicionado com um esforço relativamente pequeno a quase qualquer outra coisa.
Trata-se de um REPL incorporado, de preferência permitindo conectar-se a qualquer momento por meio de um soquete, executando em um thread dedicado e capaz de usar todas as formas possíveis de reflexão para o código em execução, modificando ou substituindo completamente o código em execução, adicionando coisas novas, executando funções, etc.
Você o terá pronto para usar se codificar, digamos, Common Lisp, Smalltalk, Python, Ruby, etc. É possível incorporar um intérprete leve (digamos, Lua, Guile, JavaScript ou Python) em um aplicativo nativo . Para ambientes baseados em JVM ou .NET, existem muitos compiladores e intérpretes incorporáveis disponíveis, e uma reflexão bastante poderosa existe de graça.
Essa abordagem é muito mais eficiente que os depuradores interativos / iterativos (como gdb, depurador do visual studio, etc.) e, para obter os melhores resultados, deve ser usado em conjunto com um recurso de registro adequado e asserções adequadamente colocadas.
fonte
Viaweb
história - eles se beneficiaram muito dessa abordagem, com a capacidade de depurar o sistema de produção em execução.A ferramenta de depuração mais importante que já usei é o despejo de memória post-mortem (despejo de núcleo no Linux, usuário dmp no Windows).
É um tópico realmente complexo, então aqui está um link :
basicamente (no Windows, a plataforma com a qual tenho mais experiência em depuração post-mortem), você cria seus aplicativos com símbolos salvos em um arquivo separado (um arquivo .pdb). Você os mantém em segurança (para que ninguém possa fazer engenharia reversa do código com facilidade) e aguarde uma falha. Quando isso ocorre (e você tem o DrWatson ou semelhante em execução para capturar a falha e gerar o arquivo de despejo), carrega o arquivo .dmp no WinDbg (depurador do Windows) junto com os símbolos (e, opcionalmente, um caminho para o código-fonte) e mostrará muitas informações, como pilha de chamadas, registradores, variáveis, valores de memória etc. É lindo quando funciona.
Para uma compilação de depuração, tudo isso é configurado para acontecer automaticamente. Você precisa ativar os símbolos ao criar a liberação. As compilações de depuração também adicionam outras coisas, como guardas de memória (que acionam exceções: você tenta gravá-las, isso mostra estouros de buffer ou corrupção de memória com muita facilidade; ou afirma coisas que não estão certas). Geralmente, embora as versões de depuração sejam para os desenvolvedores executarem e testarem seu código antes de transmiti-lo.
Agora, isso é para depuração de código nativo, o código .NET é um PiTA para itens post-mortem como esse, mas às vezes você pode obter itens de exceção .NET carregando o sos .
Todas as coisas complexas, mas não é tão ruim. Porém, não espere uma boa GUI pontuda, esta é a beleza da linha de comando.
fonte
Você também deve usar qualquer tipo de declaração de afirmação oferecida.
Você também deve escrever testes de unidade.
Perfeito. Você sabe tudo o que precisa saber.
Não. Não que você deva aprender. Você pode aprender mais se achar que isso vai ajudar. Mas você não precisa de nada além do que tem.
Nos últimos 30 anos, usei um depurador apenas algumas vezes (talvez três ou quatro). E então, eu apenas o usei para ler despejos de memória post-mortem para encontrar a chamada de função que falhou.
O uso do depurador não é uma habilidade essencial . A declaração de impressão é suficiente.
fonte
Aqui está uma lista rápida de técnicas:
Você também pode implementar comportamentos personalizados com ferramentas no estilo AOP, além de percorrer um longo caminho com uma boa ferramenta de análise estática.
fonte
Aprenda tudo sobre sua ferramenta de depuração.
Eles geralmente ocultam recursos realmente poderosos que podem ajudá-lo a entender melhor o que está acontecendo. (em particular, o depurador C ++ do Visual Studio)
fonte