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 115 # The CFSR can accumulate multiple exceptions. 116 def split_major_cause(cause): 117 return cause if not cause else cause + ', ' 118 119 if self._cpu_state.HasField('cfsr') and self.is_fault_active(): 120 if ( 121 self._cpu_state.cfsr 122 & cortex_m_constants.PW_CORTEX_M_CFSR_USAGE_FAULT_MASK 123 ): 124 cause += 'usage fault' 125 126 if ( 127 self._cpu_state.cfsr 128 & cortex_m_constants.PW_CORTEX_M_CFSR_MEM_FAULT_MASK 129 ): 130 cause = split_major_cause(cause) 131 cause += 'memory management fault' 132 if ( 133 self._cpu_state.cfsr 134 & cortex_m_constants.PW_CORTEX_M_CFSR_MMARVALID_MASK 135 ): 136 addr = ( 137 '???' 138 if not self._cpu_state.HasField('mmfar') 139 else f'0x{self._cpu_state.mmfar:08x}' 140 ) 141 cause += f' at {addr}' 142 143 if ( 144 self._cpu_state.cfsr 145 & cortex_m_constants.PW_CORTEX_M_CFSR_BUS_FAULT_MASK 146 ): 147 cause = split_major_cause(cause) 148 if ( 149 self._cpu_state.cfsr 150 & cortex_m_constants.PW_CORTEX_M_CFSR_IMPRECISERR_MASK 151 ): 152 cause += 'imprecise ' 153 cause += 'bus fault' 154 if ( 155 self._cpu_state.cfsr 156 & cortex_m_constants.PW_CORTEX_M_CFSR_BFARVALID_MASK 157 ): 158 addr = ( 159 '???' 160 if not self._cpu_state.HasField('bfar') 161 else f'0x{self._cpu_state.bfar:08x}' 162 ) 163 cause += f' at {addr}' 164 if show_active_cfsr_fields: 165 for field in self.active_cfsr_fields(): 166 cause += f' [{field.name}]' 167 168 return cause if cause else 'unknown exception' 169 170 def dump_registers(self) -> str: 171 """Dumps all captured CPU registers as a multi-line string.""" 172 registers = [] 173 for field in self._cpu_state.DESCRIPTOR.fields: 174 if self._cpu_state.HasField(field.name): 175 register_value = getattr(self._cpu_state, field.name) 176 register_str = f'{field.name:<10} 0x{register_value:08x}' 177 if ( 178 self._symbolizer is not None 179 and field.name in _SYMBOLIZED_REGISTERS 180 ): 181 symbol = self._symbolizer.symbolize(register_value) 182 if symbol.name: 183 register_str += f' {symbol}' 184 registers.append(register_str) 185 return '\n'.join(registers) 186 187 def dump_active_active_cfsr_fields(self) -> str: 188 """Dumps CFSR flags with their descriptions as a multi-line string.""" 189 fields = [] 190 for field in self.active_cfsr_fields(): 191 fields.append(f'{field.name:<11} {field.description}') 192 if isinstance(field.long_description, tuple): 193 long_desc = ' {}'.format( 194 '\n '.join(field.long_description) 195 ) 196 fields.append(long_desc) 197 return '\n'.join(fields) 198 199 def __str__(self): 200 dump = [f'Exception caused by a {self.exception_cause(False)}.', ''] 201 if self.active_cfsr_fields(): 202 dump.extend( 203 ( 204 'Active Crash Fault Status Register (CFSR) fields:', 205 self.dump_active_active_cfsr_fields(), 206 '', 207 ) 208 ) 209 else: 210 dump.extend( 211 ( 212 'No active Crash Fault Status Register (CFSR) fields.', 213 '', 214 ) 215 ) 216 dump.extend( 217 ( 218 'All registers:', 219 self.dump_registers(), 220 ) 221 ) 222 return '\n'.join(dump) 223 224 225def process_snapshot( 226 serialized_snapshot: bytes, 227 symbolizer: pw_symbolizer.Symbolizer | None = None, 228) -> str: 229 """Returns the stringified result of a SnapshotCpuStateOverlay message run 230 though a CortexMExceptionAnalyzer. 231 """ 232 snapshot = cpu_state_pb2.SnapshotCpuStateOverlay() 233 snapshot.ParseFromString(serialized_snapshot) 234 235 if snapshot.HasField('armv7m_cpu_state'): 236 state_analyzer = CortexMExceptionAnalyzer( 237 snapshot.armv7m_cpu_state, symbolizer 238 ) 239 return f'{state_analyzer}\n' 240 241 return '' 242