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

  1. 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.

  2. 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.

  3. Conditional Move Instructions: The ITE EQ (If-Then-Else) instruction is used to conditionally execute the following instructions based on the result of the TST instruction. If the exception was taken using the MSP, the MRSEQ R0, MSP instruction moves the MSP to R0. If the exception was taken using the PSP, the MRSNE R0, PSP instruction moves the PSP to R0. This ensures that the correct stack pointer is used when extracting the stack frame.

  4. Branch with Link: The BL Pmd_Fault_Handler instruction is used to call the Pmd_Fault_Handler function. 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 is crucial for preserving the EXC_RETURN value, which is needed for the exception return.

  5. 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 and ITE 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.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *