Cortex-M7 UsageFault Handler Misrouting Due to Incorrect Link Register Management
The core issue revolves around the improper handling of the Link Register (LR) during exception handling in an ARM Cortex-M7 microcontroller. Specifically, the problem manifests when attempting to return from a UsageFault exception triggered by an unaligned memory access. The handler, UsageFault_Handler
, is designed to extract the stack pointer (SP) and call a C routine, Pmd_Fault_Handler
, to print debug information. However, after executing Pmd_Fault_Handler
, the program fails to return to the instruction following the one that caused the exception. Instead, it re-enters the UsageFault_Handler
, creating an infinite loop.
The root of the problem lies in the mismanagement of the LR and the stack during the exception handling process. The LR is critical for exception return in ARM Cortex-M processors, as it contains the EXC_RETURN value that dictates the processor’s behavior upon returning from an exception. In this case, the LR is not being preserved correctly, leading to incorrect exception return behavior.
Improper Use of Branch Instructions and LR Preservation
The primary cause of the issue is the incorrect use of branch instructions and the failure to properly preserve the LR during the exception handling process. The UsageFault_Handler
initially uses a simple branch instruction (B
) to call Pmd_Fault_Handler
, which does not save the return address in the LR. This results in the loss of the EXC_RETURN value, which is essential for correctly returning from the exception.
Additionally, the handler attempts to save and restore the LR manually using a global variable, lr_exc
. However, this approach is flawed because the LR is not being restored to its original state before the exception return. The EXC_RETURN value in the LR is modified during the execution of Pmd_Fault_Handler
, leading to incorrect behavior when the BX LR
instruction is executed.
Another contributing factor is the potential addition of a prolog by the compiler, which can corrupt the stack and the LR. The prolog typically includes instructions like push {r7, lr}
, which can interfere with the exception handling process if the handler is not marked as naked
. A naked
function tells the compiler not to generate prolog or epilog code, which is crucial for exception handlers that need to manage the stack and LR manually.
Correcting Exception Handling with Naked Functions and Proper LR Management
To resolve the issue, the UsageFault_Handler
must be marked as naked
to prevent the compiler from adding a prolog that could corrupt the stack or LR. This ensures that the handler has full control over the stack and LR, which is essential for proper exception handling.
The handler should use the BL
(Branch with Link) instruction instead of B
when calling Pmd_Fault_Handler
. The BL
instruction saves the return address in the LR, allowing the handler to return to the correct location after executing Pmd_Fault_Handler
. This ensures that the EXC_RETURN value is preserved and correctly used for exception return.
Here is the corrected implementation of the UsageFault_Handler
:
__attribute__((naked)) void UsageFault_Handler(void) {
__asm__("TST LR, #4"); // Test bit 2 of EXC_RETURN to determine stack pointer
__asm__("ITE EQ"); // If-Then-Else for MSP or PSP
__asm__("MRSEQ R0, MSP"); // Move MSP to R0 if using MSP
__asm__("MRSNE R0, PSP"); // Move PSP to R0 if using PSP
__asm__("MOV R1, LR"); // Move LR to R1 for later use
__asm__("BL Pmd_Fault_Handler"); // Call Pmd_Fault_Handler with Branch with Link
__asm__("BX LR"); // Return from exception using EXC_RETURN in LR
}
In this corrected version, the BL
instruction ensures that the return address is saved in the LR, allowing the handler to return to the correct location after executing Pmd_Fault_Handler
. The BX LR
instruction then uses the preserved EXC_RETURN value to return from the exception.
Additionally, the naked
attribute ensures that the compiler does not add any prolog or epilog code, which could interfere with the manual management of the stack and LR. This is crucial for maintaining the integrity of the exception handling process.
Detailed Explanation of the Corrected Implementation
-
Naked Function Attribute: The
__attribute__((naked))
attribute is used to indicate that the function is a naked function. This tells the compiler not to generate any prolog or epilog code for the function. In the context of exception handlers, this is essential because the handler needs to manually manage the stack and LR. Any automatic prolog or epilog code generated by the compiler could corrupt the stack or LR, leading to incorrect exception handling. -
Testing EXC_RETURN: The
TST LR, #4
instruction tests bit 2 of the EXC_RETURN value in the LR. This bit indicates whether the exception was taken using the Main Stack Pointer (MSP) or the Process Stack Pointer (PSP). The result of this test is used to determine which stack pointer to use when extracting the stack frame. -
Conditional Move Instructions: The
ITE EQ
(If-Then-Else) instruction is used to conditionally execute the following instructions based on the result of theTST
instruction. If the exception was taken using the MSP, theMRSEQ R0, MSP
instruction moves the MSP to R0. If the exception was taken using the PSP, theMRSNE R0, PSP
instruction moves the PSP to R0. This ensures that the correct stack pointer is used when extracting the stack frame. -
Branch with Link: The
BL Pmd_Fault_Handler
instruction is used to call thePmd_Fault_Handler
function. TheBL
instruction saves the return address in the LR, allowing the handler to return to the correct location after executingPmd_Fault_Handler
. This is crucial for preserving the EXC_RETURN value, which is needed for the exception return. -
Exception Return: The
BX LR
instruction is used to return from the exception. The LR contains the EXC_RETURN value, which dictates the processor’s behavior upon returning from the exception. This ensures that the processor returns to the correct mode and stack pointer, and resumes execution from the instruction following the one that caused the exception.
Summary of Key Points
- Naked Functions: Exception handlers should be marked as
naked
to prevent the compiler from adding prolog or epilog code that could interfere with manual stack and LR management. - Branch with Link: Use the
BL
instruction to call functions within exception handlers to ensure that the return address is saved in the LR. - EXC_RETURN Handling: Properly manage the EXC_RETURN value in the LR to ensure correct exception return behavior.
- Stack Pointer Selection: Use the
TST
andITE
instructions to determine the correct stack pointer (MSP or PSP) based on the EXC_RETURN value.
By following these steps, the UsageFault_Handler
can correctly handle exceptions and return to the instruction following the one that caused the exception, ensuring proper program execution.