• 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 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