1 # Copyright 2020 The Pigweed Authors
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 # use this file except in compliance with the License. You may obtain a copy of
7 # https://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations under
14 """Tools to analyze Cortex-M CPU state context captured during an exception."""
16 from typing import Tuple
18 from pw_cpu_exception_cortex_m import cortex_m_constants
21 class CortexMExceptionAnalyzer:
22 """This class provides helper functions to dump a ArmV7mCpuState proto."""
23 def __init__(self, cpu_state):
24 self._cpu_state = cpu_state
25 self._active_cfsr_fields = None
27 def active_cfsr_fields(self) -> Tuple[cortex_m_constants.BitField, ...]:
28 """Returns a list of BitFields for each active CFSR flag."""
30 if self._active_cfsr_fields is not None:
31 return self._active_cfsr_fields
34 if self._cpu_state.HasField('cfsr'):
35 for bit_field in cortex_m_constants.PW_CORTEX_M_CFSR_BIT_FIELDS:
36 if self._cpu_state.cfsr & bit_field.bit_mask:
37 temp_field_list.append(bit_field)
38 self._active_cfsr_fields = tuple(temp_field_list)
39 return self._active_cfsr_fields
41 def is_fault_active(self) -> bool:
42 """Returns true if the current CPU state indicates a fault is active."""
43 if self._cpu_state.HasField('cfsr') and self._cpu_state.cfsr != 0:
45 if self._cpu_state.HasField('icsr'):
48 & cortex_m_constants.PW_CORTEX_M_ICSR_VECTACTIVE_MASK)
49 if (cortex_m_constants.PW_CORTEX_M_HARD_FAULT_ISR_NUM <=
51 cortex_m_constants.PW_CORTEX_M_USAGE_FAULT_ISR_NUM):
55 def is_nested_fault(self) -> bool:
56 """Returns true if the current CPU state indicates a nested fault."""
57 if not self.is_fault_active():
59 if (self._cpu_state.HasField('hfsr') and self._cpu_state.hfsr
60 & cortex_m_constants.PW_CORTEX_M_HFSR_FORCED_MASK):
64 def exception_cause(self, show_active_cfsr_fields=True) -> str:
65 """Analyzes CPU state to tries and classify the exception.
68 show_active_cfsr_fields=False
70 memory management fault at 0x00000000
71 usage fault, imprecise bus fault
73 show_active_cfsr_fields=True
74 usage fault [DIVBYZERO]
75 memory management fault at 0x00000000 [DACCVIOL] [MMARVALID]
78 # The CFSR can accumulate multiple exceptions.
79 split_major_cause = lambda cause: cause if not cause else cause + ', '
81 if self._cpu_state.HasField('cfsr') and self.is_fault_active():
82 if (self._cpu_state.cfsr
83 & cortex_m_constants.PW_CORTEX_M_CFSR_USAGE_FAULT_MASK):
84 cause += 'usage fault'
86 if (self._cpu_state.cfsr
87 & cortex_m_constants.PW_CORTEX_M_CFSR_MEM_FAULT_MASK):
88 cause = split_major_cause(cause)
89 cause += 'memory management fault'
90 if (self._cpu_state.cfsr
91 & cortex_m_constants.PW_CORTEX_M_CFSR_MMARVALID_MASK):
92 addr = '???' if not self._cpu_state.HasField(
93 'mmfar') else f'0x{self._cpu_state.mmfar:08x}'
94 cause += f' at {addr}'
96 if (self._cpu_state.cfsr
97 & cortex_m_constants.PW_CORTEX_M_CFSR_BUS_FAULT_MASK):
98 cause = split_major_cause(cause)
99 if (self._cpu_state.cfsr &
100 cortex_m_constants.PW_CORTEX_M_CFSR_IMPRECISERR_MASK):
101 cause += 'imprecise '
103 if (self._cpu_state.cfsr
104 & cortex_m_constants.PW_CORTEX_M_CFSR_BFARVALID_MASK):
105 addr = '???' if not self._cpu_state.HasField(
106 'bfar') else f'0x{self._cpu_state.bfar:08x}'
107 cause += f' at {addr}'
108 if show_active_cfsr_fields:
109 for field in self.active_cfsr_fields():
110 cause += f' [{field.name}]'
112 return cause if cause else 'unknown exception'
114 def dump_registers(self) -> str:
115 """Dumps all captured CPU registers as a multi-line string."""
117 # TODO(amontanez): Do fancier decode of some registers like PC and LR.
118 for field in self._cpu_state.DESCRIPTOR.fields:
119 if self._cpu_state.HasField(field.name):
120 register_value = getattr(self._cpu_state, field.name)
121 registers.append(f'{field.name:<10} 0x{register_value:08x}')
122 return '\n'.join(registers)
124 def dump_active_active_cfsr_fields(self) -> str:
125 """Dumps CFSR flags with their descriptions as a multi-line string."""
127 for field in self.active_cfsr_fields():
128 fields.append(f'{field.name:<11} {field.description}')
129 return '\n'.join(fields)
132 dump = [f'Exception caused by a {self.exception_cause(False)}.', '']
133 if self.active_cfsr_fields():
135 'Active Crash Fault Status Register (CFSR) fields:',
136 self.dump_active_active_cfsr_fields(),
141 'No active Crash Fault Status Register (CFSR) fields.',
146 self.dump_registers(),
148 return '\n'.join(dump)