pw_cpu_exception: Snapshot processor integration

Updates pw_cpu_exception to provide integration with the pw_snapshot
processor.

Change-Id: Ib995a102edf954a98428d5642068427d4c38bb49
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/56382
Pigweed-Auto-Submit: Armando Montanez <amontanez@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Ewout van Bekkum <ewout@google.com>
diff --git a/pw_cpu_exception_cortex_m/docs.rst b/pw_cpu_exception_cortex_m/docs.rst
index 2a023a8..26999c9 100644
--- a/pw_cpu_exception_cortex_m/docs.rst
+++ b/pw_cpu_exception_cortex_m/docs.rst
@@ -184,3 +184,9 @@
   ``pw_cpu_exception_DefaultHandler()`` instead of using the current running
   context to capture the main stack to minimize how much of the snapshot
   handling is captured in the stack.
+
+Python processor
+================
+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.
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 78ec218..cae1f26 100644
--- a/pw_cpu_exception_cortex_m/py/exception_analyzer_test.py
+++ b/pw_cpu_exception_cortex_m/py/exception_analyzer_test.py
@@ -15,19 +15,8 @@
 """Tests dumped Cortex-M CPU state."""
 
 import unittest
-import os
-
-from pw_protobuf_compiler import python_protos
-from pw_cli import env
 from pw_cpu_exception_cortex_m import exception_analyzer, cortex_m_constants
-
-CPU_STATE_PROTO_PATH = os.path.join(
-    env.pigweed_environment().PW_ROOT,  #pylint: disable=no-member
-    'pw_cpu_exception_cortex_m',
-    'pw_cpu_exception_cortex_m_protos',
-    'cpu_state.proto')
-
-cpu_state_pb2 = python_protos.compile_and_import_file(CPU_STATE_PROTO_PATH)
+from pw_cpu_exception_cortex_m_protos import cpu_state_pb2
 
 # pylint: disable=protected-access
 
diff --git a/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/__init__.py b/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/__init__.py
index e69de29..99a4f92 100644
--- a/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/__init__.py
+++ b/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2021 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.
+"""Python tooling for Cortex-M CPU state analysis."""
+from pw_cpu_exception_cortex_m.exception_analyzer import (
+    CortexMExceptionAnalyzer, process_snapshot)
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 2810b64..1fe3f9e 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
@@ -16,6 +16,7 @@
 from typing import Tuple
 
 from pw_cpu_exception_cortex_m import cortex_m_constants
+from pw_cpu_exception_cortex_m_protos import cpu_state_pb2
 
 
 class CortexMExceptionAnalyzer:
@@ -150,3 +151,16 @@
             self.dump_registers(),
         ))
         return '\n'.join(dump)
+
+
+def process_snapshot(serialized_snapshot: bytes) -> str:
+    """Returns the stringified result of a SnapshotCpuState message run though
+    a CortexMExceptionAnalyzer.
+    """
+    snapshot = cpu_state_pb2.SnapshotCpuState()
+    snapshot.ParseFromString(serialized_snapshot)
+
+    if snapshot.HasField('armv7m_cpu_state'):
+        return f'{CortexMExceptionAnalyzer(snapshot.armv7m_cpu_state)}\n'
+
+    return ''
diff --git a/pw_snapshot/py/BUILD.gn b/pw_snapshot/py/BUILD.gn
index 6c77b54..7b7b1da 100644
--- a/pw_snapshot/py/BUILD.gn
+++ b/pw_snapshot/py/BUILD.gn
@@ -48,6 +48,7 @@
   tests = [ "metadata_test.py" ]
   python_deps = [
     ":pw_snapshot_metadata",
+    "$dir_pw_cpu_exception_cortex_m/py",
     "$dir_pw_symbolizer/py",
     "$dir_pw_thread:protos.python",
     "$dir_pw_thread/py",
diff --git a/pw_snapshot/py/pw_snapshot/processor.py b/pw_snapshot/py/pw_snapshot/processor.py
index 1fcf352..2cd67ed 100644
--- a/pw_snapshot/py/pw_snapshot/processor.py
+++ b/pw_snapshot/py/pw_snapshot/processor.py
@@ -18,6 +18,7 @@
 from pathlib import Path
 from typing import Optional, BinaryIO, TextIO, Callable
 import pw_tokenizer
+import pw_cpu_exception_cortex_m
 from pw_snapshot_metadata import metadata
 from pw_snapshot_protos import snapshot_pb2
 from pw_symbolizer import LlvmSymbolizer
@@ -63,6 +64,11 @@
     else:
         symbolizer = LlvmSymbolizer()
 
+    cortex_m_cpu_state = pw_cpu_exception_cortex_m.process_snapshot(
+        serialized_snapshot)
+    if cortex_m_cpu_state:
+        output.append(cortex_m_cpu_state)
+
     thread_info = thread_analyzer.process_snapshot(serialized_snapshot,
                                                    detokenizer, symbolizer)
     if thread_info: