SPM: Add error inspection for fatal errors (exceptions)
SecureFault, HardFault, BusFault, MemManage fault, UsageFault.
Store relevant information to memory and print it with DEBUG log level.
Make all error handlers "naked" so the MSP and PSP can be retrieved
correctly.
Change-Id: I3c14343afcbe190f3182d6d35a6e456556db62b8
Signed-off-by: Øyvind Rønningstad <oyvind.ronningstad@nordicsemi.no>
diff --git a/config/config_default.cmake b/config/config_default.cmake
index 8a3d25c..e3b0f19 100644
--- a/config/config_default.cmake
+++ b/config/config_default.cmake
@@ -41,6 +41,8 @@
set(TFM_PXN_ENABLE OFF CACHE BOOL "Use Privileged execute never (PXN)")
+set(TFM_EXCEPTION_INFO_DUMP OFF CACHE BOOL "On fatal errors in the secure firmware, capture info about the exception. Print the info if the SPM log level is sufficient.")
+
########################## BL2 #################################################
set(MCUBOOT_IMAGE_NUMBER 2 CACHE STRING "Whether to combine S and NS into either 1 image, or sign each seperately")
diff --git a/secure_fw/spm/CMakeLists.txt b/secure_fw/spm/CMakeLists.txt
index 28b02ce..67363d0 100755
--- a/secure_fw/spm/CMakeLists.txt
+++ b/secure_fw/spm/CMakeLists.txt
@@ -34,6 +34,7 @@
ffm/tfm_boot_data.c
ffm/tfm_core_utils.c
ffm/utilities.c
+ $<$<BOOL:${TFM_EXCEPTION_INFO_DUMP}>:cmsis_psa/exception_info.c>
$<$<NOT:$<STREQUAL:${TFM_SPM_LOG_LEVEL},TFM_SPM_LOG_LEVEL_SILENCE>>:ffm/spm_log.c>
$<$<BOOL:${TFM_MULTI_CORE_TOPOLOGY}>:cmsis_psa/tfm_multi_core.c>
$<$<BOOL:${TFM_MULTI_CORE_TOPOLOGY}>:cmsis_psa/tfm_multi_core_mem_check.c>
@@ -93,6 +94,7 @@
PRIVATE
$<$<CONFIG:Debug>:TFM_CORE_DEBUG>
$<$<AND:$<BOOL:${BL2}>,$<BOOL:${MCUBOOT_MEASURED_BOOT}>>:BOOT_DATA_AVAILABLE>
+ $<$<BOOL:${TFM_EXCEPTION_INFO_DUMP}>:TFM_EXCEPTION_INFO_DUMP>
)
# With constant optimizations on tfm_nspc_func emits a symbol that the linker
diff --git a/secure_fw/spm/cmsis_func/arch.c b/secure_fw/spm/cmsis_func/arch.c
index 5fbc507..0ec79d8 100644
--- a/secure_fw/spm/cmsis_func/arch.c
+++ b/secure_fw/spm/cmsis_func/arch.c
@@ -6,6 +6,7 @@
*/
#include "arch.h"
+#include "exception_info.h"
#include "tfm_secure_api.h"
#include "tfm/tfm_spm_services.h"
@@ -357,6 +358,8 @@
__attribute__((naked)) void HardFault_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_HARDFAULT);
+
/* A HardFault may indicate corruption of secure state, so it is essential
* that Non-secure code does not regain control after one is raised.
* Returning from this exception could allow a pending NS exception to be
@@ -367,6 +370,8 @@
__attribute__((naked)) void MemManage_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_MEMFAULT);
+
/* A MemManage fault may indicate corruption of secure state, so it is
* essential that Non-secure code does not regain control after one is
* raised. Returning from this exception could allow a pending NS exception
@@ -377,6 +382,8 @@
__attribute__((naked)) void BusFault_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_BUSFAULT);
+
/* A BusFault may indicate corruption of secure state, so it is essential
* that Non-secure code does not regain control after one is raised.
* Returning from this exception could allow a pending NS exception to be
@@ -387,6 +394,8 @@
__attribute__((naked)) void SecureFault_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_SECUREFAULT);
+
/* A SecureFault may indicate corruption of secure state, so it is essential
* that Non-secure code does not regain control after one is raised.
* Returning from this exception could allow a pending NS exception to be
@@ -394,3 +403,9 @@
*/
__ASM volatile("b .");
}
+
+__attribute__((naked)) void UsageFault_Handler(void)
+{
+ EXCEPTION_INFO(EXCEPTION_TYPE_USAGEFAULT);
+ __ASM volatile("b .");
+}
diff --git a/secure_fw/spm/cmsis_psa/arch/tfm_arch_v6m_v7m.c b/secure_fw/spm/cmsis_psa/arch/tfm_arch_v6m_v7m.c
index b5c6843..090e401 100644
--- a/secure_fw/spm/cmsis_psa/arch/tfm_arch_v6m_v7m.c
+++ b/secure_fw/spm/cmsis_psa/arch/tfm_arch_v6m_v7m.c
@@ -8,6 +8,7 @@
#include <inttypes.h>
#include "tfm_hal_device_header.h"
#include "tfm_arch.h"
+#include "exception_info.h"
#if !defined(__ARM_ARCH_6M__) && !defined(__ARM_ARCH_7M__) && \
!defined(__ARM_ARCH_7EM__)
@@ -106,12 +107,14 @@
#if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__)
void HardFault_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_HARDFAULT);
/* HFSR can be read to provide further information of cause of HardFault */
__ASM volatile("b .");
}
#elif defined(__ARM_ARCH_6M__)
void HardFault_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_HARDFAULT);
/* In a baseline implementation there is no way, to find out whether this is
* a hard fault triggered directly, or another fault that has been
* escalated.
@@ -123,16 +126,19 @@
/* Reserved for future usage */
__attribute__((naked)) void MemManage_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_MEMFAULT);
__ASM volatile("b .");
}
__attribute__((naked)) void BusFault_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_BUSFAULT);
__ASM volatile("b .");
}
__attribute__((naked)) void UsageFault_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_USAGEFAULT);
__ASM volatile("b .");
}
diff --git a/secure_fw/spm/cmsis_psa/arch/tfm_arch_v8m_base.c b/secure_fw/spm/cmsis_psa/arch/tfm_arch_v8m_base.c
index 5367a0a..61a8cd7 100644
--- a/secure_fw/spm/cmsis_psa/arch/tfm_arch_v8m_base.c
+++ b/secure_fw/spm/cmsis_psa/arch/tfm_arch_v8m_base.c
@@ -9,6 +9,7 @@
#include "spm_ipc.h"
#include "tfm_hal_device_header.h"
#include "tfm_arch.h"
+#include "exception_info.h"
#include "tfm_secure_api.h"
#include "tfm/tfm_core_svc.h"
@@ -91,8 +92,10 @@
* In case of a baseline implementation fault conditions that would generate a
* SecureFault in a mainline implementation instead generate a Secure HardFault.
*/
-void HardFault_Handler(void)
+__attribute__((naked)) void HardFault_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_HARDFAULT);
+
/* In a baseline implementation there is no way, to find out whether this is
* a hard fault triggered directly, or another fault that has been
* escalated.
@@ -102,9 +105,8 @@
* Returning from this exception could allow a pending NS exception to be
* taken, so the current solution is not to return.
*/
- while (1) {
- ;
- }
+
+ __ASM volatile("b .");
}
#if defined(__ICCARM__)
diff --git a/secure_fw/spm/cmsis_psa/arch/tfm_arch_v8m_main.c b/secure_fw/spm/cmsis_psa/arch/tfm_arch_v8m_main.c
index ea07673..57fd9a2 100644
--- a/secure_fw/spm/cmsis_psa/arch/tfm_arch_v8m_main.c
+++ b/secure_fw/spm/cmsis_psa/arch/tfm_arch_v8m_main.c
@@ -11,6 +11,7 @@
#include "tfm_arch.h"
#include "tfm_memory_utils.h"
#include "tfm_core_utils.h"
+#include "exception_info.h"
#include "tfm_secure_api.h"
#include "spm_ipc.h"
#include "tfm/tfm_core_svc.h"
@@ -80,17 +81,16 @@
/**
* \brief Overwrites default Secure fault handler.
*/
-void SecureFault_Handler(void)
+__attribute__((naked)) void SecureFault_Handler(void)
{
- ERROR_MSG("Oops... Secure fault!!! You're not going anywhere!");
+ EXCEPTION_INFO(EXCEPTION_TYPE_SECUREFAULT);
+
/* A SecureFault may indicate corruption of secure state, so it is essential
* that Non-secure code does not regain control after one is raised.
* Returning from this exception could allow a pending NS exception to be
* taken, so the current solution is not to return.
*/
- while (1) {
- ;
- }
+ __ASM volatile("b .");
}
#if defined(__ICCARM__)
@@ -112,6 +112,8 @@
/* Reserved for future usage */
__attribute__((naked)) void HardFault_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_HARDFAULT);
+
/* A HardFault may indicate corruption of secure state, so it is essential
* that Non-secure code does not regain control after one is raised.
* Returning from this exception could allow a pending NS exception to be
@@ -122,6 +124,8 @@
__attribute__((naked)) void MemManage_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_MEMFAULT);
+
/* A MemManage fault may indicate corruption of secure state, so it is
* essential that Non-secure code does not regain control after one is
* raised. Returning from this exception could allow a pending NS exception
@@ -132,6 +136,8 @@
__attribute__((naked)) void BusFault_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_BUSFAULT);
+
/* A BusFault may indicate corruption of secure state, so it is essential
* that Non-secure code does not regain control after one is raised.
* Returning from this exception could allow a pending NS exception to be
@@ -142,6 +148,7 @@
__attribute__((naked)) void UsageFault_Handler(void)
{
+ EXCEPTION_INFO(EXCEPTION_TYPE_USAGEFAULT);
__ASM volatile("b .");
}
diff --git a/secure_fw/spm/cmsis_psa/exception_info.c b/secure_fw/spm/cmsis_psa/exception_info.c
new file mode 100644
index 0000000..a240fc8
--- /dev/null
+++ b/secure_fw/spm/cmsis_psa/exception_info.c
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2021, Nordic Semiconductor ASA. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <string.h>
+#include "tfm_arch.h"
+#include "exception_info.h"
+#include "tfm_spm_log.h"
+#include "tfm_core_utils.h"
+
+struct exception_info_t {
+ uint32_t EXC_RETURN; /* EXC_RETURN value in LR. */
+ uint32_t MSP; /* (Secure) MSP. */
+ uint32_t PSP; /* (Secure) PSP. */
+ uint32_t *EXC_FRAME; /* Exception frame on stack. */
+ uint32_t EXC_FRAME_COPY[8]; /* Copy of the basic exception frame. */
+ uint32_t xPSR; /* Program Status Registers. */
+
+#ifdef FAULT_STATUS_PRESENT
+ uint32_t CFSR; /* Configurable Fault Status Register. */
+ uint32_t HFSR; /* Hard Fault Status Register. */
+ uint32_t BFAR; /* Bus Fault address register. */
+ uint32_t BFARVALID; /* Whether BFAR contains a valid address. */
+ uint32_t MMFAR; /* MemManage Fault address register. */
+ uint32_t MMARVALID; /* Whether MMFAR contains a valid address. */
+#ifdef TRUSTZONE_PRESENT
+ uint32_t SFSR; /* SecureFault Status Register. */
+ uint32_t SFAR; /* SecureFault Address Register. */
+ uint32_t SFARVALID; /* Whether SFAR contains a valid address. */
+#endif
+
+#endif
+};
+
+static struct exception_info_t exception_info;
+
+/**
+ * \brief Check whether the exception was triggered in thread or handler mode.
+ *
+ * \param[in] lr LR register containing the EXC_RETURN value.
+ *
+ * \retval true The exception will return to thread mode.
+ */
+__STATIC_INLINE bool is_return_thread_mode(uint32_t lr)
+{
+#if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__)
+ return !((lr == EXC_RETURN_HANDLER) || (lr == EXC_RETURN_HANDLER_FPU));
+#elif defined(__ARM_ARCH_8M_BASE__) || defined(__ARM_ARCH_8M_MAIN__) \
+ || defined(__ARM_ARCH_8_1M_MAIN__)
+ return (lr & EXC_RETURN_MODE);
+#else
+ return !(lr == EXC_RETURN_HANDLER);
+#endif
+}
+
+/**
+ * \brief Check whether the PSP or MSP is used to restore stack frame on
+ * exception return.
+ *
+ * \param[in] lr LR register containing the EXC_RETURN value.
+ *
+ * \retval true The exception frame is on the PSP
+ */
+__STATIC_INLINE bool is_return_psp(uint32_t lr)
+{
+#if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__)
+ return ((lr == EXC_RETURN_THREAD_PSP) || (lr == EXC_RETURN_THREAD_PSP_FPU));
+#elif defined(__ARM_ARCH_8M_BASE__) || defined(__ARM_ARCH_8M_MAIN__) \
+ || defined(__ARM_ARCH_8_1M_MAIN__)
+ /* PSP is used only if SPSEL is set, and we came from thread mode. */
+ return ((lr & EXC_RETURN_SPSEL) && is_return_thread_mode(lr));
+#else
+ return (lr == EXC_RETURN_THREAD_PSP);
+#endif
+}
+
+/**
+ * \brief Get a pointer to the current exception frame
+ *
+ * \param[in] lr LR register containing the EXC_RETURN value.
+ * \param[in] msp The MSP at the start of the exception handler.
+ * \param[in] psp The PSP at the start of the exception handler.
+ *
+ * \return A pointer to the current exception frame.
+ */
+__STATIC_INLINE
+uint32_t *get_exception_frame(uint32_t lr, uint32_t msp, uint32_t psp)
+{
+#if defined(__ARM_ARCH_8M_BASE__) || defined(__ARM_ARCH_8M_MAIN__) \
+ || defined(__ARM_ARCH_8_1M_MAIN__)
+ bool is_psp = is_return_psp(lr);
+
+ return (uint32_t *)(is_return_secure_stack(lr)
+ ? (is_psp ? psp : msp)
+ : (is_psp ? __TZ_get_PSP_NS() : __TZ_get_MSP_NS()));
+#else
+ return (uint32_t *)(is_return_psp(lr) ? psp : msp);
+#endif
+}
+
+static void dump_exception_info_t(bool stack_error,
+ struct exception_info_t *ctx)
+{
+ SPMLOG_DBGMSG("Here is some context for the exception:\n");
+ SPMLOG_DBGMSGVAL(" EXC_RETURN (LR): ", ctx->EXC_RETURN);
+ SPMLOG_DBGMSG(" Exception came from");
+#ifdef TRUSTZONE_PRESENT
+ if (is_return_secure_stack(ctx->EXC_RETURN)) {
+ SPMLOG_DBGMSG(" secure FW in");
+ } else {
+ SPMLOG_DBGMSG(" non-secure FW in");
+ }
+#endif
+
+ if (is_return_thread_mode(ctx->EXC_RETURN)) {
+ SPMLOG_DBGMSG(" thread mode.\n");
+ } else {
+ SPMLOG_DBGMSG(" handler mode.\n");
+ }
+ SPMLOG_DBGMSGVAL(" xPSR: ", ctx->xPSR);
+ SPMLOG_DBGMSGVAL(" MSP: ", ctx->MSP);
+ SPMLOG_DBGMSGVAL(" PSP: ", ctx->PSP);
+#ifdef TRUSTZONE_PRESENT
+ SPMLOG_DBGMSGVAL(" MSP_NS: ", __TZ_get_MSP_NS());
+ SPMLOG_DBGMSGVAL(" PSP_NS: ", __TZ_get_PSP_NS());
+#endif
+
+ SPMLOG_DBGMSGVAL(" Exception frame at: ", (uint32_t)ctx->EXC_FRAME);
+ if (stack_error) {
+ SPMLOG_DBGMSG(
+ " (Note that the exception frame may be corrupted for this type of error.)\n");
+ }
+ SPMLOG_DBGMSGVAL(" R0: ", ctx->EXC_FRAME_COPY[0]);
+ SPMLOG_DBGMSGVAL(" R1: ", ctx->EXC_FRAME_COPY[1]);
+ SPMLOG_DBGMSGVAL(" R2: ", ctx->EXC_FRAME_COPY[2]);
+ SPMLOG_DBGMSGVAL(" R3: ", ctx->EXC_FRAME_COPY[3]);
+ SPMLOG_DBGMSGVAL(" R12: ", ctx->EXC_FRAME_COPY[4]);
+ SPMLOG_DBGMSGVAL(" LR: ", ctx->EXC_FRAME_COPY[5]);
+ SPMLOG_DBGMSGVAL(" PC: ", ctx->EXC_FRAME_COPY[6]);
+ SPMLOG_DBGMSGVAL(" xPSR: ", ctx->EXC_FRAME_COPY[7]);
+
+#ifdef FAULT_STATUS_PRESENT
+ SPMLOG_DBGMSGVAL(" CFSR: ", ctx->CFSR);
+ SPMLOG_DBGMSGVAL(" BFSR: ",
+ (ctx->CFSR & SCB_CFSR_BUSFAULTSR_Msk) >> SCB_CFSR_BUSFAULTSR_Pos);
+ if (ctx->BFARVALID) {
+ SPMLOG_DBGMSGVAL(" BFAR: ", ctx->BFAR);
+ } else {
+ SPMLOG_DBGMSG(" BFAR: Not Valid\n");
+ }
+ SPMLOG_DBGMSGVAL(" MMFSR: ",
+ (ctx->CFSR & SCB_CFSR_MEMFAULTSR_Msk) >> SCB_CFSR_MEMFAULTSR_Pos);
+ if (ctx->MMARVALID) {
+ SPMLOG_DBGMSGVAL(" MMFAR: ", ctx->MMFAR);
+ } else {
+ SPMLOG_DBGMSG(" MMFAR: Not Valid\n");
+ }
+ SPMLOG_DBGMSGVAL(" UFSR: ",
+ (ctx->CFSR & SCB_CFSR_USGFAULTSR_Msk) >> SCB_CFSR_USGFAULTSR_Pos);
+ SPMLOG_DBGMSGVAL(" HFSR: ", ctx->HFSR);
+#ifdef TRUSTZONE_PRESENT
+ SPMLOG_DBGMSGVAL(" SFSR: ", ctx->SFSR);
+ if (ctx->SFARVALID) {
+ SPMLOG_DBGMSGVAL(" SFAR: ", ctx->SFAR);
+ } else {
+ SPMLOG_DBGMSG(" SFAR: Not Valid\n");
+ }
+#endif
+
+#endif
+}
+
+static void dump_error(uint32_t error_type)
+{
+ bool stack_error = false;
+
+ SPMLOG_DBGMSG("FATAL ERROR: ");
+ switch (error_type) {
+ case EXCEPTION_TYPE_SECUREFAULT:
+ SPMLOG_DBGMSG("SecureFault\n");
+ break;
+ case EXCEPTION_TYPE_HARDFAULT:
+ SPMLOG_DBGMSG("HardFault\n");
+ break;
+ case EXCEPTION_TYPE_MEMFAULT:
+ SPMLOG_DBGMSG("MemManage fault\n");
+ stack_error = true;
+ break;
+ case EXCEPTION_TYPE_BUSFAULT:
+ SPMLOG_DBGMSG("BusFault\n");
+ stack_error = true;
+ break;
+ case EXCEPTION_TYPE_USAGEFAULT:
+ SPMLOG_DBGMSG("UsageFault\n");
+ stack_error = true;
+ break;
+ default:
+ SPMLOG_DBGMSG("Unknown\n");
+ break;
+ }
+ dump_exception_info_t(stack_error, &exception_info);
+}
+
+void store_and_dump_context(uint32_t LR_in, uint32_t MSP_in, uint32_t PSP_in,
+ uint32_t exception_type)
+{
+ struct exception_info_t *ctx = &exception_info;
+
+ ctx->xPSR = __get_xPSR();
+ ctx->EXC_RETURN = LR_in;
+ ctx->MSP = MSP_in;
+ ctx->PSP = PSP_in;
+ ctx->EXC_FRAME = get_exception_frame(ctx->EXC_RETURN, ctx->MSP, ctx->PSP);
+ spm_memcpy(ctx->EXC_FRAME_COPY, ctx->EXC_FRAME,
+ sizeof(ctx->EXC_FRAME_COPY));
+
+#ifdef FAULT_STATUS_PRESENT
+ ctx->CFSR = SCB->CFSR;
+ ctx->HFSR = SCB->HFSR;
+ ctx->BFAR = SCB->BFAR;
+ ctx->BFARVALID = ctx->CFSR & SCB_CFSR_BFARVALID_Msk;
+ ctx->MMFAR = SCB->MMFAR;
+ ctx->MMARVALID = ctx->CFSR & SCB_CFSR_MMARVALID_Msk;
+ SCB->CFSR = ctx->CFSR; /* Clear bits. CFSR is write-one-to-clear. */
+ SCB->HFSR = ctx->HFSR; /* Clear bits. HFSR is write-one-to-clear. */
+#ifdef TRUSTZONE_PRESENT
+ ctx->SFSR = SAU->SFSR;
+ ctx->SFAR = SAU->SFAR;
+ ctx->SFARVALID = ctx->SFSR & SAU_SFSR_SFARVALID_Msk;
+ SAU->SFSR = ctx->SFSR; /* Clear bits. SFSR is write-one-to-clear. */
+#endif
+#endif
+
+ dump_error(exception_type);
+}
diff --git a/secure_fw/spm/include/exception_info.h b/secure_fw/spm/include/exception_info.h
new file mode 100644
index 0000000..a272061
--- /dev/null
+++ b/secure_fw/spm/include/exception_info.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2021, Nordic Semiconductor ASA. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef __EXCEPTION_INFO_H__
+#define __EXCEPTION_INFO_H__
+
+#include <stdint.h>
+
+#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
+#define TRUSTZONE_PRESENT
+#endif
+
+#if defined(__ARM_ARCH_8_1M_MAIN__) || defined(__ARM_ARCH_8M_MAIN__) \
+ || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__)
+#define FAULT_STATUS_PRESENT
+#endif
+
+/* Arguments to EXCEPTION_INFO() */
+#define EXCEPTION_TYPE_SECUREFAULT 0
+#define EXCEPTION_TYPE_HARDFAULT 1
+#define EXCEPTION_TYPE_MEMFAULT 2
+#define EXCEPTION_TYPE_BUSFAULT 3
+#define EXCEPTION_TYPE_USAGEFAULT 4
+
+/* This level of indirection is needed to fully resolve exception info when it's
+ * a macro
+ */
+#define _STRINGIFY(exception_info) #exception_info
+
+/* Store context for an exception, and print an error message with the context.
+ *
+ * @param[in] exception_type One of the EXCEPTION_TYPE_* values defined above. Any
+ * other value will result in printing "Unknown".
+ */
+#ifdef TFM_EXCEPTION_INFO_DUMP
+#define EXCEPTION_INFO(exception_type) \
+ __ASM volatile( \
+ "MOV r0, lr\n" \
+ "MRS r1, MSP\n" \
+ "MRS r2, PSP\n" \
+ "MOVS r3, #" _STRINGIFY(exception_type) "\n" \
+ "BL store_and_dump_context\n" \
+ )
+
+/* Store context for an exception, then print the info.
+ * Call EXCEPTION_INFO() instead of calling this directly.
+ */
+void store_and_dump_context(uint32_t LR_in, uint32_t MSP_in, uint32_t PSP_in,
+ uint32_t exception_type);
+#else
+#define EXCEPTION_INFO(exception_type)
+#endif
+
+#endif /* __EXCEPTION_INFO_H__ */