| #!/usr/bin/env python3 |
| # 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. |
| """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_armv7m', |
| 'pw_cpu_exception_armv7m_protos', |
| 'cpu_state.proto') |
| |
| cpu_state_pb2 = python_protos.compile_and_import_file(CPU_STATE_PROTO_PATH) |
| |
| # pylint: disable=protected-access |
| |
| |
| class BasicFaultTest(unittest.TestCase): |
| """Test basic fault analysis functions.""" |
| def test_empty_state(self): |
| """Ensure an empty CPU state proto doesn't indicate an active fault.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| self.assertFalse(cpu_state_info.is_fault_active()) |
| |
| def test_cfsr_fault(self): |
| """Ensure a fault is active if CFSR bits are set.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| cpu_state_proto.cfsr = ( |
| cortex_m_constants.PW_CORTEX_M_CFSR_STKOF_MASK |
| | cortex_m_constants.PW_CORTEX_M_CFSR_MUNSTKERR_MASK) |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| self.assertTrue(cpu_state_info.is_fault_active()) |
| |
| def test_icsr_fault(self): |
| """Ensure a fault is active if ICSR says the handler is active.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| cpu_state_proto.icsr = ( |
| cortex_m_constants.PW_CORTEX_M_HARD_FAULT_ISR_NUM) |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| self.assertTrue(cpu_state_info.is_fault_active()) |
| |
| def test_cfsr_fields(self): |
| """Ensure correct fields are returned when CFSR bits are set.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| cpu_state_proto.cfsr = ( |
| cortex_m_constants.PW_CORTEX_M_CFSR_STKOF_MASK |
| | cortex_m_constants.PW_CORTEX_M_CFSR_MUNSTKERR_MASK) |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| active_fields = [ |
| field.name for field in cpu_state_info.active_cfsr_fields() |
| ] |
| self.assertEqual(len(active_fields), 2) |
| self.assertIn('STKOF', active_fields) |
| self.assertIn('MUNSTKERR', active_fields) |
| |
| |
| class ExceptionCauseTest(unittest.TestCase): |
| """Test exception cause analysis.""" |
| def test_empty_cpu_state(self): |
| """Ensure empty CPU state has no known cause.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| self.assertEqual(cpu_state_info.exception_cause(), 'unknown exception') |
| |
| def test_unknown_exception(self): |
| """Ensure CPU state with insufficient info has no known cause.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| # Set CFSR to a valid value. |
| cpu_state_proto.cfsr = 0 |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| self.assertEqual(cpu_state_info.exception_cause(), 'unknown exception') |
| |
| def test_single_usage_fault(self): |
| """Ensure usage faults are properly identified.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| cpu_state_proto.cfsr = cortex_m_constants.PW_CORTEX_M_CFSR_STKOF_MASK |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| self.assertEqual(cpu_state_info.exception_cause(), |
| 'usage fault [STKOF]') |
| |
| def test_single_usage_fault_without_fields(self): |
| """Ensure disabling show_active_cfsr_fields hides field names.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| cpu_state_proto.cfsr = cortex_m_constants.PW_CORTEX_M_CFSR_STKOF_MASK |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| self.assertEqual(cpu_state_info.exception_cause(False), 'usage fault') |
| |
| def test_multiple_faults(self): |
| """Ensure multiple CFSR bits are identified and reported.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| cpu_state_proto.cfsr = ( |
| cortex_m_constants.PW_CORTEX_M_CFSR_STKOF_MASK |
| | cortex_m_constants.PW_CORTEX_M_CFSR_UNSTKERR_MASK) |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| self.assertEqual(cpu_state_info.exception_cause(), |
| 'usage fault, bus fault [UNSTKERR] [STKOF]') |
| |
| def test_mmfar_missing(self): |
| """Ensure if mmfar is valid but missing it is handled safely.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| cpu_state_proto.cfsr = ( |
| cortex_m_constants.PW_CORTEX_M_CFSR_MUNSTKERR_MASK |
| | cortex_m_constants.PW_CORTEX_M_CFSR_MMARVALID_MASK) |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| self.assertEqual(cpu_state_info.exception_cause(False), |
| 'memory management fault at ???') |
| |
| def test_mmfar_valid(self): |
| """Validate output format of valid MMFAR.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| cpu_state_proto.cfsr = ( |
| cortex_m_constants.PW_CORTEX_M_CFSR_MUNSTKERR_MASK |
| | cortex_m_constants.PW_CORTEX_M_CFSR_MMARVALID_MASK) |
| cpu_state_proto.mmfar = 0x722470e4 |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| self.assertEqual(cpu_state_info.exception_cause(False), |
| 'memory management fault at 0x722470e4') |
| |
| def test_imprecise_bus_fault(self): |
| """Check that imprecise bus faults are identified correctly.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| cpu_state_proto.cfsr = ( |
| cortex_m_constants.PW_CORTEX_M_CFSR_IMPRECISERR_MASK |
| | cortex_m_constants.PW_CORTEX_M_CFSR_IBUSERR_MASK) |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| self.assertEqual(cpu_state_info.exception_cause(False), |
| 'imprecise bus fault') |
| |
| |
| class TextDumpTest(unittest.TestCase): |
| """Test larger state dumps.""" |
| def test_registers(self): |
| """Validate output of general register dumps.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| cpu_state_proto.pc = 0xdfadd966 |
| cpu_state_proto.mmfar = 0xaf2ea98a |
| cpu_state_proto.r0 = 0xf3b235b1 |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| expected_dump = '\n'.join(( |
| 'pc 0xdfadd966', |
| 'mmfar 0xaf2ea98a', |
| 'r0 0xf3b235b1', |
| )) |
| 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() |
| cpu_state_proto.pc = 0xd2603058 |
| cpu_state_proto.mmfar = 0x8e4eb9a2 |
| cpu_state_proto.r0 = 0xdb5e7168 |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| expected_dump = '\n'.join(( |
| 'Exception caused by a unknown exception.', |
| '', |
| 'No active Crash Fault Status Register (CFSR) fields.', |
| '', |
| 'All registers:', |
| 'pc 0xd2603058', |
| 'mmfar 0x8e4eb9a2', |
| 'r0 0xdb5e7168', |
| )) |
| self.assertEqual(str(cpu_state_info), expected_dump) |
| |
| def test_dump_with_cfsr(self): |
| """Validate CPU state dump with CFSR bits set is formatted correctly.""" |
| cpu_state_proto = cpu_state_pb2.ArmV7mCpuState() |
| cpu_state_proto.cfsr = ( |
| cortex_m_constants.PW_CORTEX_M_CFSR_PRECISEERR_MASK |
| | cortex_m_constants.PW_CORTEX_M_CFSR_BFARVALID_MASK) |
| cpu_state_proto.pc = 0xd2603058 |
| cpu_state_proto.bfar = 0xdeadbeef |
| cpu_state_proto.mmfar = 0x8e4eb9a2 |
| cpu_state_proto.r0 = 0xdb5e7168 |
| cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer( |
| cpu_state_proto) |
| expected_dump = '\n'.join(( |
| 'Exception caused by a bus fault at 0xdeadbeef.', |
| '', |
| 'Active Crash Fault Status Register (CFSR) fields:', |
| 'PRECISEERR Precise bus fault.', |
| 'BFARVALID BFAR is valid.', |
| '', |
| 'All registers:', |
| 'pc 0xd2603058', |
| 'cfsr 0x00008200', |
| 'mmfar 0x8e4eb9a2', |
| 'bfar 0xdeadbeef', |
| 'r0 0xdb5e7168', |
| )) |
| self.assertEqual(str(cpu_state_info), expected_dump) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |