pw_cpu_exception_cortex_m: Symbolize PC and LR
Updates the exception analyzer to support symbolization of the PC and LR
registers to improve exception debugability.
Change-Id: I7baa9b1029801082d0f019234186740326488dfb
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/71741
Pigweed-Auto-Submit: Armando Montanez <amontanez@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
diff --git a/pw_cpu_exception_cortex_m/docs.rst b/pw_cpu_exception_cortex_m/docs.rst
index b1972a6..a744a77 100644
--- a/pw_cpu_exception_cortex_m/docs.rst
+++ b/pw_cpu_exception_cortex_m/docs.rst
@@ -192,4 +192,44 @@
================
This module's included Python exception analyzer tooling provides snapshot
integration via a ``process_snapshot()`` function that produces a multi-line
-dump from a serialized snapshot proto.
+dump from a serialized snapshot proto, for example:
+
+.. code-block::
+
+ Exception caused by a usage fault.
+
+ Active Crash Fault Status Register (CFSR) fields:
+ UNDEFINSTR Undefined Instruction UsageFault.
+ The processor has attempted to execute an undefined
+ instruction. When this bit is set to 1, the PC value stacked
+ for the exception return points to the undefined instruction.
+ An undefined instruction is an instruction that the processor
+ cannot decode.
+
+ All registers:
+ pc 0x0800e1c4 example::Service::Crash(_example_service_CrashRequest const&, _pw_protobuf_Empty&) (src/example_service/service.cc:131)
+ lr 0x0800e141 example::Service::Crash(_example_service_CrashRequest const&, _pw_protobuf_Empty&) (src/example_service/service.cc:128)
+ psr 0x81000000
+ msp 0x20040fd8
+ psp 0x20001488
+ exc_return 0xffffffed
+ cfsr 0x00010000
+ mmfar 0xe000ed34
+ bfar 0xe000ed38
+ icsr 0x00000803
+ hfsr 0x40000000
+ shcsr 0x00000000
+ control 0x00000000
+ r0 0xe03f7847
+ r1 0x714083dc
+ r2 0x0b36dc49
+ r3 0x7fbfbe1a
+ r4 0xc36e8efb
+ r5 0x69a14b13
+ r6 0x0ec35eaa
+ r7 0xa5df5543
+ r8 0xc892b931
+ r9 0xa2372c94
+ r10 0xbd15c968
+ r11 0x759b95ab
+ r12 0x00000000
diff --git a/pw_cpu_exception_cortex_m/py/BUILD.gn b/pw_cpu_exception_cortex_m/py/BUILD.gn
index f840c7b..9de835e 100644
--- a/pw_cpu_exception_cortex_m/py/BUILD.gn
+++ b/pw_cpu_exception_cortex_m/py/BUILD.gn
@@ -32,6 +32,7 @@
python_deps = [
"$dir_pw_cli/py",
"$dir_pw_protobuf_compiler/py",
+ "$dir_pw_symbolizer/py",
"..:cpu_state_protos.python",
]
pylintrc = "$dir_pigweed/.pylintrc"
diff --git a/pw_cpu_exception_cortex_m/py/exception_analyzer_test.py b/pw_cpu_exception_cortex_m/py/exception_analyzer_test.py
index cae1f26..e12ee6b 100644
--- a/pw_cpu_exception_cortex_m/py/exception_analyzer_test.py
+++ b/pw_cpu_exception_cortex_m/py/exception_analyzer_test.py
@@ -17,6 +17,7 @@
import unittest
from pw_cpu_exception_cortex_m import exception_analyzer, cortex_m_constants
from pw_cpu_exception_cortex_m_protos import cpu_state_pb2
+import pw_symbolizer
# pylint: disable=protected-access
@@ -163,6 +164,25 @@
))
self.assertEqual(cpu_state_info.dump_registers(), expected_dump)
+ def test_symbolization(self):
+ """Ensure certain registers are symbolized."""
+ cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
+ known_symbols = (
+ pw_symbolizer.Symbol(0x0800A200, 'foo()', 'src/foo.c', 41),
+ pw_symbolizer.Symbol(0x08000004, 'boot_entry()',
+ 'src/vector_table.c', 5),
+ )
+ symbolizer = pw_symbolizer.FakeSymbolizer(known_symbols)
+ cpu_state_proto.pc = 0x0800A200
+ cpu_state_proto.lr = 0x08000004
+ cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
+ cpu_state_proto, symbolizer)
+ expected_dump = '\n'.join((
+ 'pc 0x0800a200 foo() (src/foo.c:41)',
+ 'lr 0x08000004 boot_entry() (src/vector_table.c:5)',
+ ))
+ self.assertEqual(cpu_state_info.dump_registers(), expected_dump)
+
def test_dump_no_cfsr(self):
"""Validate basic CPU state dump."""
cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
diff --git a/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/exception_analyzer.py b/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/exception_analyzer.py
index 1fe3f9e..a6cb5cb 100644
--- a/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/exception_analyzer.py
+++ b/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/exception_analyzer.py
@@ -13,17 +13,27 @@
# the License.
"""Tools to analyze Cortex-M CPU state context captured during an exception."""
-from typing import Tuple
+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):
+ def __init__(self,
+ cpu_state,
+ symbolizer: Optional[pw_symbolizer.Symbolizer] = None):
self._cpu_state = cpu_state
- self._active_cfsr_fields = None
+ 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."""
@@ -115,11 +125,16 @@
def dump_registers(self) -> str:
"""Dumps all captured CPU registers as a multi-line string."""
registers = []
- # TODO(amontanez): Do fancier decode of some registers like PC and LR.
for field in self._cpu_state.DESCRIPTOR.fields:
if self._cpu_state.HasField(field.name):
register_value = getattr(self._cpu_state, field.name)
- registers.append(f'{field.name:<10} 0x{register_value:08x}')
+ 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:
@@ -153,7 +168,9 @@
return '\n'.join(dump)
-def process_snapshot(serialized_snapshot: bytes) -> str:
+def process_snapshot(
+ serialized_snapshot: bytes,
+ symbolizer: Optional[pw_symbolizer.Symbolizer] = None) -> str:
"""Returns the stringified result of a SnapshotCpuState message run though
a CortexMExceptionAnalyzer.
"""
@@ -161,6 +178,8 @@
snapshot.ParseFromString(serialized_snapshot)
if snapshot.HasField('armv7m_cpu_state'):
- return f'{CortexMExceptionAnalyzer(snapshot.armv7m_cpu_state)}\n'
+ state_analyzer = CortexMExceptionAnalyzer(snapshot.armv7m_cpu_state,
+ symbolizer)
+ return f'{state_analyzer}\n'
return ''
diff --git a/pw_snapshot/py/pw_snapshot/processor.py b/pw_snapshot/py/pw_snapshot/processor.py
index 3375bed..dbcaf4a 100644
--- a/pw_snapshot/py/pw_snapshot/processor.py
+++ b/pw_snapshot/py/pw_snapshot/processor.py
@@ -74,7 +74,7 @@
symbolizer = LlvmSymbolizer()
cortex_m_cpu_state = pw_cpu_exception_cortex_m.process_snapshot(
- serialized_snapshot)
+ serialized_snapshot, symbolizer)
if cortex_m_cpu_state:
output.append(cortex_m_cpu_state)