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