O que devo aprender mais sobre depuração? [fechadas]

8

Para depurar meus programas, uso principalmente os seguintes métodos:

  1. 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.

  2. 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?

Biscoito
fonte
A diferença depende da plataforma. Para Java, a diferença é pequena. Para C, pode ser muito diferente
2
em quais idiomas / plataformas você está codificando?
DXM

Respostas:

7

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.

JBRWilkinson
fonte
6

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 -gsinalizador 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.

Jarryd
fonte
1
Mesmo? Toda vez que tive que usar o GDB, considero um retrocesso significativo em relação à capacidade e facilidade de uso que tenho ao meu alcance com um bom depurador baseado em IDE. Particularmente porque o GDB é uma ferramenta altamente geral, enquanto um depurador integrado pode tirar vantagem de conhecer as regras do idioma para o qual foi projetado para fornecer feedback mais específico e relevante.
Mason Wheeler
1
O GDB é muito projetado para C e C ++. Eu usei depuradores IDE por um bom tempo e eles me frustraram sem fim. Acho que tenho muito mais controle sobre o que estou fazendo quando uso o GDB.
Jarryd
2
@MasonWheeler, 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á.
SK-logic
3
@MasonWheeler, não, você está fazendo algo errado ao clicar em "passo-inspecionar-passo-inspecionar" como um louco. É muito mais fácil escrever um script minúsculo para verificar sua hipótese atual sobre a causa de um problema, executá-lo (da mesma maneira que você está executando os testes funcionais) e analisar os resultados, repetir, se necessário. Todas as formas de uma depuração interativa são quase sempre quase inúteis, mesmo que seja um post-mortem de um core dump. Um script de depuração típico configuraria vários pontos de interrupção, executaria, inspecionaria e formataria bem os valores nos pontos de interrupção.
SK-logic,
1
@Mason Wheeler, um simples rastreamento de pilha ( btin gdb) 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.
SK-logic,
3

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.

SK-logic
fonte
mas não usado em conjunto com um hacker que acessa sua rede. Certifique-se de desativar isso em compilações Release :)
gbjbaanb
@gbjbaanb, é para isso que o SSL é usado. Leia a Viawebhistória - eles se beneficiaram muito dessa abordagem, com a capacidade de depurar o sistema de produção em execução.
SK-logic
2

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.

gbjbaanb
fonte
2

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.

Você também deve usar qualquer tipo de declaração de afirmação oferecida.

Você também deve escrever testes de unidade.

Eu sou capaz de resolver os problemas usando os métodos acima.

Perfeito. Você sabe tudo o que precisa saber.

Existem outras técnicas de depuração que devo aprender?

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.

S.Lott
fonte
0

Aqui está uma lista rápida de técnicas:

  • Gráficos de chamada estática
  • Gráficos dinâmicos de chamadas
  • Criação de perfil de ponto ativo
  • Observação de mensagens de thread
  • Reproduzir depuração / execução reversa
  • Pontos de controle
  • Tracepoints

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.

Paul Nathan
fonte
0

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)

Klaim
fonte