Analyzing Cortex-M Faults with Ozone
On the Cortex-M architecture, Ozone helps users to quickly analyze and find software bugs that cause CPU faults. This article explains how to employ Ozone's basic and advanced debugging capabilites in order to gain a profound insight into these types of errors.
Fault Analysis Workflow
We start off by demonstrating Ozone's fault analysis workflow on the basis of an example application.
The example application can generate different types of Cortex-M faults and can be downloaded here.
In the illustration below, the example application was downloaded to a SEGGER Cortex-M trace reference board. The debug probe in use is a J-Trace PRO V2. Program execution was advanced to function
_NoThumbFunc and the PC is on an interworking branch to address 0. Since there is a Thumb instruction at address 0, resuming program execution will lead to a Cortex-M CPU fault. Lets's resume program execution and see how Ozone handles the fault.
Target Exception Dialog
Immediately after hitting GO, program execution breaks and Ozone's target exception dialog pops up:
The target exception dialog shows a description of the CPU fault and its system register context. In this example, SHCSR register field USGFAULTACT indicates that a Cortex-M UsageFault exception has occured. Register UFSR provides the specific type of UsageFault exception that has occurred; in this example, the INVSTATE bit is set which indicates that an instruction has been executed in an invalid CPU state. Field USGFAULTENA indicates that the UsageFault handler is enabled. If this was not the case, the exception would have been escalated to a HardFault. As can be recognized, the interpretation of the exception register context is highly architecture-specific. Therefore, Ozone aims to display as much processed information as possible within the exception description. The target exception dialog is shown whenever program execution halts and the target is in an exception state.
When a debug session is started, Ozone sets vector catches on all Cortex-M fault vectors.
This causes program execution to immediately break when a fault handler is entered.
This also enables Ozone to bring up the target exception dialog immediately upon fault entry.
Individual vector catches can be set or cleared using Ozone's Break & Tracepoints window, which is illustrated above.
Ozone also provides command
Break.SetVectorCatch to edit the target's vector catch state programatically.
This command can e.g. be used to modify Ozone's default vector catch initialization behavior on debug session start.
The command should be employed from project script function
Continuing with our workflow example, lets now employ Ozone's debug windows to gain further insight into the fault.
The Call Stack Window indicates that the target is in UsageFault exception state. The window also indicates that the fault originated within function
Let's select the call frame of
_NoThumbFunc within the Call Stack Window. When a call frame is selected within the Call Stack Window, Ozone's call frame aware debug windows will switch output to the execution context of the selected frame (see the illustration above).
The Local Data Window indicates that an interworking branch to address 0 was performed within function
This caused the CPU to execute a Thumb instruction in ARM state. Looking into Ozone's Registers Window, register UFSR confirms that the CPU has issued an INVSTATE UsageFault.
This concludes the overview of Ozone's basic fault analysis workflow.
Section Analyzing Faults with Trace Support will demonstrate how Ozone's trace windows can be employed to determine the cause of more complex faults, in particular imprecise faults.
Ozone can provide accurate information about nested exceptions and nested faults on Cortex-M. To demonstate this, let's continue debugging the example application of the previous section. We now overwrite the single branch instruction of the UsageFault handler with an undefined instruction:
After stepping over the undefined instruction at address 0x8000384, Ozone's target exception dialog pops up again as expected:
This time, the dialog informs about the Cortex-M HardFault exception which has preempted the UsageFault exception. In addition to register field INVSTATE, field UNDEFINSTR is now also set. This indicates that both types of UsageFault exceptions have now occurred within the call path. HFSR register field FORCED furthermore indicates that the UNDEFINSTR exception has been escalated to a HardFault.
As the Cortex-M CPU is now handling a nested exception, Ozone's Call Stack Window has been updated accordingly:
Nested Exceptions on Multiple Stacks
Information about nested exceptions can occupy both Cortex-M CPU stacks. As the next example demonstrates, Ozone is capable to provide accurate call stack and local data information in this situation. In this example, program execution has been halted at call stack level 5 on a supervisor call instruction:
Following execution of the supervisor call, an additional function call path has been executed in handler mode. The application now halts on a load instruction which is about to load from invalid address 0x10000:
Also of interrest at this point is that Ozone's Call Stack Window indicates that a stack switch has occurred. From the position of stack usage value 0 within table column Stack Used, users can see that:
- Call frames above and including
SVC_Handlerare on the main stack.
- Call frames below and including the
SVCall Exceptionare on the process stack.
- 40 bytes of main stack and 88 bytes of process stack are currently in use.
Note that Ozone encloses target-specific call frames with angle brackets. Let's now resume program execution to see how Ozone handles the imminent fault. Immediately after program execution is resumed, the target exception dialog pops up to indicate a HardFault exception:
The Cortex-M core has escalated the BusFault exception to a HardFault, as the BusFault exception handler was not enabled. Register field PRECISERR indicates that a precise BusFault exception has occured. Field BFARVALID indicates that the memory access address of the faulting load/store instruction is available. Ozone integrates all of this information into the user-friendly exception description shown in the top area of the target exception dialog. Field SVCALLACT furthermore indicates that a supervisor event handler has been preempted for the current exception.
Let's close the target exception dialog to further investigate the fault using Ozone's debug windows.
As can be seen in the illustration above, Ozone's call-frame-aware debug windows provide a clear picture of the fault: The Local Data Window displays the invalid value of the dereferenced data pointer. The Call Stack Window displays the complete program execution path, traced over multiple nested exceptions and CPU stacks. The Registers Window can be queried for the same information that was already provided by Ozone's target exception dialog.
Analyzing Faults with Trace Support
In imprecise fault scenarios, where the Cortex-M core cannot provide the exact PC of the faulting instruction, Ozone's trace windows can be employed to quickly identify the faulting instruction.
As illustrated in the example above, an imprecise BusFault exception can be easily traced back to the faulting store instruction using Ozone's Backtrace Highlighting feature.