• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright 2020 The Pigweed Authors
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may not
5# use this file except in compliance with the License. You may obtain a copy of
6# the License at
7#
8#     https://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations under
14# the License.
15"""Tests dumped Cortex-M CPU state."""
16
17import unittest
18import os
19
20from pw_protobuf_compiler import python_protos
21from pw_cli import env
22from pw_cpu_exception_cortex_m import exception_analyzer, cortex_m_constants
23
24CPU_STATE_PROTO_PATH = os.path.join(
25    env.pigweed_environment().PW_ROOT,  #pylint: disable=no-member
26    'pw_cpu_exception_cortex_m',
27    'pw_cpu_exception_cortex_m_protos',
28    'cpu_state.proto')
29
30cpu_state_pb2 = python_protos.compile_and_import_file(CPU_STATE_PROTO_PATH)
31
32# pylint: disable=protected-access
33
34
35class BasicFaultTest(unittest.TestCase):
36    """Test basic fault analysis functions."""
37    def test_empty_state(self):
38        """Ensure an empty CPU state proto doesn't indicate an active fault."""
39        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
40        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
41            cpu_state_proto)
42        self.assertFalse(cpu_state_info.is_fault_active())
43
44    def test_cfsr_fault(self):
45        """Ensure a fault is active if CFSR bits are set."""
46        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
47        cpu_state_proto.cfsr = (
48            cortex_m_constants.PW_CORTEX_M_CFSR_STKOF_MASK
49            | cortex_m_constants.PW_CORTEX_M_CFSR_MUNSTKERR_MASK)
50        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
51            cpu_state_proto)
52        self.assertTrue(cpu_state_info.is_fault_active())
53
54    def test_icsr_fault(self):
55        """Ensure a fault is active if ICSR says the handler is active."""
56        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
57        cpu_state_proto.icsr = (
58            cortex_m_constants.PW_CORTEX_M_HARD_FAULT_ISR_NUM)
59        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
60            cpu_state_proto)
61        self.assertTrue(cpu_state_info.is_fault_active())
62
63    def test_cfsr_fields(self):
64        """Ensure correct fields are returned when CFSR bits are set."""
65        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
66        cpu_state_proto.cfsr = (
67            cortex_m_constants.PW_CORTEX_M_CFSR_STKOF_MASK
68            | cortex_m_constants.PW_CORTEX_M_CFSR_MUNSTKERR_MASK)
69        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
70            cpu_state_proto)
71        active_fields = [
72            field.name for field in cpu_state_info.active_cfsr_fields()
73        ]
74        self.assertEqual(len(active_fields), 2)
75        self.assertIn('STKOF', active_fields)
76        self.assertIn('MUNSTKERR', active_fields)
77
78
79class ExceptionCauseTest(unittest.TestCase):
80    """Test exception cause analysis."""
81    def test_empty_cpu_state(self):
82        """Ensure empty CPU state has no known cause."""
83        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
84        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
85            cpu_state_proto)
86        self.assertEqual(cpu_state_info.exception_cause(), 'unknown exception')
87
88    def test_unknown_exception(self):
89        """Ensure CPU state with insufficient info has no known cause."""
90        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
91        # Set CFSR to a valid value.
92        cpu_state_proto.cfsr = 0
93        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
94            cpu_state_proto)
95        self.assertEqual(cpu_state_info.exception_cause(), 'unknown exception')
96
97    def test_single_usage_fault(self):
98        """Ensure usage faults are properly identified."""
99        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
100        cpu_state_proto.cfsr = cortex_m_constants.PW_CORTEX_M_CFSR_STKOF_MASK
101        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
102            cpu_state_proto)
103        self.assertEqual(cpu_state_info.exception_cause(),
104                         'usage fault [STKOF]')
105
106    def test_single_usage_fault_without_fields(self):
107        """Ensure disabling show_active_cfsr_fields hides field names."""
108        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
109        cpu_state_proto.cfsr = cortex_m_constants.PW_CORTEX_M_CFSR_STKOF_MASK
110        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
111            cpu_state_proto)
112        self.assertEqual(cpu_state_info.exception_cause(False), 'usage fault')
113
114    def test_multiple_faults(self):
115        """Ensure multiple CFSR bits are identified and reported."""
116        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
117        cpu_state_proto.cfsr = (
118            cortex_m_constants.PW_CORTEX_M_CFSR_STKOF_MASK
119            | cortex_m_constants.PW_CORTEX_M_CFSR_UNSTKERR_MASK)
120        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
121            cpu_state_proto)
122        self.assertEqual(cpu_state_info.exception_cause(),
123                         'usage fault, bus fault [UNSTKERR] [STKOF]')
124
125    def test_mmfar_missing(self):
126        """Ensure if mmfar is valid but missing it is handled safely."""
127        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
128        cpu_state_proto.cfsr = (
129            cortex_m_constants.PW_CORTEX_M_CFSR_MUNSTKERR_MASK
130            | cortex_m_constants.PW_CORTEX_M_CFSR_MMARVALID_MASK)
131        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
132            cpu_state_proto)
133        self.assertEqual(cpu_state_info.exception_cause(False),
134                         'memory management fault at ???')
135
136    def test_mmfar_valid(self):
137        """Validate output format of valid MMFAR."""
138        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
139        cpu_state_proto.cfsr = (
140            cortex_m_constants.PW_CORTEX_M_CFSR_MUNSTKERR_MASK
141            | cortex_m_constants.PW_CORTEX_M_CFSR_MMARVALID_MASK)
142        cpu_state_proto.mmfar = 0x722470e4
143        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
144            cpu_state_proto)
145        self.assertEqual(cpu_state_info.exception_cause(False),
146                         'memory management fault at 0x722470e4')
147
148    def test_imprecise_bus_fault(self):
149        """Check that imprecise bus faults are identified correctly."""
150        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
151        cpu_state_proto.cfsr = (
152            cortex_m_constants.PW_CORTEX_M_CFSR_IMPRECISERR_MASK
153            | cortex_m_constants.PW_CORTEX_M_CFSR_IBUSERR_MASK)
154        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
155            cpu_state_proto)
156        self.assertEqual(cpu_state_info.exception_cause(False),
157                         'imprecise bus fault')
158
159
160class TextDumpTest(unittest.TestCase):
161    """Test larger state dumps."""
162    def test_registers(self):
163        """Validate output of general register dumps."""
164        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
165        cpu_state_proto.pc = 0xdfadd966
166        cpu_state_proto.mmfar = 0xaf2ea98a
167        cpu_state_proto.r0 = 0xf3b235b1
168        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
169            cpu_state_proto)
170        expected_dump = '\n'.join((
171            'pc         0xdfadd966',
172            'mmfar      0xaf2ea98a',
173            'r0         0xf3b235b1',
174        ))
175        self.assertEqual(cpu_state_info.dump_registers(), expected_dump)
176
177    def test_dump_no_cfsr(self):
178        """Validate basic CPU state dump."""
179        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
180        cpu_state_proto.pc = 0xd2603058
181        cpu_state_proto.mmfar = 0x8e4eb9a2
182        cpu_state_proto.r0 = 0xdb5e7168
183        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
184            cpu_state_proto)
185        expected_dump = '\n'.join((
186            'Exception caused by a unknown exception.',
187            '',
188            'No active Crash Fault Status Register (CFSR) fields.',
189            '',
190            'All registers:',
191            'pc         0xd2603058',
192            'mmfar      0x8e4eb9a2',
193            'r0         0xdb5e7168',
194        ))
195        self.assertEqual(str(cpu_state_info), expected_dump)
196
197    def test_dump_with_cfsr(self):
198        """Validate CPU state dump with CFSR bits set is formatted correctly."""
199        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
200        cpu_state_proto.cfsr = (
201            cortex_m_constants.PW_CORTEX_M_CFSR_PRECISERR_MASK
202            | cortex_m_constants.PW_CORTEX_M_CFSR_BFARVALID_MASK)
203        cpu_state_proto.pc = 0xd2603058
204        cpu_state_proto.bfar = 0xdeadbeef
205        cpu_state_proto.mmfar = 0x8e4eb9a2
206        cpu_state_proto.r0 = 0xdb5e7168
207        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
208            cpu_state_proto)
209        expected_dump = '\n'.join((
210            'Exception caused by a bus fault at 0xdeadbeef.',
211            '',
212            'Active Crash Fault Status Register (CFSR) fields:',
213            'PRECISERR   Precise bus fault.',
214            'BFARVALID   BFAR is valid.',
215            '',
216            'All registers:',
217            'pc         0xd2603058',
218            'cfsr       0x00008200',
219            'mmfar      0x8e4eb9a2',
220            'bfar       0xdeadbeef',
221            'r0         0xdb5e7168',
222        ))
223        self.assertEqual(str(cpu_state_info), expected_dump)
224
225
226if __name__ == '__main__':
227    unittest.main()
228