2 Comentários

Meu firmware gerou Hardfault! E agora?

Hardfault

Olá caro leitor, saindo um pouco da minha tradicional linha de artigos voltados a processamento de sinais, neste pequeno texto vou ilustrar a vocês um problema que ocorre muito quando estamos em processo de desenvolvimento de firmware, as exceções! Aquele famoso loop infinito que ocorre quando algo deu errado durante um acesso de memória, instrução ou operação matemática indefinida. Tenho visto nas minhas andanças com cada vez mais frequência dúvidas de colegas de como proceder quando o microcontrolador apronta uma dessas, uma vez que o código do vetor de exceção quase nunca está implementado. Vamos ilustrar nosso caso especifico assumindo que o processador alvo seja um popular ARM Cortex-M3/M4.

Hardfault ou exceção de hardware

Bem comum em microcontroladores ARM Cortex, o hardfault é aquela exceção não mascarável (não pode ser desabilitada) que vai ocorrer quando o processador realizar ações como:

  • Acesso desalinhado de memória;
  • Acesso à instrução não definida ou ilegal;
  • Execução de instrução ARM em modo Thumb (ou vice-versa);
  • Operação matemática ilegal (divisão por 0).

A grande verdade é que pra cada um desses eventos existem vetores de exceção próprios, porém mascaráveis, ou seja, podem ser desligados caso aquele programador mais despreocupado não queira uma exceção "enchendo o saco". O hardfault propriamente dito só vai ocorrer quando o processador verificar no NVIC que uma das demais exceções ocorreu e ao mesmo tempo que essas estão mascaradas para somente então  desviar para o vetor de hardfault, que contém aquele triste loop infinito, e em várias situações deixando o desenvolvedor frustrado.

Como conseguir pistas de "crash" do Firmware pelo Hardfault?

Agora que já sabemos da existência da exceção e como funciona nos processadores derivados do ARMv7M (ARM Cortex), fica a questão de como iniciar a busca por um eventual bug que esteja causando o disparo da exceção (ao leitor mais curioso, o modelo de exceção apresentado é muito similar a outras arquiteturas de 16/32bits). O primeiro ponto é escrever um código de tratamento dentro do vetor de hardfault, de modo que em modo debug o processador faça algumas ações e pare a execução (sim, como se fosse um breakpoint) para permitir que o programador comece a buscar o problema.

O primeiro passo para escrever um bom código de notificação de exceção consiste primeiro em entender o Stack Frame gerado do processador cada vez que uma interrupção ou exceção é gerada. O Stack Frame é um conjunto de registradores que imediatamente após a ocorrência de uma exceção é acessado e salvo na pilha corrente (estando apontado pelo registrador MSP em sistemas bare-metal ou PSP em sistemas com uso de sistema operacional). No caso dos ARM Cortex-M, o Stack Frame gerado pode ser verificado na figura abaixo:

Hardfault - Stack Frame para processadores derivados do ARMv7M
Figura 1 : Stack Frame para processadores derivados do ARMv7M

Os registradores em questão são:

  • xPSR : Registrador de status e controle da aplicação;
  • PC : Contador de programa, aponta para o endereço de ocorrência da interrupção + 1;
  • LR : Chamado de link register, utilizado também para salvamento de endereço de retorno;
  • R12~R0: Registradores de uso geral, frequentemente usados como área de trabalho de um compilador C.

Basicamente, temos acima um snapshot completo do contexto corrente da nossa aplicação. Agora imagine, caro leitor, que seu programa escrito com todo esmero inesperadamente trava e cai dentro do vetor de Hardfault, concorda que podemos acessar o último stack frame gerado e rastrear de que lugar a falha veio?

Para isso, vamos pegar alguns trechos do tradicional startup.S. Esse arquivo é na verdade um "early code" que prepara toda a aplicação C para rodar (basicamente inicializa todas as memórias do microcontrolador e chama a função main). Vejam como ele é originalmente:

Acima  temos o trecho que implementa a estrutura da tabela de vetores dos microcontroladores Cortex, reparem que essa possui as primeiras 16 entradas separadas das demais. São essas as entradas das exceções donde tem-se a quarta entrada, o vetor que nos interessa, o de hardfault. Sabendo agora onde ele fica, vamos implementar uma função que consiga extrair o último stack frame gerado antes do firmware entrar nesse ponto, vejam como está:

Como o foco aqui é simplificar, iremos implementar nosso código de debug dentro do próprio Default_Handler, vejam como fica:

Eu sei, eu sei, Assembly, apesar do susto inicial veja que o código de startup é geralmente implementado em baixo nível tornando muito mais fácil embarcar código extra nesse módulo do que criar um arquivo separado e usar Assembly inline. Veja também que tal solução pode ser implementada em C com o auxílio das funções implementadas na CMSIS. Sobre o nosso módulo de rastreio de "crash", iniciamos, ao entrar no vetor de exceção, o desligamento de toda e qualquer interrupção futura com o cpsid e inibimos qualquer tipo de preempção (até mesmo de interrupções de altíssima prioridade). Em seguida garantimos que o processador fique no chamado thread mode, nesse modo o acesso a qualquer registrador e memória é permitido, para isso modificamos o registrador especial control. Com o uso das instruções isb e dsb fazemos o que se chama de esvaziamento do pipeline do microcontrolador, isso é necessário pois garantimos que a próxima instrução a ser executada seja em thread mode, evitando por exemplo que o acesso ao ponteiro de pilha principal MSP, seja inibido pelo hardware, e falando em pilha, esse é o passo seguinte. Com o uso da instrução de carga multipla, acessamos o stack frame composto de 7 registradores e os colocamos na área de trabalho. Agora eis que o leitor me pergunta: Por que na área de trabalho e não em uma estrutura com acesso pelo watch de variáveis? Simples, o acesso aos registradores do microcontrolador é o mínimo invasivo possível, não existe nenhum pós processamento que poderia mascarar os dados lidos, além disso, no work podemos acessar de todo tipo de ferramenta de debug, de um completíssimo ARM DS Studio a um terminal conectado via OpenOCD. Ao final a instrução bkpt é chamada fazendo com que o programa pare sem qualquer necessidade de ação do desenvolvedor, ou seja, toda a ação é transparente e, se uma falha for gerada, o programa automaticamente tomará todas as providências necessárias para análise e através do evento de parada irá notificar o usuário.

Tenho os registradores, e agora?

Uma vez de posse do stack frame, precisamos saber o que fazer, é nesse ponto que deixo a cargo de cada desenvolvedor tomar as suas providências de análise, porém alguns procedimentos que podem ajudar e muito:

  • Sempre cheque o contador de programa (PC), ele incialmente contém o endereço do exato local onde a exceção foi chamada;
  • Se no endereço apontado pelo PC você encontrar words de 0x00000000 ou 0xFFFFFFFF, desconfie, esses são tratados por undefined instruction pelo ARM;
  • Seguindo o raciocínio do tópico anterior, caso essa suspeita se confirme, cheque também o conteúdo do link register LR, e subtraia 1 do valor. O endereço resultante é o ponto de chamada de uma subrotina que contém entre suas instruções a que gerou a falha;
  • Um hardfault muito comum é o associado ao modo de operação do microcontrolador, assim no lugar do LR extraído da pilha, checar o LR que já está no work é muito útil, pois algo que pode ter acontecido é o microcontrolador Cortex, que só executa instruções thumb, ter feito algum acesso em modo ARM. Assim, cheque o modo de operação através desses códigos disponibilizados no guia de usuário da ARM.

Um exemplo prático, a movimentação de memória defeituosa

Vamos a um exemplo bem comum de hardfault, overflow de memória, considere o seguinte trecho de código:

Se executarmos esse código será quase certeza (salvo alguma providência tomada na linkagem) que veremos o código travado lá naquele breakpoint. Vamos seguir os passos básicos, vamos checar o PC, é provável que encontremos algo como: 0x2AC00000, 0x0, ou 0xFFFFFFFF, o que não diz muita coisa. Então vamos ao LR. Se pegarmos seu valor, subtrairmos 1, e jogar no disassembly, estaremos provavelmente no ponto onde o memcpy() foi chamado. Ainda com o disassembly aberto, poderemos observar as instruções até chegar em algo parecido com isso:

Um processo de load e store, e aqui entra a personalidade de quem esta com a tarefa de busca do bug. Alguns logo de cara ja irão desconfiar do overflow, esse que vos escrever, vai olhar provavelmente no conteúdo dos registradores r0~r12 onde aparecerá alguma operação suspeita, porém relacionada ao ciclo de load e store, concluindo que está havendo estouro de um ou dois dos vetores. Mas vejam que o bug propriamente vem desse ponto, mas a causa é outra, levando-nos a procurar na chamada do memcpy() por erros nos argumentos, corrigindo assim o erro.

Conclusão

Debug é uma tarefa que pode consumir minutos ou dias, porém um fator bem determinante do tempo gasto na procura por um problema de firmware está na forma que o desenvolvedor aproveita as ferramentas que seu ambiente de desenvolvimento oferece, seja linha de comando ou uma IDE completa. Assim, o uso de um código de trace no vetor de hardfault se torna útil pois atende aos dois extremos do desenvolvedor, reduzindo o tempo de busca de problemas de firmware fornecendo no mínimo uma rota incial de onde buscar a causa de falhas.

Referências

Manual de usuário dos processadores ARM Cortex

YIU, Joseph - The Definitive Guide for ARM Cortex M3 and M4 Processors

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Software » Meu firmware gerou Hardfault! E agora?
Talvez você goste:
Comentários:

2
Deixe um comentário

avatar
2 Comentários
0 Respostas
0 Seguidores
 
Discussão de maior alcance
Discussão mais quente
2 Autores de comentários
Ronaldo LinsSimao Berkof Comentários recentes
  Notificações  
recentes antigos mais votados
Notificar
Ronaldo Lins
Visitante
Ronaldo Lins

Muito bom o material. HardFault é um terror, porém pode ser muito útil quando acontece no momento do desenvolvimento...

Simao Berkof
Visitante
Simao Berkof

Parabéns pelo material, o artigo é muito bom.

Séries

Menu