• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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