ti-enxame.com

Qual foi a sua caça de bugs mais difícil e como você a encontrou e matou?

Esta é uma pergunta "Compartilhe o conhecimento". Estou interessado em aprender com seus sucessos e/ou fracassos.

Informações que podem ser úteis ...

Histórico:

  • Contexto: idioma, aplicativo, ambiente etc.
  • Como o bug foi identificado?
  • Quem ou o que identificou o bug?
  • Quão complexo foi reproduzir o bug?

A Caça.

  • Qual foi o seu plano?
  • Que dificuldades você encontrou?
  • Como o código ofensivo foi finalmente encontrado?

A Matança.

  • Quão complexa foi a correção?
  • Como você determinou o escopo da correção?
  • Quanto código estava envolvido na correção?

Pós-morte

  • Qual foi a causa raiz tecnicamente? saturação de buffer, etc.
  • Qual foi a causa raiz de 30.000 pés?
  • Quanto tempo levou o processo?
  • Houve algum recurso prejudicado pela correção?
  • Quais métodos, ferramentas, motivações você achou particularmente útil? ... terrivelmente inútil?
  • Se você pudesse fazer tudo de novo? ............

Estes exemplos são gerais, não aplicáveis ​​em todas as situações e possivelmente inúteis. Por favor, tempere conforme necessário.

31
Rusty

Na verdade, ele estava em um subcomponente de visualizador de imagens de terceiros do nosso aplicativo.

Descobrimos que 2-3 dos usuários de nosso aplicativo frequentemente faziam com que o componente visualizador de imagens lançasse uma exceção e morresse horrivelmente. No entanto, tivemos dezenas de outros usuários que nunca viram o problema, apesar de usar o aplicativo para a mesma tarefa na maior parte do dia útil. Também havia um usuário em particular que o recebia com muito mais frequência do que o resto deles.

Tentamos as etapas usuais:

(1) Eles trocaram de computador com outro usuário que nunca teve o problema de descartar o computador/configuração. - O problema os seguiu.

(2) Eles fizeram o login no aplicativo e funcionaram como um usuário que nunca viu o problema. - O problema ainda os seguia.

(3) O usuário relatou qual imagem estava visualizando e configurou um equipamento de teste para repetir a visualização da imagem milhares de vezes em rápida sucessão. O problema não se apresentou no arnês.

(4) Um desenvolvedor sentou-se com os usuários e assistiu-os o dia todo. Eles viram os erros, mas não os notaram fazendo nada fora do comum para causá-los.

Nós lutamos com isso por semanas tentando descobrir o que os "Usuários de erro" tinham em comum que os outros usuários não tinham. Não tenho idéia de como, mas o desenvolvedor na etapa (4) teve um momento eureka no caminho para trabalhar um dia digno da Enciclopédia Brown.

Ele percebeu que todos os "Usuários de erro" eram canhotos e confirmou esse fato. Somente usuários canhotos obtiveram os erros, nunca Righties. Mas como ser canhoto pode causar um bug?

Nós o fizemos sentar e assistir os canhotos novamente prestando atenção especificamente a qualquer coisa que eles estivessem fazendo de maneira diferente, e foi assim que descobrimos.

Aconteceu que o erro só acontecia se você movesse o mouse para a coluna de pixels mais à direita no visualizador de imagens enquanto carregava uma nova imagem (erro de estouro porque o fornecedor tinha um cálculo único para o evento de passagem do mouse).

Aparentemente, enquanto esperavam o carregamento da próxima imagem, todos os usuários naturalmente moveram a mão (e, portanto, o mouse) em direção ao teclado.

O único usuário que cometeu o erro com mais frequência foi um desses tipos de ADD que movia o mouse compulsivamente com muita impaciência enquanto esperava a próxima página carregar, assim, movia o mouse para a direita muito mais rapidamente e pressionava o botão no momento certo para que ela fizesse isso quando o evento de carregamento aconteceu. Até recebermos uma correção do fornecedor, pedimos para ela soltar o mouse depois de clicar (próximo documento) e não tocá-lo até carregar.

Daí em diante, era conhecido na legenda da equipe de desenvolvedores como "The Left Handed Bug"

70
JohnFx

Isso é de um período de longo tempo atrás (final dos anos 80).

A empresa em que trabalhei escreveu um pacote CAD (em FORTRAN) que rodava em várias estações de trabalho Unix (HP, Sun, Silcon Graphics etc.). Utilizamos nosso próprio formato de arquivo para armazenar os dados e Quando o pacote foi iniciado, o espaço em disco era escasso; portanto, havia muita troca de bits usada para armazenar vários sinalizadores nos cabeçalhos das entidades.

O tipo da entidade (linha, arco, texto etc) foi multiplicado por 4096 (eu acho) quando armazenado. Além disso, esse valor foi negado para indicar um item excluído. Então, para obter o tipo, tínhamos um código que:

type = record[1] MOD 4096

Em todas as máquinas, exceto uma, isso indicava ± 1 (para uma linha), ± 2 (para um arco) etc. e poderíamos verificar o sinal para ver se havia sido excluído.

Em uma máquina (eu acho que a HP), tivemos um problema estranho, onde o manuseio de itens excluídos estava estragado.

Isso foi nos dias que antecederam o IDE e os depuradores visuais, então eu tive que inserir instruções de rastreamento e log para tentar rastrear o problema.

Acabei descobrindo que era porque, enquanto todos os outros fabricantes implementavam MOD para que -4096 MOD 4096 resultou em -1 A HP implementou matematicamente corretamente para que -4096 MOD 4096 resultou em -4097.

Acabei tendo que passar por toda a base de código, salvando o sinal do valor e tornando-o positivo antes de executar o MOD e, em seguida, multiplicar o resultado pelo valor do sinal.

Isso levou vários dias.

11
ChrisF

Uau, boa leitura aqui!

O mais difícil foi anos atrás, quando o Turbo Pascal era grande, embora pudesse ter sido um dos primeiros IDEs C++ da época. Como desenvolvedor único (e terceiro participante desta startup), escrevi algo como um programa simplificado e amigável para o vendedor CAD. Foi ótimo na época, mas desenvolvi um travamento aleatório desagradável. impossível reproduzir, mas aconteceu com frequência suficiente para que eu partisse para uma caça aos bichos.

Minha melhor estratégia era dar um passo no depurador. O bug acontecia apenas quando o usuário havia inserido um desenho suficiente e talvez tivesse que estar em um determinado modo ou estado de zoom; portanto, havia muitas configurações tediosas e pontos de interrupção de limpeza, funcionando normalmente por um minuto para inserir um desenho e, em seguida, percorra um grande pedaço de código. Especialmente úteis foram os pontos de interrupção que pulariam um número ajustável de vezes e depois quebrariam. Todo esse exercício teve que ser repetido várias vezes.

Eventualmente, reduzi-o a um local onde uma sub-rotina estava sendo chamada, recebendo um 2, mas de dentro dela vi algum número sem sentido. Eu poderia ter percebido isso antes, mas não havia entrado nessa sub-rotina, supondo que ela recebesse o que lhe foi dado. Cego ao assumir que as coisas mais simples estavam bem!

Acabou colocando um int de 16 bits na pilha, mas a sub-rotina esperava 32 bits. Ou algo assim. O compilador não preencheu automaticamente todo o valor para 32 bits ou fez uma verificação de tipo suficiente. Era trivial consertar, apenas parte de uma linha, quase nenhum pensamento necessário. Mas para chegar lá foram necessários três dias de caça e questionamento do óbvio.

Então, eu tenho experiência pessoal com essa história sobre o consultor caro, depois de um tempo dá um toque em algum lugar e cobra US $ 2000. Os executivos exigem um colapso, e US $ 1 para a torneira, US $ 1999 para saber onde tocar. Exceto no meu caso, era hora e não dinheiro.

Lições aprendidas: 1) use os melhores compiladores, onde "melhor" é definido como incluindo a verificação de tantos problemas quanto a ciência da computação sabe verificar e 2) questiona as coisas óbvias simples ou, pelo menos, verifica seu funcionamento adequado.

Desde então, todos os bugs difíceis têm sido realmente difíceis, pois sei verificar as coisas simples com mais detalhes do que parece necessário.

A lição 2 também se aplica ao bug eletrônico mais difícil que já corrigi, também com uma correção trivial, mas vários EEs inteligentes foram interrompidos por meses. Mas este não é um fórum de eletrônicos, então não vou dizer mais nada.

7
DarenW

A condição de corrida de dados de rede do inferno

Eu estava escrevendo um cliente/servidor de rede (Windows XP/C #) para trabalhar com um aplicativo semelhante em uma estação de trabalho muito antiga (Encore 32/77) escrita por outro desenvolvedor.

O que o aplicativo fez basicamente foi compartilhar/manipular certos dados no Host para controlar o processo do Host executando o sistema com nossa sofisticada interface do usuário com tela sensível ao toque para vários monitores baseada em PC.

Isso foi feito com uma estrutura de três camadas. O processo de comunicação leu/gravou dados de/para o Host, realizou todas as conversões de formato necessárias (endianness, formato de ponto flutuante etc.) e gravou/leu os valores de/para um banco de dados. O banco de dados agiu como um intermediário de dados entre as comunicações e as UIs da tela sensível ao toque. O aplicativo da interface do usuário da tela de toque gerou interfaces de tela de toque com base em quantos monitores foram conectados ao PC (ele detectou isso automaticamente).

No período de tempo determinado, um pacote de valores entre o Host e o nosso PC poderia enviar apenas 128 valores no máximo por fio, com uma latência máxima de ~ 110ms por ida e volta (o UDP foi usado com uma conexão Ethernet direta de x-over entre os computadores). Portanto, o número de variáveis ​​permitidas com base no número variável de telas sensíveis ao toque anexadas estava sob controle estrito. Além disso, o Host (apesar de ter uma arquitetura multiprocessador bastante complexa com barramento de memória compartilhada usado para computação em tempo real) tinha cerca de 1/100 da capacidade de processamento do meu telefone celular, por isso foi encarregado de realizar o mínimo processamento possível e seu servidor/client teve que ser escrito em Assembly para garantir isso (o Host estava executando uma simulação em tempo real que não podia ser afetada pelo nosso programa).

A questão era. Alguns valores, quando alterados na tela sensível ao toque, não aceitavam apenas o valor recém-inserido, mas alternavam aleatoriamente entre esse valor e o valor anterior. Isso e apenas em alguns valores específicos em algumas páginas específicas com uma certa combinação de páginas já exibiu o sintoma. Quase perdemos o problema completamente até começarmos a executá-lo no processo inicial de aceitação do cliente


Para definir o problema, escolhi um dos valores oscilantes:

  • Eu verifiquei o aplicativo Touchscreen, estava oscilando
  • Eu verifiquei o banco de dados, oscilando
  • Eu verifiquei o aplicativo de comunicação, oscilando

Então eu comecei o wireshark e comecei a decodificar manualmente as capturas de pacotes. Resultado:

  • Não oscilando, mas os pacotes não pareciam corretos, havia muitos dados.

Percorri todos os detalhes do código de comunicação cem vezes, sem encontrar falhas/erros.

Finalmente, comecei a enviar e-mails para o outro desenvolvedor, perguntando em detalhes como o fim dele funcionava para ver se havia algo que estava faltando. Então eu encontrei.

Aparentemente, quando ele enviou dados, ele não liberou a matriz de dados antes da transmissão; portanto, basicamente, ele estava substituindo o último buffer usado com os novos valores substituindo os antigos, mas os valores antigos não substituídos ainda estão sendo transmitidos.

Portanto, se um valor estivesse na posição 80 da matriz de dados e a lista de valores solicitados mudasse para menos de 80, mas esse mesmo valor estivesse contido na nova lista, os dois valores existiriam no buffer de dados para esse buffer específico em qualquer Tempo dado.

O valor lido do banco de dados dependia do intervalo de tempo em que a interface do usuário estava solicitando o valor.


A correção foi dolorosamente simples. Leia o número de itens recebidos no buffer de dados (na verdade, ele estava contido como parte do protocolo de pacote) e não leia o buffer além desse número de itens.


Lições aprendidas:

  • Não tome como garantido o poder da computação moderna. Houve um tempo em que os computadores não eram compatíveis com Ethernet e a descarga de uma matriz podia ser considerada cara. Se você realmente deseja ver até onde chegamos, imagine um sistema que praticamente não tem forma de alocação dinâmica de memória. Ou seja, o processo executivo teve que pré-alocar toda a memória para todos os programas em ordem e nenhum programa poderia crescer além desse limite. IE, alocar mais memória para um programa sem recompilar todo o sistema pode causar uma falha maciça. Eu me pergunto se as pessoas vão falar sobre os dias pré-coleta de lixo à mesma luz algum dia.

  • Ao trabalhar em rede com protocolos personalizados (ou manipular a representação de dados binários em geral), certifique-se de ler as especificações até entender todas as funções de todos os valores enviados pelo canal. Quero dizer, leia até seus olhos doerem. As pessoas manipulam dados manipulando bits ou bytes individuais, têm maneiras muito inteligentes e eficientes de fazer as coisas. A falta dos mínimos detalhes pode danificar o sistema.

O tempo total para consertar foi de 2 a 3 dias, com a maior parte do tempo gasto trabalhando em outras coisas, quando fiquei frustrado com isso.

Nota: O computador host em questão não suporta ethernet por padrão. O cartão para conduzi-lo foi feito sob medida e adaptado e a pilha de protocolos praticamente não existia. O desenvolvedor com quem eu estava trabalhando era um programador infernal, ele não apenas implementou uma versão simplificada do UDP e uma pilha Ethernet falsa mínima (o processador não era poderoso o suficiente para lidar com uma pilha Ethernet completa) no sistema para este projeto mas ele fez isso em menos de uma semana. Ele também fora um dos líderes originais da equipe do projeto que havia projetado e programado o SO em primeiro lugar. Vamos apenas dizer, qualquer coisa que ele já tenha compartilhado sobre computadores/programação/arquitetura, não importa quanto tempo acabe ou quanto eu já seja novo, eu ouviria cada Palavra. Não há nada mais valioso do que trabalhar com pessoas boas que têm uma verdadeira paixão pelo que fazem.

6
Evan Plaice

O fundo

  • Em um aplicativo WCF de missão crítica, dirige um site e fornece processamento trasacional de back-end.
  • Aplicativo de grande volume (centenas de chamadas por segundo)
  • Múltiplas instâncias do servidor
  • centenas de testes de unidade aprovados e inúmeros ataques de controle de qualidade

O inseto

  • Quando movido para a produção, o servidor funcionaria bem por um período aleatório e começaria a se degradar rapidamente e levar a CPU da caixa para 100%.

Como eu o encontrei

No começo, eu tinha certeza de que era um problema de desempenho normal, então criei um log elaborado. O desempenho verificado em todas as chamadas conversava com o pessoal do banco de dados sobre a utilização, observava os servidores em busca de problemas. 1 semana

Então eu tinha certeza de que tinha um problema de contenção de threads. Eu verifiquei que meus deadlocks tentavam criar a situação, criamos ferramentas para tentar criar a situação na depuração. Com a crescente frustração da gerência, voltei-me aos meus colegas sobre como sugerir coisas de reiniciar o projeto do zero para limitar o servidor a um thread. 1,5 semanas

Então olhei para o Tess Ferrandez O blog criou um arquivo de despejo de usuário e o analizou com o windebug na próxima vez que o servidor fez um despejo. Constatou que todos os meus threads estavam presos na função dictionary.add.

O longo e curto dicionário pequeno que apenas controlava qual log gravar erros de x threads não foi sincronizado.

5
rerun

Tínhamos um aplicativo que estava conversando com um dispositivo de hardware que, em alguns casos, falharia em funcionar corretamente se o dispositivo fosse fisicamente desconectado até que fosse novamente conectado e redefinido duas vezes.

O problema acabou sendo que um aplicativo em execução na inicialização ocasionalmente apresentava falhas de falha ao tentar ler a partir de um sistema de arquivos que ainda não havia sido montado (por exemplo, se um usuário o configurasse para ler em um volume NFS). Na inicialização, o aplicativo envia alguns ioctls ao driver para inicializar o dispositivo, depois lê as definições de configuração e envia mais ioctls para colocar o dispositivo no estado correto.

Um bug no driver estava causando a gravação de um valor inválido no dispositivo quando a chamada de inicialização foi feita, mas o valor foi substituído por dados válidos depois que as chamadas foram feitas para colocar o dispositivo em um estado específico.

O dispositivo em si tinha uma bateria e detectaria se perdesse energia da placa-mãe e gravaria um sinalizador na memória volátil, indicando que havia perdido energia, entraria em um estado específico na próxima vez em que fosse ligado e em um dispositivo específico. É necessário enviar instruções para limpar a bandeira.

O problema era que, se a energia fosse removida depois que os ioctls fossem enviados para inicializar o dispositivo (e gravassem o valor inválido no dispositivo), mas antes que dados válidos pudessem ser enviados. Quando o dispositivo era ligado novamente, ele via o sinalizador definido e tentava ler os dados inválidos enviados pelo driver devido à initalização incompleta. Isso colocaria o dispositivo em um estado inválido, onde o sinalizador de desligamento havia sido apagado, mas o dispositivo não receberia mais instruções até que fosse reinicializado pelo driver. A segunda redefinição significaria que o dispositivo não estava tentando ler os dados inválidos armazenados nele e receberia instruções de configuração corretas, permitindo que elas fossem colocadas no estado correto (supondo que o aplicativo que envia os ioctls não tenha falhado) ).

No final, demorou cerca de duas semanas para descobrir o conjunto exato de circunstâncias que estavam causando o problema.

3
Cercerilla

Programa de quadro principal parou de funcionar sem motivo

Acabei de publicar isso em outra pergunta . Veja a publicação aqui

Isso aconteceu porque eles instalaram uma versão mais recente do compilador no quadro principal.

Atualização 06/11/13: (a resposta original foi excluída pelo OP)

Eu herdei esse aplicativo de quadro principal. Um dia, do nada, parou de funcionar. É isso aí ... só parou.

Meu trabalho era fazê-lo funcionar o mais rápido possível. O código fonte não foi modificado por dois anos, mas de repente parou. Tentei compilar o código e ele quebrou na linha XX. Olhei para a linha XX e não sabia dizer o que faria a linha XX quebrar. Eu pedi as especificações detalhadas para esta aplicação e não havia nenhuma. A linha XX não era a culpada.

Imprimi o código e comecei a analisá-lo de cima para baixo. Comecei a criar um fluxograma do que estava acontecendo. O código era tão complicado que eu mal conseguia entender. Desisti de tentar fazer um fluxograma. Eu tinha medo de fazer alterações sem saber como essa alteração afetaria o restante do processo, principalmente porque não tinha detalhes do que o aplicativo fazia.

Então, decidi começar no topo do código-fonte e adicionar freios à linha branca e à espinha para tornar o código mais legível. Percebi que, em alguns casos, havia condições que combinavam ANDs e ORs e não era claramente distinguível entre quais dados estavam sendo ANDed e quais dados estavam sendo ORed. Então comecei a colocar parênteses em torno das condições AND e OR para torná-las mais legíveis.

À medida que eu descia limpando, eu periodicamente salvava meu trabalho. A certa altura, tentei compilar o código e aconteceu uma coisa estranha. O erro saltou da linha de código original e agora estava mais abaixo. Então continuei, separando as condições AND e OR com parens. Quando terminei de limpá-lo, funcionou. Vá em frente.

Decidi então visitar a loja de operações e perguntar se eles haviam instalado recentemente algum novo componente no chassi principal. Eles disseram que sim, recentemente atualizamos o compilador. Hmmmm.

Acontece que o compilador antigo avaliou a expressão da esquerda para a direita, independentemente. A nova versão do compilador também avaliou expressões da esquerda para a direita, mas o código ambíguo significa que a combinação pouco clara de ANDs e ORs não pôde ser resolvida.

Lição que aprendi com isso ... SEMPRE, SEMPRE, SEMPRE use parênteses para separar as condições AND e as condições OR quando forem usadas em conjunto uma com a outra).

2
Michael Riley - AKA Gunny

Eu tive que consertar algumas coisas confusas de concorrência no último semestre, mas o bug que ainda mais se destaca foi em um jogo baseado em texto que eu estava escrevendo no PDP-11 Assembly para uma tarefa de casa. Era baseado no Jogo da Vida de Conway e, por algum motivo estranho, grande parte da informação ao lado da grade era constantemente substituída por informações que não deveriam estar lá. A lógica também era bastante direta, por isso era muito confusa. Depois de passar por isso várias vezes para redescobrir que toda a lógica está correta, notei de repente qual era o problema. Essa coisa: .

No PDP-11, esse pequeno ponto próximo a um número faz com que seja a base 10 em vez de 8. Era próximo a um número que delimitava um loop que deveria estar limitado à grade, cujo tamanho foi definido com os mesmos números, mas na base 8).

Ele ainda se destaca por causa da quantidade de danos causados ​​por uma adição tão pequena do tamanho de 4 pixels. Então, qual é a conclusão? Não codifique no conjunto PDP-11.

2
EpsilonVector

Ainda estou na minha caçada mais difícil. É um daqueles que às vezes está lá e às vezes não é bugs. É por isso que estou aqui, às 6h10 do dia seguinte.

Histórico:

  • Contexto: idioma, aplicativo, ambiente, etc.
    • PHP OS Commerce
  • Como o bug foi identificado?
    • Ordens aleatórias que funcionam parcialmente, aleatoriamente falham e redirecionam questões
  • Quem ou o que identificou o bug?
    • Cliente, e o problema de redirecionamento era óbvio
  • Qual a complexidade da reprodução do bug?
    • Não consegui reproduzir, mas o cliente conseguiu.

A Caça.

  • Qual foi o seu plano?
    • Adicione código de depuração, preencha a ordem, analise os dados, repita
  • Que dificuldades você encontrou?
    • Falta de problemas repetíveis e código horrível
  • Como o código infrator foi finalmente encontrado?
    • muito código ofensivo foi encontrado ... mas não exatamente o que eu precisava.

A Matança.

  • Quão complexa foi a correção?
    • muito
  • Como você determinou o escopo da correção?
    • não havia escopo ... estava em todo lugar
  • Quanto código estava envolvido na correção?
    • Tudo isso? Eu não acho que houve um arquivo intocado

Pós-morte

  • Qual foi a causa raiz tecnicamente? saturação de buffer, etc.
    • má prática de codificação
  • Qual foi a causa raiz de 30.000 pés?
    • Eu prefiro não falar...
  • Quanto tempo levou o processo?
    • para sempre e um dia
  • Houve algum recurso prejudicado pela correção?
    • característica? Ou seria um inseto?
  • Quais métodos, ferramentas, motivações você achou particularmente útil? ... terrivelmente inútil?
  • Se você pudesse fazer tudo de novo? ............
    • ctrl + a Del
2
WalterJ89

Para um projeto da Universidade, estávamos escrevendo um sistema de nós P2P distribuídos que compartilham arquivos, com suporte a multicasting para detectar um ao outro, vários anéis de nós e um servidor de nomes para que um nó seja atribuído a um cliente.

Escrito em C++, usamos POCO para isso, pois permite a programação de Nice IO, Socket e Thread.


Surgiram dois bugs que nos incomodaram e nos fizeram perder muito tempo, um realmente lógico:

Aleatoriamente, um computador estava compartilhando o IP do host local em vez do IP remoto.

Isso fez com que os clientes se conectassem ao nó no mesmo PC ou nós para se conectarem.

Como identificamos isso? Quando melhoramos a saída no servidor de nomes, descobrimos mais tarde quando reiniciamos os computadores que nosso script para determinar o IP a fornecer estava errado. Aleatoriamente, o dispositivo lo foi listado primeiro em vez do dispositivo eth0 ... Realmente estúpido. Portanto, agora codificamos o código para eth0, pois isso é compartilhado entre todos os computadores da universidade ...


E agora mais irritante:

Aleatoriamente, o fluxo de pacotes seria interrompido aleatoriamente.
Quando o próximo cliente se conectar, ele continuará ...

Isso aconteceu de forma aleatória e, como mais de um computador está envolvido, é mais irritante depurar esse problema, os computadores da universidade não nos permitem executar o Wireshark naqueles, então ficamos imaginando se o problema estava no lado de envio ou no recebimento. lado.

Com muita saída no código, assumimos que o envio dos comandos funciona bem,
isso nos deixou imaginando onde estava o problema real ... Parecia que a maneira como o POCO pesquisa era errado e que deveríamos procurar caracteres disponíveis no soquete de entrada.

Partimos do pressuposto de que isso funcionava como testes mais simples em um protótipo que envolvia menos pacotes não causava esse problema, então isso nos levou a supor que a declaração da pesquisa estava funcionando, mas ... Não estava. :-(


Lições aprendidas:

  • Não faça suposições estúpidas como a ordem dos dispositivos de rede.

  • As estruturas nem sempre realizam seu trabalho (implementação ou documentação) corretamente.

  • Forneça saída suficiente no código, se não for permitido, certifique-se de registrar detalhes estendidos em um arquivo.

  • Quando o código não foi testado por unidade (porque é muito difícil), não assuma que as coisas funcionem.

2
Tamara Wijsman

Este é apenas um bug muito simples que de alguma forma eu me transformei em um pesadelo.

Antecedentes: eu estava trabalhando para criar meu próprio sistema operacional. A depuração é muito difícil (instruções de rastreamento são tudo o que você pode ter e, às vezes, nem isso)

Bug: Em vez de fazer duas opções de encadeamento no modo de usuário, seria uma falha de proteção geral.

A caça aos bugs: passei provavelmente uma semana ou duas tentando consertar esse problema. Inserindo instruções de rastreamento em todos os lugares. Examinando o código de montagem gerado (do GCC). Imprimindo todo e qualquer valor que pude.

O problema: em algum lugar no início da busca de bugs, eu havia colocado uma instrução hlt no crt0. O crt0 é basicamente o que inicializa um programa do usuário para uso em um sistema operacional. Esta instrução hlt causa um GPF quando executado no modo de usuário. Coloquei lá e basicamente esqueci. (originalmente, o problema era um excesso de buffer ou erro de alocação de memória)

A correção: remova a instrução hlt :) Depois de removê-la, tudo funcionou sem problemas.

O que eu aprendi: Ao tentar depurar um problema, não perca o controle das correções que você tenta. Faça diffs regulares em relação à versão estável mais recente do controle de origem e veja o que você mudou recentemente quando nada mais funciona

1
Earlz

Histórico:

  • Contexto: servidor Web (C++), que permite aos clientes fazer check-in
  • Bug: ao solicitar a página, ela simplesmente não responde, o farm inteiro que é, e os processos são eliminados (e relançados) porque demoram muito tempo (apenas alguns segundos são permitidos) para servir a página
  • Alguns usuários se queixaram, mas era extremamente esporádico, portanto quase imperceptível (as pessoas tendem a clicar em "Atualizar" quando uma página não é veiculada). No entanto, percebemos os lixões principais;)
  • Na verdade, nunca conseguimos reproduzir em nossos ambientes locais, o bug apareceu algumas vezes nos sistemas de teste, mas nunca apareceu durante os testes de desempenho?

A Caça.

  • Plano: Bem, como tínhamos despejos e registros de memória, queríamos analisá-los. Como estava afetando todo o farm e tivemos alguns problemas com bancos de dados no passado, suspeitamos que o banco de dados (banco de dados único para vários servidores)
  • Dificuldades: um despejo de servidor completo é enorme e, portanto, é limpo com bastante frequência (para não ficar sem espaço), por isso tivemos que ser rápidos em pegar um quando ocorreu ... Nós persistimos. Os dumps mostravam várias pilhas (nunca nada de banco de dados, tanto por isso), falharam ao preparar a página em si (não nos cálculos anteriores) e confirmaram o que os logs mostravam, preparar a página às vezes levava muito tempo, até embora seja apenas um mecanismo de modelo básico com dados pré-calculados (MVC tradicional)
  • Como chegar a isso: Depois de mais algumas amostras e algumas reflexões, percebemos que era necessário um tempo lendo os dados do disco rígido (o modelo da página). Como se tratava de toda a fazenda, primeiro procuramos trabalhos agendados (crontab, lotes), mas os horários nunca correspondiam de uma ocorrência para outra ... Por fim, ocorreu-me que isso sempre acontecia alguns dias antes da ativação de uma nova versão do software e eu tive um AhAh! momento ... ele foi causado pela distribuição do software! A entrega de várias centenas de megabytes (compactados) pode prejudicar o desempenho do disco:/É claro que a distribuição é automatizada e o arquivo é enviado para todos os servidores ao mesmo tempo (multicast).

A Matança.

  • Fix Complexity: alternando para modelos compilados
  • Código afetado: nenhum, uma simples alteração no processo de compilação

Pós-morte

  • Causa raiz: problema operacional ou falta de planejamento futuro :)
  • Escala de tempo: demorou meses para rastrear, uma questão de dias para corrigir e testar, algumas semanas para teste e implantação de QA e desempenho - não há pressa, pois sabíamos que a implantação da correção acionaria o bug ... e nada mais ... meio pervertido mesmo!
  • Efeitos colaterais adversos: impossibilidade de alternar modelos em tempo de execução agora que eles estão inseridos no código entregue, mas não usamos muito o recurso, pois geralmente alternar modelos significa que você tem mais dados para despejar. O uso de css é principalmente suficiente para alterações de layout "pequenas".
  • Métodos, ferramentas: gdb + monitoramento! Levamos um tempo para suspeitar do disco e, em seguida, identificar a causa dos picos de atividade no gráfico de monitoramento ...
  • Da próxima vez: trate todos IO como adversos!
1
Matthieu M.

O mais difícil nunca foi morto porque nunca poderia ser reproduzido senão em todo o ambiente de produção com a fábrica em operação.

O mais louco que eu matei:

Os desenhos são impressos sem sentido!

Olho o código e não consigo ver nada. Pego um trabalho para fora da fila da impressora e o examino, parece bom. (Isso foi na época dos, PCL5 com HPGl/2 incorporado - na verdade, muito bom para plotar desenhos e sem dores de cabeça para criar uma imagem rasterizada em memória limitada.) Dirijo-o para outra impressora que deve entendê-lo, ele imprime bem .

Reverta o código, o problema ainda está lá.

Finalmente, eu faço manualmente um arquivo simples e o envio para a impressora - sem sentido. Acontece que não foi meu bug, mas a própria impressora. A empresa de manutenção atualizou para a versão mais recente quando estavam consertando outra coisa e essa versão mais recente teve um erro. Fazer com que eles entendessem que haviam retirado a funcionalidade crítica e precisavam atualizá-la para uma versão anterior era mais difícil do que encontrar o bug.

Um que foi ainda mais irritante, mas como estava apenas na minha caixa, eu não colocaria em primeiro lugar:

Borland Pascal, código DPMI para lidar com algumas APIs não suportadas. Execute-o, ocasionalmente funcionou, geralmente deu certo tentando lidar com um ponteiro inválido. Porém, nunca produziu um resultado errado, como seria de esperar de pisar em um ponteiro.

Depuração - se eu seguisse o código, ele sempre funcionaria corretamente, caso contrário, seria tão instável quanto antes. A inspeção sempre mostrava os valores certos.

O culpado: havia dois.

1) O código da biblioteca da Borland tinha um erro grave: ponteiros em modo real estavam sendo armazenados em variáveis ​​de ponteiro em modo protegido. O problema é que a maioria dos ponteiros no modo real tem endereços de segmento inválidos no modo protegido e, quando você tenta copiar o ponteiro, ele o carrega em um par de registradores e o salva.

2) O depurador nunca diria nada sobre uma carga tão inválida no modo de etapa única. Não sei o que ele fez internamente, mas o que foi apresentado ao usuário parecia completamente correto. Eu suspeito que ele não estava realmente executando a instrução, mas sim simulando-a.

1
Loren Pechtel