blob: 82500a135de694c3e870a3c97c89a2dc636aaf50 [file] [log] [blame]
# Copyright 2020 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Tools to analyze Cortex-M CPU state context captured during an exception."""
from typing import Optional, Tuple
from pw_cpu_exception_cortex_m import cortex_m_constants
from pw_cpu_exception_cortex_m_protos import cpu_state_pb2
import pw_symbolizer
# These registers are symbolized when dumped.
_SYMBOLIZED_REGISTERS = (
'pc',
'lr',
'bfar',
'mmfar',
'msp',
'psp',
'r0',
'r1',
'r2',
'r3',
'r4',
'r5',
'r6',
'r7',
'r8',
'r9',
'r10',
'r11',
'r12',
)
class CortexMExceptionAnalyzer:
"""This class provides helper functions to dump a ArmV7mCpuState proto."""
def __init__(
self, cpu_state, symbolizer: Optional[pw_symbolizer.Symbolizer] = None
):
self._cpu_state = cpu_state
self._symbolizer = symbolizer
self._active_cfsr_fields: Optional[
Tuple[cortex_m_constants.BitField, ...]
] = None
def active_cfsr_fields(self) -> Tuple[cortex_m_constants.BitField, ...]:
"""Returns a list of BitFields for each active CFSR flag."""
if self._active_cfsr_fields is not None:
return self._active_cfsr_fields
temp_field_list = []
if self._cpu_state.HasField('cfsr'):
for bit_field in cortex_m_constants.PW_CORTEX_M_CFSR_BIT_FIELDS:
if self._cpu_state.cfsr & bit_field.bit_mask:
temp_field_list.append(bit_field)
self._active_cfsr_fields = tuple(temp_field_list)
return self._active_cfsr_fields
def is_fault_active(self) -> bool:
"""Returns true if the current CPU state indicates a fault is active."""
if self._cpu_state.HasField('cfsr') and self._cpu_state.cfsr != 0:
return True
if self._cpu_state.HasField('icsr'):
exception_number = (
self._cpu_state.icsr
& cortex_m_constants.PW_CORTEX_M_ICSR_VECTACTIVE_MASK
)
if (
cortex_m_constants.PW_CORTEX_M_HARD_FAULT_ISR_NUM
<= exception_number
<= cortex_m_constants.PW_CORTEX_M_USAGE_FAULT_ISR_NUM
):
return True
return False
def is_nested_fault(self) -> bool:
"""Returns true if the current CPU state indicates a nested fault."""
if not self.is_fault_active():
return False
if (
self._cpu_state.HasField('hfsr')
and self._cpu_state.hfsr
& cortex_m_constants.PW_CORTEX_M_HFSR_FORCED_MASK
):
return True
return False
def exception_cause(self, show_active_cfsr_fields=True) -> str:
"""Analyzes CPU state to tries and classify the exception.
Examples:
show_active_cfsr_fields=False
unknown exception
memory management fault at 0x00000000
usage fault, imprecise bus fault
show_active_cfsr_fields=True
usage fault [DIVBYZERO]
memory management fault at 0x00000000 [DACCVIOL] [MMARVALID]
"""
cause = ''
# The CFSR can accumulate multiple exceptions.
split_major_cause = lambda cause: cause if not cause else cause + ', '
if self._cpu_state.HasField('cfsr') and self.is_fault_active():
if (
self._cpu_state.cfsr
& cortex_m_constants.PW_CORTEX_M_CFSR_USAGE_FAULT_MASK
):
cause += 'usage fault'
if (
self._cpu_state.cfsr
& cortex_m_constants.PW_CORTEX_M_CFSR_MEM_FAULT_MASK
):
cause = split_major_cause(cause)
cause += 'memory management fault'
if (
self._cpu_state.cfsr
& cortex_m_constants.PW_CORTEX_M_CFSR_MMARVALID_MASK
):
addr = (
'???'
if not self._cpu_state.HasField('mmfar')
else f'0x{self._cpu_state.mmfar:08x}'
)
cause += f' at {addr}'
if (
self._cpu_state.cfsr
& cortex_m_constants.PW_CORTEX_M_CFSR_BUS_FAULT_MASK
):
cause = split_major_cause(cause)
if (
self._cpu_state.cfsr
& cortex_m_constants.PW_CORTEX_M_CFSR_IMPRECISERR_MASK
):
cause += 'imprecise '
cause += 'bus fault'
if (
self._cpu_state.cfsr
& cortex_m_constants.PW_CORTEX_M_CFSR_BFARVALID_MASK
):
addr = (
'???'
if not self._cpu_state.HasField('bfar')
else f'0x{self._cpu_state.bfar:08x}'
)
cause += f' at {addr}'
if show_active_cfsr_fields:
for field in self.active_cfsr_fields():
cause += f' [{field.name}]'
return cause if cause else 'unknown exception'
def dump_registers(self) -> str:
"""Dumps all captured CPU registers as a multi-line string."""
registers = []
for field in self._cpu_state.DESCRIPTOR.fields:
if self._cpu_state.HasField(field.name):
register_value = getattr(self._cpu_state, field.name)
register_str = f'{field.name:<10} 0x{register_value:08x}'
if (
self._symbolizer is not None
and field.name in _SYMBOLIZED_REGISTERS
):
symbol = self._symbolizer.symbolize(register_value)
if symbol.name:
register_str += f' {symbol}'
registers.append(register_str)
return '\n'.join(registers)
def dump_active_active_cfsr_fields(self) -> str:
"""Dumps CFSR flags with their descriptions as a multi-line string."""
fields = []
for field in self.active_cfsr_fields():
fields.append(f'{field.name:<11} {field.description}')
if isinstance(field.long_description, tuple):
long_desc = ' {}'.format(
'\n '.join(field.long_description)
)
fields.append(long_desc)
return '\n'.join(fields)
def __str__(self):
dump = [f'Exception caused by a {self.exception_cause(False)}.', '']
if self.active_cfsr_fields():
dump.extend(
(
'Active Crash Fault Status Register (CFSR) fields:',
self.dump_active_active_cfsr_fields(),
'',
)
)
else:
dump.extend(
(
'No active Crash Fault Status Register (CFSR) fields.',
'',
)
)
dump.extend(
(
'All registers:',
self.dump_registers(),
)
)
return '\n'.join(dump)
def process_snapshot(
serialized_snapshot: bytes,
symbolizer: Optional[pw_symbolizer.Symbolizer] = None,
) -> str:
"""Returns the stringified result of a SnapshotCpuStateOverlay message run
though a CortexMExceptionAnalyzer.
"""
snapshot = cpu_state_pb2.SnapshotCpuStateOverlay()
snapshot.ParseFromString(serialized_snapshot)
if snapshot.HasField('armv7m_cpu_state'):
state_analyzer = CortexMExceptionAnalyzer(
snapshot.armv7m_cpu_state, symbolizer
)
return f'{state_analyzer}\n'
return ''