sexta-feira, abril 28, 2006

Bugs Terríveis de Achar - Execução Paranormal de Código

Os dois últimos dias foram de uma busca frenética atrás de um bug.

O bug se apresentou em um firmware que estamos desenvolvendo para um dispositivo e que roda em um microcontrolador. A programação deste tipo (sistema embarcado) é bem diferente da programação normal para PC. Como é comum nestes casos, o firmware é gravado em uma memória não volátil (memória Flash) dentro do próprio microcontrolador. Esta gravação é feita por um software de PC, através de um gravador ligado à porta paralela do PC e a determinados pinos do microcontrolador.

Uma segunda memória não volátil (EEProm), externa ao microcontrolador, é usada para manter dados não voláteis, alguns deles extremamente críticos para a operação (como o número de série do dispositivo). A gravação na EEProm requer uma série de passos: é preciso habilitar a gravação, enviar o comando de gravação, o endereço e o dado. Não é algo que possa acontecer por conta própria.

No processo de produção dos dispositivos, é gravado primeiro um software de teste no microcontrolador. Através de uma serial de debug, são feitos testes e gravadas as informações críticas. Após isto é gravado o firmware normal.

Bem, após a produção de 140 dispositivos, verificou-se que cerca de uma dúzia estava com número de série incorreto. Pânico! Uma primeira hipótese, erro de operação na produção, parece improvável dado a taxa de erros. Restam o software de teste estar gravando algo errado ou, pânico maior, o firmware normal estar destruindo as informações na EEProm. Por outro lado, este efeito nunca tinha sido observado com as duas dezenas de protótipos.

Uma primeira revisão do firmware mostra que a EEProm é gravada em poucos pontos. Segue-se uma discussão filosófica sobre a necessidade e conveniência destes pontos. Faz sentido colocar um número de série default em caso erro de checksum na EEProm? A conclusão é que o firmware normal deve evitar ao máximo gravar na região onde estão os parâmetros mais críticos. Entretanto, mudar isto antes de descobrir o que está acontecendo pode apenas mascarar um problema sério.

A decisão é acrescentar um teste para tentar pegar se o número de série está danificado. Se sim, acender um LED de erro e preservar o conteúdo da EEProm para ver se dá uma pista do que ocorre. Em seguida, tentar reproduzir o problema, refazendo o procedimento de produção com esta nova versão.

Feito o procedimento com 20 dispositivos, em 2 ocorre o erro. É uma boa notícia: o problema pode ser reproduzido.

Alguns testes mostram que o software de teste está gravando os valores corretos na EEProm. Hora de ativar os traces do firmware e observar as informações enviadas pela serial. Após alguma tentativas, verifica-se que a corrupção da EEProm ocorre logo após a carga do firmware.

O teste começa a ficar mais complicado. É preciso prestar atenção no trace enviado pelo dispositivo quando ele é re-iniciado após a carga. É um problema esporádico, às vezes ocorre apenas após muitas tentativas. Para dificultar, às vezes ocorre um erro na verificação feita após a gravação. Outras vezes, a execução do firmware não acontece ao final da gravação e verificação, sendo preciso esticar o braço e desligar e re-ligar o dispositivo.

Prestando mais atenção, percebe-se que o firmware executa mesmo quando ocorre um erro na verificação. Em uma destas ocasiões, o conteúdo da EEProm é danificado. Primeira teoria: o firmware executado estava danificado de uma forma que causou a gravação incorreta da EEProm. Segue-se uma discussão de como o firmware pode descobrir que está gravado errado e não executar neste caso (a técnica tradicional é colocar um checkum no código, obviamente não resolve se a rotina de verificação estiver danificada).

Segue-se mais bateria de testes, tentando ver se sempre que a EEProm é danificada ocorreu erro na verificação. Alguns minutos depois, teoria abalada: a EEprom foi corrompida sem que tivesse ocorrido erro de verificação.

Para tentar cercar mais o problema, coloquei na rotina de gravação da EEProm (no mais baixo nível) um teste do endereço de gravação. Se for o endereço do número de série, ao invés de gravar envia um ! para a serial. Mais algumas tentativas e aparece !!! isolado, sem os demais traces...

Olhando com mais atenção ainda, percebemos que existe alguma atividade no dispositivo durante a verificação da gravação, quando o processador devia estar parado. Nova teoria: existe algo errado no processo de gravação e verificação, que faz o processador iniciar a execução antes da hora e de forma "meio torta".

Para comprovar, coloquei uma variável que é iniciada com zero e incrementada a cada passo da iniciação do software. Na rotina de escrita na EEProm, acrescentei um teste: se não tiverem sido executados todos os passos da iniciação, envia pela serial @ seguido do valor do contador. Alguns poucos testes são suficientes para ver alguns @0 e @1 surgirem durante a verificação. Execução paranormal: a rotina de gravação está sendo executada desrespeitando o fluxo normal da execução.

Hora de reclamar do gravador para o projetista do hardware. Aparentemente o processador está recebendo um sinal de reset fora de hora. Após algumas tentativas de achar uma explicação no software para o comportamento anormal, decide-se colocar um capacitorzinho no sinal de reset que vem do gravador (algo como dar um tempinho em software). Antes de soldar o capacitor, o técnico decidiu verificar o cabo. Tinha um mau contato no pino de reset. Testes com um outro cabo e com o cabo com o mau contato eliminado confirmam que o problema sumiu.

Resumindo, um mau contato no cabo usado para gravar o firmware fazia com que ocasionalmente o sinal de reset "balança-se" durante a verificação da gravação. Isto deixava o processador meio "bêbado" e ele saia executando errado o firmware.

Agora é só eliminar todos os problemas potenciais que identificamos enquanto procurávamos o bug que não existia.

quinta-feira, abril 20, 2006

Sinais de Um Programa Ruim

Esbarrei com um artigo interessante sobre sinais no código fonte que indicam um programa problemático. Para quem não tiver paciência de ler o artigo todo, eis a lista de sinais:
  1. Comentários ausentes ou ruins
  2. Nomes inapropriados
  3. Formatação inconsistente do fonte
  4. Falta de teste de códigos de retorno e de erros em geral
  5. Repetições de código
  6. Falta de clareza
  7. Complicações desnecessárias
  8. Condições assumidas não documentadas
  9. Substituição das funções das bibliotecas padrões por funções próprias
  10. Falta de suporte à depuração
Esta lista pode inclusive ser usada como um checklist às avessas durante o desenvolvimento. Se você perceber que o seu código contém algum dos sinais acima, pare e conserte.

Se você precisar dar manutenção em um software, verifique antes os sinais. Se forem muitos, considere seriamente fazer um refactoring (mas pense muito antes de reescrever tudo).

quarta-feira, abril 19, 2006

GPF - Grilo Pulsante Fantasma

A foto acima é do brinqedinho que levei no Segundo Encontro de Programadores C/C++ em São Paulo. Como a regua e o cartão mostram, é um circuito bastante pequeno, onde os maiores componentes são a bateria, o buzzer (uma espécie de alto falante sem imã) e o resistor sensível a luz (LDR).

O componente mais importante, entretanto, é o PIC 12F675. Trata-se de um microcontrolador, um computardor quase completo dentro de um chip. Este modelo possui 1024 palavras para armazenamento de programa (Flash), 64 bytes de Ram para variáveis e 128 bytes de memória não volátil para dados (EEProm). O processador utiliza um conjunto de apenas 35 instruções. Neste projeto usei o clock interno de 4 MHz, o componente permite usar um cristal externo de até 20MHz. Possui um modo sleep, útil a circuitos a bateria, que reduz o consumo de energia a quase zero.

O microcontrolador possui uma quantidade grande de recursos. Possui internamente dois timers, que podem ser usados para gerar interrupções periódicas, fornecendo uma base tempo para o software. Seis dos oito pinos podem ser tratados como entrada ou saída digital (isto é, serem colocados em nível lógico "0" ou "1" por controle do software). Quatro dos pinos podem ser usados como entradas analógicas (o software pode ler o nível do sinal do pino como um valor digital de 8 ou 10 bits).

O objetivo do circuito era mostar os potenciais do microcontrolador e o fato dele poder até ser programado em C. Inspirado em um exemplo do fabricante do PIC, é um circuito para ser escondido em um lugar e perturbar as pessoas presentes. O circuito fica a maior parte do tempo dormindo. Em tempos aleatórios ele acorda e faz um pequeno bip (daí o grilo pulsante). Para tornar a detecção mais difícil, o bip é feito somente se o circuito estiver no escuro (daí o fantasma). O LED (que também só é acionado no escuro), permite confirmar que o circuito está ligado. Por último, um botão permite testar o circuito, soando o bip e piscando o LED. Desta forma, o cicuito usa:
  • uma entrada digital (o botão)
  • duas saídas digitais (o LED e o buzzer)
  • uma entrada analógica (o sensor de luz)
  • um timer para acordar o circuito periodicamente
  • um timer para fornecer a temporização para a geração da frequência enviada ao buzzer.
O software foi desenvolvido em C no PC e gravado no PIC através de um gravador externo. Acrescentando um conector e um resistor seria possível gravar o PIC sem retirá-lo da placa (usando o mesmo gravador).

O custo total do circuito fica abaixo de R$50. O PIC custa menos de R$15, comprado unitariamente em uma loja. O preço USA, para 100 peças é USD 1,19.

Quando se fala em microprocessadores, a maioria das pessoas pensa imediatamente em computadores de mesa. Entretanto a grande revolução está nos microcontroladores, que permitem acrescentar inteligência a equipamentos por um baixo custo. A nossa casa está repleta deles, escondidos em geladeiras, máquinas de lavar, micro-ondas, TVs, DVDs, etc.

segunda-feira, abril 17, 2006

Livros do mês

Um dos meus motivadores para aprender inglês foi a coleção de livros do meu pai, principalmente os de fição científica. A minha primeira leitura de verdade em inglês foi a trilogia original da Fundação de Isac Asimov (Foundation, Foundation and Empire e Second Foundation).

Embora isto tenho sido há muito tempo atrás, ainda existem alguns livros da coleção do meu pai que não tinha lido. Um deles era o clássico (infelizmente esgotado) The Weapon Shops of Isher de A. E. Van Vogt. Algumas idéias podem hoje parecer chavão, mas provavelmente eram novidade quando o livro foi escrito. É um daqueles livros que prende o leitor, você fica sempre querendo saber o que vai acontecer. De quebra tem um tradicional paradoxo de viagem no tempo, com o personagem que só existe no curto intervalo de tempo entre ele chegar do futuro e voltar ao passado.

Ao devolver este livro acabei pegando para reler The Double Helix de James D. Watson. São as recordações da descoberta da estrutura do DNA (que de forma não esperada esclareceu o processo de duplicação dos genes). O livro não se limita aos aspectos técnicos, tratando muito da questão do relacionamento entre os envolvidos e da concorrência versus cooperação. Mostra um pouco como funciona internamente o meio científico e as suas motivações.

Resumindo uma longa história em um único parágrafo, a estrutura do DNA foi descoberta por James Watson e Francis Crick após um período relativamente curto de estudos, a partir de dados experimentais (fotografias por raio-X de cristais de DNA) obtidos por Maurice Wilkins e Rosalind Franklin num trabalho bem mais demorado. Watson e Crick utilizaram principalmente a montagem de modelos, técnica usada anteriormente por Linus Pauling para descobrir a estrutura dos amino-ácidos e desprezada por Wilkins e Rosalind. Para complicar mais a situação, as principais fotografias usadas foram tiradas por Rosalind, e usadas sem o seu conhecimento, devido a problemas de relacionamento com os demais. Watson, Crick e Wilkins vieram ganhar o prêmio Nobel por esta descoberta (e outras relacionadas), porém Rosalind morreu alguns anos antes. Embora alguns considerem Rosalind uma injustiçada, prefiro a versão presente na Wikipedia.

segunda-feira, abril 03, 2006

C e C++

No Wiki do "C/C++ Brasil" está em discussão o nome do grupo. Um ponto sempre lembrado é o desejo de manter o C no nome, e nesta hora me sinto meio culpado, já que às vezes sinto que sou o único do grupo que programa no dia a dia em C.

Isto não significa que eu não tenha estudado e continue estudando C++. Ainda outro dia eu tomei um susto ao abrir um livro de C++ (de 94) e descobrir que eu não só tinha lido como tinha feito correções nos exemplos. E afinal, eu passei no exame de Visual C++ da Microsoft.

Na verdade, comecei a ter algum interesse em C++ no final dos anos 80. Nesta época chegamos a comprar na Humana uma cópia de um dos primeiros compiladores C++ para o DOS, o Zortech (que alguns anos depois foi comprada pela Symantec). A Borland foi uma grande divulgadora do C++, com o Turbo C++ e o Borland C++; lembro de um vídeo da Borland chamado "The World of Objects" (ou algo parecido). Em 92 a Humana fez um primeiro projeto em C++, usando o Microsoft C/C++ 7.0 (o primeiro compilador C++ da Microsoft).

Na Seal, fizemos alguns projetos com o Visual C++ 2.0 para serem rodados no NT 3.1 (ambos novidade na época). Alguns dos meu programas usavam até a MFC (então na versão 3.0). A maioria dos desenvolvimentos na Seal era feito em Visual C++ 1.5, para coletores de dados com sistema DOS. Eventualmente alguem teve a idéia de fazer uma biblioteca de classes para facilitar o desenvolvimento para os coletores. Embora eu não estivesse na época trabalhando na área de software, alguem me passou a especificação para dar os meus palpites. A especificação começava com uma classe CString (como se o VC 1.5 já não tivesse duas classes de string) e terminava com uma classe CUtil cujos membros eram funções diversas (para deixar tudo orientado a objeto?). No final a classe CUtil foi abandonada, mas a CString ficou lá.

Foi lendo esta especificação e a coluna de dúvidas de C++ do Microsoft System Journal que eu concluí que conceitualmente o C++ é bem mais pesado que o C. Recentemente escrevi um artigo sobre construtores no Wiki, e foi um trabalho bastante árduo para descrever algo que é simplesmente básico (quem usa C++ no dia a dia tem que ter tudo isto e muito mais na ponta da lingua). Talvez por isso eu continue usando C mesmo quando o C++ é uma opção disponível. O fato de eu estar cercado de outros programadores C também contribui, principalmente porque eu sempre tenho a esperança de deixar a manutenção para eles.