blob: 89ae3f18b93efb513d5ab14c9b2b7f33fd826dc7 [file] [log] [blame]
// Copyright 2024 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_cpu_exception_cortex_m/crash.h"
#include <cinttypes>
#include "pw_cpu_exception_cortex_m/cpu_state.h"
#include "pw_cpu_exception_cortex_m/util.h"
#include "pw_cpu_exception_cortex_m_private/config.h"
#include "pw_cpu_exception_cortex_m_private/cortex_m_constants.h"
#include "pw_preprocessor/arch.h"
namespace pw::cpu_exception::cortex_m {
void AnalyzeCpuStateAndCrash(const pw_cpu_exception_State& cpu_state,
const char* optional_thread_name) {
const char* thread_name =
optional_thread_name == nullptr ? "?" : optional_thread_name;
const bool is_nested_fault = cpu_state.extended.hfsr & kHfsrForcedMask;
const uint32_t active_faults =
cpu_state.extended.cfsr &
(kCfsrMemAllErrorsMask | kCfsrBusAllErrorsMask | kCfsrUsageAllErrorsMask);
// The expression (n & (n - 1)) is 0 if n is a power of 2, i.e. n has
// only 1 bit (fault flag) set. So if it is != 0 there are multiple faults.
const bool has_multiple_faults = (active_faults & (active_faults - 1)) != 0;
// This provides a high-level assessment of the cause of the exception.
// These conditionals are ordered by priority to ensure the most critical
// issues are highlighted first. These are not mutually exclusive; a bus fault
// could occur during the handling of a MPU violation, causing a nested fault.
#if _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
if (cpu_state.extended.cfsr & kCfsrStkofMask) {
if (ProcessStackActive(cpu_state)) {
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"PSP stack overflow. Thread=%s PC=0x%08" PRIx32 " LR=0x%08" PRIx32
" CFSR=0x%08" PRIx32 " PSP=0x%08" PRIx32 " Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
cpu_state.extended.psp,
is_nested_fault,
has_multiple_faults);
} else {
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"MSP stack overflow. Thread=%s PC=0x%08" PRIx32 " LR=0x%08" PRIx32
" CFSR=0x%08" PRIx32 " MSP=0x%08" PRIx32 " Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
cpu_state.extended.msp,
is_nested_fault,
has_multiple_faults);
}
return;
}
#endif // _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
// Memory management fault.
if (cpu_state.extended.cfsr & kCfsrMemFaultMask) {
const bool is_mmfar_valid = cpu_state.extended.cfsr & kCfsrMmarvalidMask;
#if PW_CPU_EXCEPTION_CORTEX_M_CRASH_EXTENDED_CPU_ANALYSIS
if (cpu_state.extended.cfsr & kCfsrIaccviolMask) {
// The PC value stacked for the exception return points to the faulting
// instruction.
// The processor does not write the fault address to the MMFAR.
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"IACCVIOL: MPU violation on instruction fetch. Thread=%s "
"PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
" Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrDaccviolMask) {
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"DACCVIOL: MPU violation on memory read/write. Thread=%s "
"PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
" ValidMmfar=%d MMFAR=0x%08" PRIx32 "Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_mmfar_valid,
cpu_state.extended.mmfar,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrMunstkerrMask) {
// The processor does not write the fault address to the MMFAR.
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"MUNSTKERR: MPU violation on exception return. Thread=%s "
"PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
" Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrMstkerrMask) {
// The processor does not write the fault address to the MMFAR.
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"MSTKERR: MPU violation on exception entry. Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrMlsperrMask) {
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"MLSPERR: MPU violation on lazy FPU state preservation. Thread=%s "
"PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
" ValidMmfar=%d MMFAR=0x%08" PRIx32 " Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_mmfar_valid,
cpu_state.extended.mmfar,
is_nested_fault,
has_multiple_faults);
return;
}
#endif // PW_CPU_EXCEPTION_CORTEX_M_CRASH_EXTENDED_CPU_ANALYSIS
PW_CPU_EXCEPTION_CORTEX_M_CRASH(cpu_state,
"MPU fault. Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
" ValidMmfar=%d MMFAR=0x%08" PRIx32
" Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_mmfar_valid,
cpu_state.extended.mmfar,
is_nested_fault,
has_multiple_faults);
return;
}
// Bus fault.
if (cpu_state.extended.cfsr & kCfsrBusFaultMask) {
const bool is_bfar_valid = cpu_state.extended.cfsr & kCfsrBfarvalidMask;
#if PW_CPU_EXCEPTION_CORTEX_M_CRASH_EXTENDED_CPU_ANALYSIS
if (cpu_state.extended.cfsr & kCfsrIbuserrMask) {
// The processor does not write the fault address to the BFAR.
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"IBUSERR: Bus fault on instruction fetch. Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrPreciserrMask) {
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"PRECISERR: Precise bus fault. Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
" ValidBfar=%d BFAR=0x%08" PRIx32 " Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_bfar_valid,
cpu_state.extended.bfar,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrImpreciserrMask) {
// The processor does not write the fault address to the BFAR.
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"IMPRECISERR: Imprecise bus fault. Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrUnstkerrMask) {
// The processor does not write the fault address to the BFAR.
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"UNSTKERR: Derived bus fault on exception context save. Thread=%s "
"PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
" Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrStkerrMask) {
// The processor does not write the fault address to the BFAR.
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"STKERR: Derived bus fault on exception context restore. Thread=%s "
"PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
" Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrLsperrMask) {
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"LSPERR: Derived bus fault on lazy FPU state preservation. Thread=%s "
"PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
" ValidBfar=%d BFAR=0x%08" PRIx32 " Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_bfar_valid,
cpu_state.extended.bfar,
is_nested_fault,
has_multiple_faults);
return;
}
#endif // PW_CPU_EXCEPTION_CORTEX_M_CRASH_EXTENDED_CPU_ANALYSIS
PW_CPU_EXCEPTION_CORTEX_M_CRASH(cpu_state,
"Bus Fault. Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
" ValidBfar=%d BFAR=0x%08" PRIx32
" Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_bfar_valid,
cpu_state.extended.bfar,
is_nested_fault,
has_multiple_faults);
return;
}
// Usage fault.
if (cpu_state.extended.cfsr & kCfsrUsageFaultMask) {
#if PW_CPU_EXCEPTION_CORTEX_M_CRASH_EXTENDED_CPU_ANALYSIS
if (cpu_state.extended.cfsr & kCfsrUndefinstrMask) {
PW_CPU_EXCEPTION_CORTEX_M_CRASH(cpu_state,
"UNDEFINSTR: Encountered invalid "
"instruction. Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
" Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrInvstateMask) {
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"INVSTATE: Attempted instruction with invalid EPSR value. Thread=%s "
"PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
" Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrInvpcMask) {
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"INVPC: Invalid program counter. Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrNocpMask) {
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"NOCP: Coprocessor disabled or not present. Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrUnalignedMask) {
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"UNALIGNED: Unaligned memory access. Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
if (cpu_state.extended.cfsr & kCfsrDivbyzeroMask) {
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"DIVBYZERO: Division by zero. Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
#endif // PW_CPU_EXCEPTION_CORTEX_M_CRASH_EXTENDED_CPU_ANALYSIS
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"Fault=Usage Nested=%d Multiple=%d Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32,
is_nested_fault,
has_multiple_faults,
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr);
return;
}
if ((cpu_state.extended.icsr & kIcsrVectactiveMask) == kNmiIsrNum) {
PW_CPU_EXCEPTION_CORTEX_M_CRASH(
cpu_state,
"Non-Maskable Interrupt triggered. Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
PW_CPU_EXCEPTION_CORTEX_M_CRASH(cpu_state,
"Unknown fault. Thread=%s PC=0x%08" PRIx32
" LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
" Nested=%d Multiple=%d",
thread_name,
cpu_state.base.pc,
cpu_state.base.lr,
cpu_state.extended.cfsr,
is_nested_fault,
has_multiple_faults);
return;
}
} // namespace pw::cpu_exception::cortex_m