Finden der Quelle eines schweren Fehlers mit erweitertem HardFault_Handler

Ich habe einige schwerwiegende Fehler in der Firmware festgestellt, die ich mit FreeRTOS auf einer SAMD21 (ARM Cortex-M0) MCU erstellt habe.

Also habe ich weitere Schritte unternommen, um die Ursache herauszufinden, und bin schließlich auf diesen Artikel auf Code_Red gestoßen , der auf den unten erwähnten Ausschnitt hinweist. In diesem Stadium ist mir jedoch nicht klar, wie ich die Zahlen verwenden soll, die ich extrahiert habe, nachdem diese Methode angewendet wurde.

Offensichtlich habe ich eine Reihe von Speicherorten, aber wie kann ich Rückschlüsse darauf ziehen, welche Codezeile das Problem gemäß diesen Orten verursacht hat?

Übrigens, der Aufrufstapel war nicht nützlich und hat nur einen einzigen darin, der auf die aktuellen Haltepunkte in der zeigtHardFault_handlerC()

Vielen Dank im Voraus für Ihre Hilfe,

/**
 * 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

}

Antworten (3)

Also, hier ist der lustige Teil: Es kann unmöglich sein, genau zu zitieren, welche Linie den Fehler auslöst. Der Grund dafür ist, dass ein Fehler in Ihrem Code dazu führen kann, dass an anderer Stelle ein Fehler auftritt – oder der Fehler alle Statusinformationen im System zerstört, was super cool ist. Was jedoch wirklich helfen würde, ist, Ihre gesamte Codebasis zu sehen: einschließlich der Linker-Skripte und des Startcodes.

Im Allgemeinen jedoch, wenn Sie in einem Hard-Fault-Territorium landen, sind hier die ersten Dinge, die ich überprüfen würde:

  • Fehler, die durch den Versuch verursacht werden, Speicher dynamisch zuzuweisen, wenn von Ihrem Linker kein Heap definiert ist. Was hier passiert, ist, dass eine Funktion malloc (oder einen ihrer Cousins) aufruft und die Bibliothek fehlschlägt, weil auf dem Heap nicht genügend Speicherplatz vorhanden ist, um Speicher zuzuweisen, sodass das Programm abstürzt. Dies ist eine echte Möglichkeit für Sie, Sie verwenden ein RTOS und die meisten Vanilla-Linker-Skripte haben keinen zugewiesenen Heap-Speicherplatz. Siehe dazu: https://stackoverflow.com/questions/10467244/using-newlibs-malloc-in-an-arm-cortex-m3

  • Fehler, die durch etwas Dummes wie das Schreiben von Daten über das Ende eines Arrays hinaus verursacht werden. Dies kann sehr einfach sein, wenn Sie Mathematik zum Generieren von Array-Indizes verwenden oder direkt Zeiger auf Elemente verwenden. Was hier passieren (kann) ist, wenn Ihre Begrenzungsprüfungen fehlerhaft sind, wenn Sie Daten in Ihr Array schreiben, überschreiben Sie möglicherweise tatsächlich alles! Wenn dies nicht direkt zu einem Fehler führt (z. B. Schreiben an einen schreibgeschützten oder geschützten Speicherort), kann es Ihren Stapel beschädigen. Dann springen Sie zu einer Müllstelle und führen wahrscheinlich eine ungültige Anweisung und dann einen Fehler aus.

Ich würde mir auch dieses Dokument ansehen, das sich auf Ihren Code Red-Beitrag bezieht. Obwohl die Anweisungen für ARM Cortex-M3 und ARM Cortex-M4 gelten , ist die Methode zur Interpretation der Ergebnisse dieselbe.
Debuggen von schweren Fehlern und anderen Ausnahmen

Verwendung der Registerwerte

Das erste interessierende Register ist der Programmzähler. Im obigen Code enthält die Variable pc den Programmzählerwert. Wenn der Fehler ein präziser Fehler ist, hält der PC die Adresse der Anweisung, die ausgeführt wurde, als der harte Fehler (oder ein anderer Fehler) auftrat. Wenn der Fehler ein ungenauer Fehler ist, sind zusätzliche Schritte erforderlich, um die Adresse des Befehls zu finden, der den Fehler verursacht hat.

Um die Anweisung an der Adresse zu finden, die in der PC-Variablen enthalten ist, entweder ...

  1. Öffnen Sie im Debugger ein Assemblercodefenster, und geben Sie die Adresse manuell ein, um die Assembleranweisungen unter dieser Adresse anzuzeigen, oder

  2. Öffnen Sie das Haltepunktfenster im Debugger und definieren Sie manuell einen Ausführungs- oder Zugriffshaltepunkt an dieser Adresse. Starten Sie die Anwendung mit gesetztem Unterbrechungspunkt neu, um zu sehen, auf welche Codezeile sich die Anweisung bezieht.

Wenn Sie wissen, welche Anweisung ausgeführt wurde, als der Fehler auftrat, wissen Sie, welche anderen Registerwerte ebenfalls von Interesse sind. Wenn der Befehl beispielsweise den Wert von R7 als Adresse verwendet hat, muss der Wert von R7 bekannt sein. Darüber hinaus zeigt die Untersuchung des Assemblercodes und des C-Codes, der den Assemblercode generiert hat, was R7 tatsächlich enthält (es könnte beispielsweise der Wert einer Variablen sein).

Das sind nur meine Top-Zwei Off-the-Top-Gründe. Wenn Sie Ihre gesamte Codebasis posten, können wir Ihnen wahrscheinlich direkter helfen. Viel Glück!

Danke für die Bearbeitung. Das einzige, was ich hier hervorheben möchte, ist, dass Fehler häufig an einer Stelle auftreten und an einer anderen auftauchen. Dies gilt für beide ersten Fehler, die ich oben vorgeschlagen habe. Sobald Sie sicher sind, dass Ihr Stack intakt ist, ist es tatsächlich möglich, Ihre Ausführung mithilfe des Assembler-Listings zurückzuverfolgen, obwohl dies eine Menge harter Arbeit ist. Wie auch immer - Viel Glück!

Joseph Yius Buch über ARM-MCUs, The Definitive Guide to the ARM Cortex-M0 , bietet eine Standardmethode zum Aufzeichnen des Kontexts kurz vor dem harten Fehler.

Oftmals sind sie darauf zurückzuführen, dass sie an einem Peripheriegerät betrieben werden, ohne dass die Uhr daran angeschlossen ist.

Das klingt auch recht interessant. Würden Sie Ihre Antwort bitte etwas genauer ausführen? Ein paar Aufzählungszeichen zu den Punkten, die er erwähnt, insbesondere seine Methode, den Kontext vor dem schweren Fehler zu erfassen, wären zu schätzen.
Versuchen Sie im Grunde herauszufinden, welche Zeile auf dem Stapel den Fehler ausgelöst hat, und drucken Sie dann die verschiedenen Adressen aus - sieht so aus, als würden Sie etwas Ähnliches tun. Vielleicht würde ein Dummkopf der SCB-bezogenen Register mehr helfen. Sobald Sie die Adresse haben, schlagen Sie in der Listendatei nach, um herauszufinden, was passiert ist.

Wenn Sie dem von FreeRTOS veröffentlichten Debugging Hard Fault & Other Exceptions folgen und Atmel Studio verwenden , um den folgenden Schritt ausführen zu können,

  1. Öffnen Sie im Debugger ein Assemblercodefenster und geben Sie die Adresse manuell ein, um die Assembleranweisungen unter dieser Adresse anzuzeigen.

Sie können die Disassembly-Ansicht verwenden, um zu der Zeile zu springen, auf die der Programmzähler (PC) oder tatsächlich eine andere Adresse zum Zeitpunkt des Auftretens eines schweren Fehlers zeigte.

In Atmel Studio 7 kann darauf zugegriffen werden,

Debug→Windows→Disassembly oder Ctrl+ Alt+ Dwährend einer Debugging-Sitzung.