Encontrar la fuente de una falla grave usando HardFault_Handler extendido

He detectado algunas fallas graves en el firmware que creé con FreeRTOS en una MCU SAMD21 (ARM Cortex-M0).

Así que tomé otra medida para averiguar la causa y, finalmente, encontré este artículo en Code_Red que señala el fragmento que se menciona a continuación. Sin embargo, en esta etapa no me queda claro cómo usar los números que extraje después de que se aplicó este método.

Obviamente, tengo un montón de ubicaciones de memoria, sin embargo, ¿cómo puedo sacar conclusiones sobre qué línea de código causó el problema de acuerdo con estas ubicaciones?

Por cierto, la pila de llamadas no ha sido útil y solo tiene una que apunta a los puntos de interrupción actuales en elHardFault_handlerC()

Gracias de antemano por tu ayuda,

/**
 * HardFault_HandlerAsm:
 * Alternative Hard Fault handler to help debug the reason for a fault.
 * To use, edit the vector table to reference this function in the HardFault vector
 * This code is suitable for Cortex-M3 and Cortex-M0 cores
 */

// Use the 'naked' attribute so that C stacking is not used.
__attribute__((naked))
void HardFault_HandlerAsm(void){
        /*
         * Get the appropriate stack pointer, depending on our mode,
         * and use it as the parameter to the C handler. This function
         * will never return
         */

        __asm(  ".syntax unified\n"
                        "MOVS   R0, #4  \n"
                        "MOV    R1, LR  \n"
                        "TST    R0, R1  \n"
                        "BEQ    _MSP    \n"
                        "MRS    R0, PSP \n"
                        "B      HardFault_HandlerC      \n"
                "_MSP:  \n"
                        "MRS    R0, MSP \n"
                        "B      HardFault_HandlerC      \n"
                ".syntax divided\n") ;
}

/**
 * HardFaultHandler_C:
 * This is called from the HardFault_HandlerAsm with a pointer the Fault stack
 * as the parameter. We can then read the values from the stack and place them
 * into local variables for ease of reading.
 * We then read the various Fault Status and Address Registers to help decode
 * cause of the fault.
 * The function ends with a BKPT instruction to force control back into the debugger
 */
void HardFault_HandlerC(unsigned long *hardfault_args){
        volatile unsigned long stacked_r0 ;
        volatile unsigned long stacked_r1 ;
        volatile unsigned long stacked_r2 ;
        volatile unsigned long stacked_r3 ;
        volatile unsigned long stacked_r12 ;
        volatile unsigned long stacked_lr ;
        volatile unsigned long stacked_pc ;
        volatile unsigned long stacked_psr ;
        volatile unsigned long _CFSR ;
        volatile unsigned long _HFSR ;
        volatile unsigned long _DFSR ;
        volatile unsigned long _AFSR ;
        volatile unsigned long _BFAR ;
        volatile unsigned long _MMAR ;

        stacked_r0 = ((unsigned long)hardfault_args[0]) ;
        stacked_r1 = ((unsigned long)hardfault_args[1]) ;
        stacked_r2 = ((unsigned long)hardfault_args[2]) ;
        stacked_r3 = ((unsigned long)hardfault_args[3]) ;
        stacked_r12 = ((unsigned long)hardfault_args[4]) ;
        stacked_lr = ((unsigned long)hardfault_args[5]) ;
        stacked_pc = ((unsigned long)hardfault_args[6]) ;
        stacked_psr = ((unsigned long)hardfault_args[7]) ;

        // Configurable Fault Status Register
        // Consists of MMSR, BFSR and UFSR
        _CFSR = (*((volatile unsigned long *)(0xE000ED28))) ;   
                                                                                        
        // Hard Fault Status Register
        _HFSR = (*((volatile unsigned long *)(0xE000ED2C))) ;

        // Debug Fault Status Register
        _DFSR = (*((volatile unsigned long *)(0xE000ED30))) ;

        // Auxiliary Fault Status Register
        _AFSR = (*((volatile unsigned long *)(0xE000ED3C))) ;

        // Read the Fault Address Registers. These may not contain valid values.
        // Check BFARVALID/MMARVALID to see if they are valid values
        // MemManage Fault Address Register
        _MMAR = (*((volatile unsigned long *)(0xE000ED34))) ;
        // Bus Fault Address Register
        _BFAR = (*((volatile unsigned long *)(0xE000ED38))) ;

        __asm("BKPT #0\n") ; // Break into the debugger

}

Respuestas (3)

Entonces, aquí está la parte divertida: puede ser imposible citar exactamente qué línea está generando la falla. La razón es que un error en su código puede estar causando que aparezca una falla en otro lugar -o- el error podría estar destruyendo toda la información de estado en el sistema, lo cual es genial. Sin embargo, lo que realmente ayudaría es ver toda su base de código: incluidos los scripts del enlazador y el código de inicio.

Sin embargo, en general, si termina en un territorio de fallas graves, estas son las primeras cosas que revisaría:

  • Errores causados ​​al intentar asignar memoria dinámicamente cuando no hay un montón definido por su enlazador. Lo que sucede aquí es que alguna función está llamando a malloc (o uno de sus primos) y la biblioteca está fallando porque no hay suficiente espacio en el montón para asignar memoria, por lo que bloquea el programa. Esta es una posibilidad real para usted, está utilizando un RTOS y la mayoría de los scripts de enlace de vainilla no tienen espacio de almacenamiento dinámico asignado. Vea esto: https://stackoverflow.com/questions/10467244/using-newlibs-malloc-in-an-arm-cortex-m3

  • Fallos causados ​​por hacer algo tonto como escribir datos más allá del final de una matriz. Esto puede ser realmente fácil de hacer si está usando matemáticas para generar índices de matriz o usando punteros a elementos directamente. Lo que (puede) suceder aquí es que si sus verificaciones de límites tienen errores, cuando escribe datos en su matriz, ¡de hecho, puede estar sobrescribiendo todo! Si esto no causa un error directamente (por ejemplo, escribir en una ubicación protegida o de solo lectura), puede romper su pila. Luego salta a una ubicación de basura y probablemente ejecuta una instrucción no válida y luego falla.

También echaría un vistazo a este documento, que está relacionado con su publicación Code Red. Aunque las instrucciones son para ARM Cortex-M3 y ARM Cortex-M4, el método de interpretación de los resultados es el mismo.
Depuración de errores graves y otras excepciones

Uso de los valores de registro

El primer registro de interés es el contador de programa. En el código anterior, la variable pc contiene el valor del contador del programa. Cuando la falla es una falla precisa, la PC mantiene la dirección de la instrucción que se estaba ejecutando cuando ocurrió la falla dura (u otra falla). Cuando la falla es imprecisa, se requieren pasos adicionales para encontrar la dirección de la instrucción que causó la falla.

Para encontrar la instrucción en la dirección contenida en la variable pc, ya sea...

  1. Abra una ventana de código de ensamblaje en el depurador e ingrese manualmente la dirección para ver las instrucciones de ensamblaje en esa dirección, o

  2. Abra la ventana del punto de interrupción en el depurador y defina manualmente un punto de interrupción de acceso o ejecución en esa dirección. Con el punto de interrupción establecido, reinicie la aplicación para ver a qué línea de código se refiere la instrucción.

Conocer la instrucción que se estaba ejecutando cuando ocurrió la falla le permite saber qué otros valores de registro también son de interés. Por ejemplo, si la instrucción estaba usando el valor de R7 como una dirección, entonces se necesita saber el valor de R7. Además, al examinar el código ensamblador y el código C que generó el código ensamblador, se mostrará lo que R7 contiene realmente (podría ser el valor de una variable, por ejemplo).

Esas son solo mis dos razones principales. Si publica su base de código completa, probablemente podamos brindarle una ayuda más directa. ¡Buena suerte!

Gracias por la edición. Lo único que destacaría aquí es que los errores ocurren con frecuencia en un lugar y aparecen en otro. Esto es cierto para los dos errores iniciales que sugerí anteriormente. Una vez que esté seguro de que su pila está intacta, es posible rastrear su ejecución utilizando la lista de ensamblaje, aunque eso es mucho trabajo. ¡De todos modos, buena suerte!

El libro de Joseph Yiu sobre MCU ARM, The Definitive Guide to the ARM Cortex-M0 , proporciona una forma estándar de registrar el contexto justo antes de la falla grave.

Muchas veces se deben a operar en un periférico sin haberle sincronizado el reloj.

Esto también suena bastante interesante. ¿Te importaría desarrollar un poco más tu respuesta, por favor? Se agradecería un par de viñetas sobre los puntos que menciona, especialmente su método para captar el contexto antes de la falla grave.
básicamente tratando de averiguar qué línea en la pila provocó la falla y luego imprimir las distintas direcciones; parece que está haciendo algo similar. tal vez un tonto de los registros relacionados con SCB ayudaría más. Una vez que tenga la dirección, busque el archivo de lista para averiguar qué sucedió.

Si está siguiendo Depuración de fallas graves y otras excepciones publicadas por FreeRTOS y usando Atmel Studio , para poder realizar el siguiente paso,

  1. Abra una ventana de código de ensamblado en el depurador e ingrese manualmente la dirección para ver las instrucciones de ensamblado en esa dirección.

podría usar la vista Desmontaje para saltar a la línea a la que apuntaba el Contador de programa (PC), o de hecho cualquiera de las otras direcciones, en el momento en que ocurrió la falla grave.

En Atmel Studio 7 se puede acceder desde,

Depurar→Windows→Desensamblar o Ctrl+ Alt+ Ddurante una sesión de depuración.