1# Copyright 2020 The Pigweed Authors 2# 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 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 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 13# the License. 14"""Tools to analyze Cortex-M CPU state context captured during an exception.""" 15 16 17from pw_cpu_exception_cortex_m import cortex_m_constants 18from pw_cpu_exception_cortex_m_protos import cpu_state_pb2 19import pw_symbolizer 20 21# These registers are symbolized when dumped. 22_SYMBOLIZED_REGISTERS = ( 23 'pc', 24 'lr', 25 'bfar', 26 'mmfar', 27 'msp', 28 'psp', 29 'r0', 30 'r1', 31 'r2', 32 'r3', 33 'r4', 34 'r5', 35 'r6', 36 'r7', 37 'r8', 38 'r9', 39 'r10', 40 'r11', 41 'r12', 42) 43 44 45class CortexMExceptionAnalyzer: 46 """This class provides helper functions to dump a ArmV7mCpuState proto.""" 47 48 def __init__( 49 self, cpu_state, symbolizer: pw_symbolizer.Symbolizer | None = None 50 ): 51 self._cpu_state = cpu_state 52 self._symbolizer = symbolizer 53 self._active_cfsr_fields: ( 54 tuple[cortex_m_constants.BitField, ...] | None 55 ) = None 56 57 def active_cfsr_fields(self) -> tuple[cortex_m_constants.BitField, ...]: 58 """Returns a list of BitFields for each active CFSR flag.""" 59 60 if self._active_cfsr_fields is not None: 61 return self._active_cfsr_fields 62 63 temp_field_list = [] 64 if self._cpu_state.HasField('cfsr'): 65 for bit_field in cortex_m_constants.PW_CORTEX_M_CFSR_BIT_FIELDS: 66 if self._cpu_state.cfsr & bit_field.bit_mask: 67 temp_field_list.append(bit_field) 68 self._active_cfsr_fields = tuple(temp_field_list) 69 return self._active_cfsr_fields 70 71 def is_fault_active(self) -> bool: 72 """Returns true if the current CPU state indicates a fault is active.""" 73 if self._cpu_state.HasField('cfsr') and self._cpu_state.cfsr != 0: 74 return True 75 if self._cpu_state.HasField('icsr'): 76 exception_number = ( 77 self._cpu_state.icsr 78 & cortex_m_constants.PW_CORTEX_M_ICSR_VECTACTIVE_MASK 79 ) 80 if ( 81 cortex_m_constants.PW_CORTEX_M_HARD_FAULT_ISR_NUM 82 <= exception_number 83 <= cortex_m_constants.PW_CORTEX_M_USAGE_FAULT_ISR_NUM 84 ): 85 return True 86 return False 87 88 def is_nested_fault(self) -> bool: 89 """Returns true if the current CPU state indicates a nested fault.""" 90 if not self.is_fault_active(): 91 return False 92 if ( 93 self._cpu_state.HasField('hfsr') 94 and self._cpu_state.hfsr 95 & cortex_m_constants.PW_CORTEX_M_HFSR_FORCED_MASK 96 ): 97 return True 98 return False 99 100 def exception_cause(self, show_active_cfsr_fields=True) -> str: 101 """Analyzes CPU state to tries and classify the exception. 102 103 Examples: 104 show_active_cfsr_fields=False 105 unknown exception 106 memory management fault at 0x00000000 107 usage fault, imprecise bus fault 108 109 show_active_cfsr_fields=True 110 usage fault [DIVBYZERO] 111 memory management fault at 0x00000000 [DACCVIOL] [MMARVALID] 112 """ 113 cause = '' 114 # The CFSR can accumulate multiple exceptions. 115 split_major_cause = lambda cause: cause if not cause else cause + ', ' 116 117 if self._cpu_state.HasField('cfsr') and self.is_fault_active(): 118 if ( 119 self._cpu_state.cfsr 120 & cortex_m_constants.PW_CORTEX_M_CFSR_USAGE_FAULT_MASK 121 ): 122 cause += 'usage fault' 123 124 if ( 125 self._cpu_state.cfsr 126 & cortex_m_constants.PW_CORTEX_M_CFSR_MEM_FAULT_MASK 127 ): 128 cause = split_major_cause(cause) 129 cause += 'memory management fault' 130 if ( 131 self._cpu_state.cfsr 132 & cortex_m_constants.PW_CORTEX_M_CFSR_MMARVALID_MASK 133 ): 134 addr = ( 135 '???' 136 if not self._cpu_state.HasField('mmfar') 137 else f'0x{self._cpu_state.mmfar:08x}' 138 ) 139 cause += f' at {addr}' 140 141 if ( 142 self._cpu_state.cfsr 143 & cortex_m_constants.PW_CORTEX_M_CFSR_BUS_FAULT_MASK 144 ): 145 cause = split_major_cause(cause) 146 if ( 147 self._cpu_state.cfsr 148 & cortex_m_constants.PW_CORTEX_M_CFSR_IMPRECISERR_MASK 149 ): 150 cause += 'imprecise ' 151 cause += 'bus fault' 152 if ( 153 self._cpu_state.cfsr 154 & cortex_m_constants.PW_CORTEX_M_CFSR_BFARVALID_MASK 155 ): 156 addr = ( 157 '???' 158 if not self._cpu_state.HasField('bfar') 159 else f'0x{self._cpu_state.bfar:08x}' 160 ) 161 cause += f' at {addr}' 162 if show_active_cfsr_fields: 163 for field in self.active_cfsr_fields(): 164 cause += f' [{field.name}]' 165 166 return cause if cause else 'unknown exception' 167 168 def dump_registers(self) -> str: 169 """Dumps all captured CPU registers as a multi-line string.""" 170 registers = [] 171 for field in self._cpu_state.DESCRIPTOR.fields: 172 if self._cpu_state.HasField(field.name): 173 register_value = getattr(self._cpu_state, field.name) 174 register_str = f'{field.name:<10} 0x{register_value:08x}' 175 if ( 176 self._symbolizer is not None 177 and field.name in _SYMBOLIZED_REGISTERS 178 ): 179 symbol = self._symbolizer.symbolize(register_value) 180 if symbol.name: 181 register_str += f' {symbol}' 182 registers.append(register_str) 183 return '\n'.join(registers) 184 185 def dump_active_active_cfsr_fields(self) -> str: 186 """Dumps CFSR flags with their descriptions as a multi-line string.""" 187 fields = [] 188 for field in self.active_cfsr_fields(): 189 fields.append(f'{field.name:<11} {field.description}') 190 if isinstance(field.long_description, tuple): 191 long_desc = ' {}'.format( 192 '\n '.join(field.long_description) 193 ) 194 fields.append(long_desc) 195 return '\n'.join(fields) 196 197 def __str__(self): 198 dump = [f'Exception caused by a {self.exception_cause(False)}.', ''] 199 if self.active_cfsr_fields(): 200 dump.extend( 201 ( 202 'Active Crash Fault Status Register (CFSR) fields:', 203 self.dump_active_active_cfsr_fields(), 204 '', 205 ) 206 ) 207 else: 208 dump.extend( 209 ( 210 'No active Crash Fault Status Register (CFSR) fields.', 211 '', 212 ) 213 ) 214 dump.extend( 215 ( 216 'All registers:', 217 self.dump_registers(), 218 ) 219 ) 220 return '\n'.join(dump) 221 222 223def process_snapshot( 224 serialized_snapshot: bytes, 225 symbolizer: pw_symbolizer.Symbolizer | None = None, 226) -> str: 227 """Returns the stringified result of a SnapshotCpuStateOverlay message run 228 though a CortexMExceptionAnalyzer. 229 """ 230 snapshot = cpu_state_pb2.SnapshotCpuStateOverlay() 231 snapshot.ParseFromString(serialized_snapshot) 232 233 if snapshot.HasField('armv7m_cpu_state'): 234 state_analyzer = CortexMExceptionAnalyzer( 235 snapshot.armv7m_cpu_state, symbolizer 236 ) 237 return f'{state_analyzer}\n' 238 239 return '' 240