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