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 Tuple 17 18from pw_cpu_exception_cortex_m import cortex_m_constants 19 20 21class 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 26 27 def active_cfsr_fields(self) -> Tuple[cortex_m_constants.BitField, ...]: 28 """Returns a list of BitFields for each active CFSR flag.""" 29 30 if self._active_cfsr_fields is not None: 31 return self._active_cfsr_fields 32 33 temp_field_list = [] 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 40 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: 44 return True 45 if self._cpu_state.HasField('icsr'): 46 exception_number = ( 47 self._cpu_state.icsr 48 & cortex_m_constants.PW_CORTEX_M_ICSR_VECTACTIVE_MASK) 49 if (cortex_m_constants.PW_CORTEX_M_HARD_FAULT_ISR_NUM <= 50 exception_number <= 51 cortex_m_constants.PW_CORTEX_M_USAGE_FAULT_ISR_NUM): 52 return True 53 return False 54 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(): 58 return False 59 if (self._cpu_state.HasField('hfsr') and self._cpu_state.hfsr 60 & cortex_m_constants.PW_CORTEX_M_HFSR_FORCED_MASK): 61 return True 62 return False 63 64 def exception_cause(self, show_active_cfsr_fields=True) -> str: 65 """Analyzes CPU state to tries and classify the exception. 66 67 Examples: 68 show_active_cfsr_fields=False 69 unknown exception 70 memory management fault at 0x00000000 71 usage fault, imprecise bus fault 72 73 show_active_cfsr_fields=True 74 usage fault [DIVBYZERO] 75 memory management fault at 0x00000000 [DACCVIOL] [MMARVALID] 76 """ 77 cause = '' 78 # The CFSR can accumulate multiple exceptions. 79 split_major_cause = lambda cause: cause if not cause else cause + ', ' 80 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' 85 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}' 95 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 ' 102 cause += 'bus fault' 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}]' 111 112 return cause if cause else 'unknown exception' 113 114 def dump_registers(self) -> str: 115 """Dumps all captured CPU registers as a multi-line string.""" 116 registers = [] 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) 123 124 def dump_active_active_cfsr_fields(self) -> str: 125 """Dumps CFSR flags with their descriptions as a multi-line string.""" 126 fields = [] 127 for field in self.active_cfsr_fields(): 128 fields.append(f'{field.name:<11} {field.description}') 129 return '\n'.join(fields) 130 131 def __str__(self): 132 dump = [f'Exception caused by a {self.exception_cause(False)}.', ''] 133 if self.active_cfsr_fields(): 134 dump.extend(( 135 'Active Crash Fault Status Register (CFSR) fields:', 136 self.dump_active_active_cfsr_fields(), 137 '', 138 )) 139 else: 140 dump.extend(( 141 'No active Crash Fault Status Register (CFSR) fields.', 142 '', 143 )) 144 dump.extend(( 145 'All registers:', 146 self.dump_registers(), 147 )) 148 return '\n'.join(dump) 149