#!/usr/bin/env python3 # Copyright 2021 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Tests for create_unwind_table.py. This test suite contains tests for the custom unwind table creation for 32-bit arm builds. """ import io import struct import unittest import unittest.mock import re from create_unwind_table import ( AddressCfi, AddressUnwind, FilterToNonTombstoneCfi, FunctionCfi, FunctionUnwind, EncodeAddressUnwind, EncodeAddressUnwinds, EncodedAddressUnwind, EncodeAsBytes, EncodeFunctionOffsetTable, EncodedFunctionUnwind, EncodeFunctionUnwinds, EncodeStackPointerUpdate, EncodePop, EncodePageTableAndFunctionTable, EncodeUnwindInfo, EncodeUnwindInstructionTable, GenerateUnwinds, GenerateUnwindTables, NullParser, ParseAddressCfi, PushOrSubSpParser, ReadFunctionCfi, REFUSE_TO_UNWIND, StoreSpParser, TRIVIAL_UNWIND, Uleb128Encode, UnwindInstructionsParser, UnwindType, VPushParser) class _TestReadFunctionCfi(unittest.TestCase): def testFilterTombstone(self): input_lines = [ 'file name', 'STACK CFI INIT 0 ', 'STACK CFI 100 ', 'STACK CFI INIT 1 ', 'STACK CFI 200 ', ] f = io.StringIO(''.join(line + '\n' for line in input_lines)) self.assertEqual([ 'STACK CFI INIT 1 \n', 'STACK CFI 200 \n', ], list(FilterToNonTombstoneCfi(f))) def testReadFunctionCfiTombstoneFiltered(self): input_lines = [ 'STACK CFI INIT 0 50 .cfa: sp 0 + .ra: lr', # Tombstone function. 'STACK CFI 2 .cfa: sp 24 + .ra: .cfa - 4 + ^ r4: .cfa - 16 + ^ ' 'r5: .cfa - 12 + ^ r7: .cfa - 8 + ^', 'STACK CFI INIT 15b6490 4 .cfa: sp 0 + .ra: lr', ] f = io.StringIO(''.join(line + '\n' for line in input_lines)) self.assertEqual( [FunctionCfi(4, (AddressCfi(0x15b6490, '.cfa: sp 0 + .ra: lr'), ))], list(ReadFunctionCfi(f))) def testReadFunctionCfiSingleFunction(self): input_lines = [ 'STACK CFI INIT 15b6490 4 .cfa: sp 0 + .ra: lr', 'STACK CFI 2 .cfa: sp 24 + .ra: .cfa - 4 + ^ r4: .cfa - 16 + ^ ' 'r5: .cfa - 12 + ^ r7: .cfa - 8 + ^', ] f = io.StringIO(''.join(line + '\n' for line in input_lines)) self.assertEqual([ FunctionCfi(4, ( AddressCfi(0x15b6490, '.cfa: sp 0 + .ra: lr'), AddressCfi( 0x2, '.cfa: sp 24 + .ra: .cfa - 4 + ^ r4: .cfa - 16 + ^ ' 'r5: .cfa - 12 + ^ r7: .cfa - 8 + ^'), )) ], list(ReadFunctionCfi(f))) def testReadFunctionCfiMultipleFunctions(self): input_lines = [ 'STACK CFI INIT 15b6490 4 .cfa: sp 0 + .ra: lr', 'STACK CFI 2 .cfa: sp 24 + .ra: .cfa - 4 + ^ r4: .cfa - 16 + ^ ' 'r5: .cfa - 12 + ^ r7: .cfa - 8 + ^', 'STACK CFI INIT 15b655a 26 .cfa: sp 0 + .ra: lr', 'STACK CFI 15b655c .cfa: sp 8 + .ra: .cfa - 4 + ^ r4: .cfa - 8 + ^', ] f = io.StringIO(''.join(line + '\n' for line in input_lines)) self.assertEqual([ FunctionCfi(0x4, ( AddressCfi(0x15b6490, '.cfa: sp 0 + .ra: lr'), AddressCfi( 0x2, '.cfa: sp 24 + .ra: .cfa - 4 + ^ r4: .cfa - 16 + ^ ' 'r5: .cfa - 12 + ^ r7: .cfa - 8 + ^'), )), FunctionCfi(0x26, ( AddressCfi(0x15b655a, '.cfa: sp 0 + .ra: lr'), AddressCfi(0x15b655c, '.cfa: sp 8 + .ra: .cfa - 4 + ^ r4: .cfa - 8 + ^'), )), ], list(ReadFunctionCfi(f))) class _TestEncodeAsBytes(unittest.TestCase): def testOutOfBounds(self): self.assertRaises(ValueError, lambda: EncodeAsBytes(1024)) self.assertRaises(ValueError, lambda: EncodeAsBytes(256)) self.assertRaises(ValueError, lambda: EncodeAsBytes(-1)) def testEncode(self): self.assertEqual(bytes([0]), EncodeAsBytes(0)) self.assertEqual(bytes([255]), EncodeAsBytes(255)) self.assertEqual(bytes([0, 1]), EncodeAsBytes(0, 1)) class _TestUleb128Encode(unittest.TestCase): def testNegativeValue(self): self.assertRaises(ValueError, lambda: Uleb128Encode(-1)) def testSingleByte(self): self.assertEqual(bytes([0]), Uleb128Encode(0)) self.assertEqual(bytes([1]), Uleb128Encode(1)) self.assertEqual(bytes([127]), Uleb128Encode(127)) def testMultiBytes(self): self.assertEqual(bytes([0b10000000, 0b1]), Uleb128Encode(128)) self.assertEqual(bytes([0b10000000, 0b10000000, 0b1]), Uleb128Encode(128**2)) class _TestEncodeStackPointerUpdate(unittest.TestCase): def testSingleByte(self): self.assertEqual(bytes([0b00000000 | 0]), EncodeStackPointerUpdate(4)) self.assertEqual(bytes([0b01000000 | 0]), EncodeStackPointerUpdate(-4)) self.assertEqual(bytes([0b00000000 | 0b00111111]), EncodeStackPointerUpdate(0x100)) self.assertEqual(bytes([0b01000000 | 0b00111111]), EncodeStackPointerUpdate(-0x100)) self.assertEqual(bytes([0b00000000 | 3]), EncodeStackPointerUpdate(16)) self.assertEqual(bytes([0b01000000 | 3]), EncodeStackPointerUpdate(-16)) self.assertEqual(bytes([0b00111111]), EncodeStackPointerUpdate(0x100)) # 10110010 uleb128 # vsp = vsp + 0x204 + (uleb128 << 2) self.assertEqual(bytes([0b10110010, 0b00000000]), EncodeStackPointerUpdate(0x204)) self.assertEqual(bytes([0b10110010, 0b00000001]), EncodeStackPointerUpdate(0x208)) # For vsp increments of 0x104-0x200, use 00xxxxxx twice. self.assertEqual(bytes([0b00111111, 0b00000000]), EncodeStackPointerUpdate(0x104)) self.assertEqual(bytes([0b00111111, 0b00111111]), EncodeStackPointerUpdate(0x200)) self.assertEqual(bytes([0b01111111, 0b01111111]), EncodeStackPointerUpdate(-0x200)) # Not multiple of 4. self.assertRaises(AssertionError, lambda: EncodeStackPointerUpdate(101)) # offset=0 is meaningless. self.assertRaises(AssertionError, lambda: EncodeStackPointerUpdate(0)) class _TestEncodePop(unittest.TestCase): def testSingleRegister(self): # Should reject registers outside r4 ~ r15 range. for r in 0, 1, 2, 3, 16: self.assertRaises(AssertionError, lambda: EncodePop([r])) # Should use # 1000iiii iiiiiiii # Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}. self.assertEqual(bytes([0b10000000, 0b00000001]), EncodePop([4])) self.assertEqual(bytes([0b10000000, 0b00001000]), EncodePop([7])) self.assertEqual(bytes([0b10000100, 0b00000000]), EncodePop([14])) self.assertEqual(bytes([0b10001000, 0b00000000]), EncodePop([15])) def testContinuousRegisters(self): # 10101nnn # Pop r4-r[4+nnn], r14. self.assertEqual(bytes([0b10101000]), EncodePop([4, 14])) self.assertEqual(bytes([0b10101001]), EncodePop([4, 5, 14])) self.assertEqual(bytes([0b10101111]), EncodePop([4, 5, 6, 7, 8, 9, 10, 11, 14])) def testDiscontinuousRegisters(self): # 1000iiii iiiiiiii # Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}. self.assertEqual(bytes([0b10001000, 0b00000001]), EncodePop([4, 15])) self.assertEqual(bytes([0b10000100, 0b00011000]), EncodePop([7, 8, 14])) self.assertEqual(bytes([0b10000111, 0b11111111]), EncodePop([4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])) self.assertEqual(bytes([0b10000100, 0b10111111]), EncodePop([4, 5, 6, 7, 8, 9, 11, 14])) class _TestEncodeAddressUnwind(unittest.TestCase): def testReturnToLr(self): self.assertEqual( bytes([0b10110000]), EncodeAddressUnwind( AddressUnwind(address_offset=0, unwind_type=UnwindType.RETURN_TO_LR, sp_offset=0, registers=tuple()))) def testNoAction(self): self.assertEqual( bytes([]), EncodeAddressUnwind( AddressUnwind(address_offset=0, unwind_type=UnwindType.NO_ACTION, sp_offset=0, registers=tuple()))) def testUpdateSpAndOrPopRegisters(self): self.assertEqual( bytes([0b0, 0b10101000]), EncodeAddressUnwind( AddressUnwind(address_offset=0, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=0x4, registers=(4, 14)))) self.assertEqual( bytes([0b0]), EncodeAddressUnwind( AddressUnwind(address_offset=0, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=0x4, registers=tuple()))) self.assertEqual( bytes([0b10101000]), EncodeAddressUnwind( AddressUnwind(address_offset=0, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=0, registers=(4, 14)))) def testRestoreSpFromRegisters(self): self.assertEqual( bytes([0b10010100, 0b0]), EncodeAddressUnwind( AddressUnwind(address_offset=0, unwind_type=UnwindType.RESTORE_SP_FROM_REGISTER, sp_offset=0x4, registers=(4, )))) self.assertEqual( bytes([0b10010100]), EncodeAddressUnwind( AddressUnwind(address_offset=0, unwind_type=UnwindType.RESTORE_SP_FROM_REGISTER, sp_offset=0, registers=(4, )))) self.assertRaises( AssertionError, lambda: EncodeAddressUnwind( AddressUnwind(address_offset=0, unwind_type=UnwindType.RESTORE_SP_FROM_REGISTER, sp_offset=0x4, registers=tuple()))) class _TestEncodeAddressUnwinds(unittest.TestCase): def testEncodeOrder(self): address_unwind1 = AddressUnwind(address_offset=0, unwind_type=UnwindType.RETURN_TO_LR, sp_offset=0, registers=tuple()) address_unwind2 = AddressUnwind( address_offset=4, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=0, registers=(4, 14)) def MockEncodeAddressUnwind(address_unwind): return { address_unwind1: bytes([1]), address_unwind2: bytes([2]), }[address_unwind] with unittest.mock.patch("create_unwind_table.EncodeAddressUnwind", side_effect=MockEncodeAddressUnwind): encoded_unwinds = EncodeAddressUnwinds((address_unwind1, address_unwind2)) self.assertEqual(( EncodedAddressUnwind(4, bytes([2]) + bytes([1])), EncodedAddressUnwind(0, bytes([1])), ), encoded_unwinds) PAGE_SIZE = 1 << 17 class _TestEncodeFunctionUnwinds(unittest.TestCase): @unittest.mock.patch('create_unwind_table.EncodeAddressUnwinds') def testEncodeOrder(self, MockEncodeAddressUnwinds): MockEncodeAddressUnwinds.return_value = EncodedAddressUnwind(0, b'\x00') self.assertEqual([ EncodedFunctionUnwind(page_number=0, page_offset=0, address_unwinds=EncodedAddressUnwind(0, b'\x00')), EncodedFunctionUnwind(page_number=0, page_offset=100 >> 1, address_unwinds=EncodedAddressUnwind(0, b'\x00')), ], list( EncodeFunctionUnwinds([ FunctionUnwind(address=100, size=PAGE_SIZE - 100, address_unwinds=()), FunctionUnwind( address=0, size=100, address_unwinds=()), ], text_section_start_address=0))) @unittest.mock.patch('create_unwind_table.EncodeAddressUnwinds') def testFillingGaps(self, MockEncodeAddressUnwinds): MockEncodeAddressUnwinds.return_value = EncodedAddressUnwind(0, b'\x00') self.assertEqual([ EncodedFunctionUnwind(page_number=0, page_offset=0, address_unwinds=EncodedAddressUnwind(0, b'\x00')), EncodedFunctionUnwind( page_number=0, page_offset=50 >> 1, address_unwinds=TRIVIAL_UNWIND), EncodedFunctionUnwind(page_number=0, page_offset=100 >> 1, address_unwinds=EncodedAddressUnwind(0, b'\x00')), ], list( EncodeFunctionUnwinds([ FunctionUnwind( address=0, size=50, address_unwinds=()), FunctionUnwind(address=100, size=PAGE_SIZE - 100, address_unwinds=()), ], text_section_start_address=0))) @unittest.mock.patch('create_unwind_table.EncodeAddressUnwinds') def testFillingLastPage(self, MockEncodeAddressUnwinds): MockEncodeAddressUnwinds.return_value = EncodedAddressUnwind(0, b'\x00') self.assertEqual( [ EncodedFunctionUnwind(page_number=0, page_offset=0, address_unwinds=EncodedAddressUnwind( 0, b'\x00')), EncodedFunctionUnwind(page_number=0, page_offset=100 >> 1, address_unwinds=EncodedAddressUnwind( 0, b'\x00')), EncodedFunctionUnwind(page_number=0, page_offset=200 >> 1, address_unwinds=REFUSE_TO_UNWIND), ], list( EncodeFunctionUnwinds([ FunctionUnwind(address=1100, size=100, address_unwinds=()), FunctionUnwind(address=1200, size=100, address_unwinds=()), ], text_section_start_address=1100))) @unittest.mock.patch('create_unwind_table.EncodeAddressUnwinds') def testFillingFirstPage(self, MockEncodeAddressUnwinds): MockEncodeAddressUnwinds.return_value = EncodedAddressUnwind(0, b'\x00') self.assertEqual( [ EncodedFunctionUnwind( page_number=0, page_offset=0, address_unwinds=REFUSE_TO_UNWIND), EncodedFunctionUnwind(page_number=0, page_offset=100 >> 1, address_unwinds=EncodedAddressUnwind( 0, b'\x00')), EncodedFunctionUnwind(page_number=0, page_offset=200 >> 1, address_unwinds=EncodedAddressUnwind( 0, b'\x00')), EncodedFunctionUnwind(page_number=0, page_offset=300 >> 1, address_unwinds=REFUSE_TO_UNWIND), ], list( EncodeFunctionUnwinds([ FunctionUnwind(address=1100, size=100, address_unwinds=()), FunctionUnwind(address=1200, size=100, address_unwinds=()), ], text_section_start_address=1000))) @unittest.mock.patch('create_unwind_table.EncodeAddressUnwinds') def testOverlappedFunctions(self, _): self.assertRaises( # Eval generator with `list`. Otherwise the code will not execute. AssertionError, lambda: list( EncodeFunctionUnwinds([ FunctionUnwind(address=0, size=100, address_unwinds=()), FunctionUnwind(address=50, size=100, address_unwinds=()), ], text_section_start_address=0))) class _TestNullParser(unittest.TestCase): def testCfaChange(self): parser = NullParser() match = parser.GetBreakpadInstructionsRegex().search('.cfa: sp 0 + .ra: lr') self.assertIsNotNone(match) address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=0, cfa_sp_offset=0, match=match) self.assertEqual(0, new_cfa_sp_offset) self.assertEqual( AddressUnwind(address_offset=0, unwind_type=UnwindType.RETURN_TO_LR, sp_offset=0, registers=()), address_unwind) class _TestPushOrSubSpParser(unittest.TestCase): def testCfaChange(self): parser = PushOrSubSpParser() match = parser.GetBreakpadInstructionsRegex().search('.cfa: sp 4 +') self.assertIsNotNone(match) address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20, cfa_sp_offset=0, match=match) self.assertEqual(4, new_cfa_sp_offset) self.assertEqual( AddressUnwind(address_offset=20, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=4, registers=()), address_unwind) def testCfaAndRaChangePopOnly(self): parser = PushOrSubSpParser() match = parser.GetBreakpadInstructionsRegex().search( '.cfa: sp 4 + .ra: .cfa -4 + ^') self.assertIsNotNone(match) address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20, cfa_sp_offset=0, match=match) self.assertEqual(4, new_cfa_sp_offset) self.assertEqual( AddressUnwind(address_offset=20, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=0, registers=(14, )), address_unwind) def testCfaAndRaChangePopAndSpUpdate(self): parser = PushOrSubSpParser() match = parser.GetBreakpadInstructionsRegex().search( '.cfa: sp 8 + .ra: .cfa -4 + ^') self.assertIsNotNone(match) address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20, cfa_sp_offset=0, match=match) self.assertEqual(8, new_cfa_sp_offset) self.assertEqual( AddressUnwind(address_offset=20, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=4, registers=(14, )), address_unwind) def testCfaAndRaAndRegistersChangePopOnly(self): parser = PushOrSubSpParser() match = parser.GetBreakpadInstructionsRegex().search( '.cfa: sp 12 + .ra: .cfa -4 + ^ r4: .cfa -12 + ^ r7: .cfa -8 + ^') self.assertIsNotNone(match) address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20, cfa_sp_offset=0, match=match) self.assertEqual(12, new_cfa_sp_offset) self.assertEqual( AddressUnwind(address_offset=20, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=0, registers=(4, 7, 14)), address_unwind) def testCfaAndRaAndRegistersChangePopAndSpUpdate(self): parser = PushOrSubSpParser() match = parser.GetBreakpadInstructionsRegex().search( '.cfa: sp 16 + .ra: .cfa -4 + ^ r4: .cfa -12 + ^ r7: .cfa -8 + ^') self.assertIsNotNone(match) address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20, cfa_sp_offset=0, match=match) self.assertEqual(16, new_cfa_sp_offset) self.assertEqual( AddressUnwind(address_offset=20, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=4, registers=(4, 7, 14)), address_unwind) def testRegistersChange(self): parser = PushOrSubSpParser() match = parser.GetBreakpadInstructionsRegex().search( 'r4: .cfa -8 + ^ r7: .cfa -4 + ^') self.assertIsNotNone(match) address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20, cfa_sp_offset=0, match=match) self.assertEqual(0, new_cfa_sp_offset) self.assertEqual( AddressUnwind(address_offset=20, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=0, registers=(4, 7)), address_unwind) def testCfaAndRegistersChange(self): parser = PushOrSubSpParser() match = parser.GetBreakpadInstructionsRegex().search( '.cfa: sp 8 + r4: .cfa -8 + ^ r7: .cfa -4 + ^') self.assertIsNotNone(match) address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20, cfa_sp_offset=0, match=match) self.assertEqual(8, new_cfa_sp_offset) self.assertEqual( AddressUnwind(address_offset=20, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=0, registers=(4, 7)), address_unwind) def testRegistersOrdering(self): parser = PushOrSubSpParser() match = parser.GetBreakpadInstructionsRegex().search( 'r10: .cfa -8 + ^ r7: .cfa -4 + ^') self.assertIsNotNone(match) address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20, cfa_sp_offset=0, match=match) self.assertEqual(0, new_cfa_sp_offset) self.assertEqual( AddressUnwind(address_offset=20, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=0, registers=(7, 10)), address_unwind) def testPoppingCallerSaveRegisters(self): """Regression test for pop unwinds that encode caller-save registers. Callee-save registers: r0 ~ r3. """ parser = PushOrSubSpParser() match = parser.GetBreakpadInstructionsRegex().search( '.cfa: sp 16 + .ra: .cfa -4 + ^ ' 'r3: .cfa -16 + ^ r4: .cfa -12 + ^ r5: .cfa -8 + ^') self.assertIsNotNone(match) address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20, cfa_sp_offset=0, match=match) self.assertEqual(16, new_cfa_sp_offset) self.assertEqual( AddressUnwind(address_offset=20, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=4, registers=(4, 5, 14)), address_unwind) class _TestVPushParser(unittest.TestCase): def testCfaAndRegistersChange(self): parser = VPushParser() match = parser.GetBreakpadInstructionsRegex().search( '.cfa: sp 40 + unnamed_register264: .cfa -40 + ^ ' 'unnamed_register265: .cfa -32 + ^') self.assertIsNotNone(match) address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20, cfa_sp_offset=24, match=match) self.assertEqual(40, new_cfa_sp_offset) self.assertEqual( AddressUnwind(address_offset=20, unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, sp_offset=16, registers=()), address_unwind) def testRegistersChange(self): parser = VPushParser() match = parser.GetBreakpadInstructionsRegex().search( 'unnamed_register264: .cfa -40 + ^ unnamed_register265: .cfa -32 + ^') self.assertIsNotNone(match) address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20, cfa_sp_offset=24, match=match) self.assertEqual(24, new_cfa_sp_offset) self.assertEqual( AddressUnwind(address_offset=20, unwind_type=UnwindType.NO_ACTION, sp_offset=0, registers=()), address_unwind) class _TestStoreSpParser(unittest.TestCase): def testCfaAndRegistersChange(self): parser = StoreSpParser() match = parser.GetBreakpadInstructionsRegex().search('.cfa: r7 8 +') self.assertIsNotNone(match) address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20, cfa_sp_offset=12, match=match) self.assertEqual(8, new_cfa_sp_offset) self.assertEqual( AddressUnwind(address_offset=20, unwind_type=UnwindType.RESTORE_SP_FROM_REGISTER, sp_offset=-4, registers=(7, )), address_unwind) class _TestEncodeUnwindInstructionTable(unittest.TestCase): def testSingleEntry(self): table, offsets = EncodeUnwindInstructionTable([bytes([3])]) self.assertEqual(bytes([3]), table) self.assertDictEqual({ bytes([3]): 0, }, offsets) def testMultipleEntries(self): self.maxDiff = None # Result should be sorted by score descending. table, offsets = EncodeUnwindInstructionTable([ bytes([1, 2, 3]), bytes([0, 3]), bytes([3]), ]) self.assertEqual(bytes([3, 0, 3, 1, 2, 3]), table) self.assertDictEqual( { bytes([1, 2, 3]): 3, # score = 1 / 3 = 0.67 bytes([0, 3]): 1, # score = 1 / 2 = 0.5 bytes([3]): 0, # score = 1 / 1 = 1 }, offsets) # When scores are same, sort by sequence descending. table, offsets = EncodeUnwindInstructionTable([ bytes([3]), bytes([0, 3]), bytes([0, 3]), bytes([1, 2, 3]), bytes([1, 2, 3]), bytes([1, 2, 3]), ]) self.assertEqual(bytes([3, 1, 2, 3, 0, 3]), table) self.assertDictEqual( { bytes([3]): 0, # score = 1 / 1 = 1 bytes([1, 2, 3]): 1, # score = 3 / 3 = 1 bytes([0, 3]): 4, # score = 2 / 2 = 1 }, offsets) class _TestFunctionOffsetTable(unittest.TestCase): def testSingleEntry(self): self.maxDiff = None complete_instruction_sequence0 = bytes([3]) complete_instruction_sequence1 = bytes([1, 3]) sequence1 = ( EncodedAddressUnwind(0x400, complete_instruction_sequence1), EncodedAddressUnwind(0x0, complete_instruction_sequence0), ) address_unwind_sequences = [sequence1] table, offsets = EncodeFunctionOffsetTable( address_unwind_sequences, { complete_instruction_sequence0: 52, complete_instruction_sequence1: 50, }) self.assertEqual( bytes([ # (0x200, 50) 128, 4, 50, # (0, 52) 0, 52, ]), table) self.assertDictEqual({ sequence1: 0, }, offsets) def testMultipleEntry(self): self.maxDiff = None complete_instruction_sequence0 = bytes([3]) complete_instruction_sequence1 = bytes([1, 3]) complete_instruction_sequence2 = bytes([2, 3]) sequence1 = ( EncodedAddressUnwind(0x20, complete_instruction_sequence1), EncodedAddressUnwind(0x0, complete_instruction_sequence0), ) sequence2 = ( EncodedAddressUnwind(0x400, complete_instruction_sequence2), EncodedAddressUnwind(0x0, complete_instruction_sequence0), ) address_unwind_sequences = [sequence1, sequence2] table, offsets = EncodeFunctionOffsetTable( address_unwind_sequences, { complete_instruction_sequence0: 52, complete_instruction_sequence1: 50, complete_instruction_sequence2: 80, }) self.assertEqual( bytes([ # (0x10, 50) 0x10, 50, # (0, 52) 0, 52, # (0x200, 80) 128, 4, 80, # (0, 52) 0, 52, ]), table) self.assertDictEqual({ sequence1: 0, sequence2: 4, }, offsets) def testDuplicatedEntry(self): self.maxDiff = None complete_instruction_sequence0 = bytes([3]) complete_instruction_sequence1 = bytes([1, 3]) complete_instruction_sequence2 = bytes([2, 3]) sequence1 = ( EncodedAddressUnwind(0x20, complete_instruction_sequence1), EncodedAddressUnwind(0x0, complete_instruction_sequence0), ) sequence2 = ( EncodedAddressUnwind(0x400, complete_instruction_sequence2), EncodedAddressUnwind(0x0, complete_instruction_sequence0), ) sequence3 = sequence1 address_unwind_sequences = [sequence1, sequence2, sequence3] table, offsets = EncodeFunctionOffsetTable( address_unwind_sequences, { complete_instruction_sequence0: 52, complete_instruction_sequence1: 50, complete_instruction_sequence2: 80, }) self.assertEqual( bytes([ # (0x10, 50) 0x10, 50, # (0, 52) 0, 52, # (0x200, 80) 128, 4, 80, # (0, 52) 0, 52, ]), table) self.assertDictEqual({ sequence1: 0, sequence2: 4, }, offsets) class _TestEncodePageTableAndFunctionTable(unittest.TestCase): def testMultipleFunctionUnwinds(self): address_unwind_sequence0 = ( EncodedAddressUnwind(0x10, bytes([0, 3])), EncodedAddressUnwind(0x0, bytes([3])), ) address_unwind_sequence1 = ( EncodedAddressUnwind(0x10, bytes([1, 3])), EncodedAddressUnwind(0x0, bytes([3])), ) address_unwind_sequence2 = ( EncodedAddressUnwind(0x200, bytes([2, 3])), EncodedAddressUnwind(0x0, bytes([3])), ) function_unwinds = [ EncodedFunctionUnwind(page_number=0, page_offset=0, address_unwinds=address_unwind_sequence0), EncodedFunctionUnwind(page_number=0, page_offset=0x8000, address_unwinds=address_unwind_sequence1), EncodedFunctionUnwind(page_number=1, page_offset=0x8000, address_unwinds=address_unwind_sequence2), ] function_offset_table_offsets = { address_unwind_sequence0: 0x100, address_unwind_sequence1: 0x200, address_unwind_sequence2: 0x300, } page_table, function_table = EncodePageTableAndFunctionTable( function_unwinds, function_offset_table_offsets) self.assertEqual(2 * 4, len(page_table)) self.assertEqual((0, 2), struct.unpack('2I', page_table)) self.assertEqual(6 * 2, len(function_table)) self.assertEqual((0, 0x100, 0x8000, 0x200, 0x8000, 0x300), struct.unpack('6H', function_table)) def testMultiPageFunction(self): address_unwind_sequence0 = ( EncodedAddressUnwind(0x10, bytes([0, 3])), EncodedAddressUnwind(0x0, bytes([3])), ) address_unwind_sequence1 = ( EncodedAddressUnwind(0x10, bytes([1, 3])), EncodedAddressUnwind(0x0, bytes([3])), ) address_unwind_sequence2 = ( EncodedAddressUnwind(0x200, bytes([2, 3])), EncodedAddressUnwind(0x0, bytes([3])), ) function_unwinds = [ EncodedFunctionUnwind(page_number=0, page_offset=0, address_unwinds=address_unwind_sequence0), # Large function. EncodedFunctionUnwind(page_number=0, page_offset=0x8000, address_unwinds=address_unwind_sequence1), EncodedFunctionUnwind(page_number=4, page_offset=0x8000, address_unwinds=address_unwind_sequence2), ] function_offset_table_offsets = { address_unwind_sequence0: 0x100, address_unwind_sequence1: 0x200, address_unwind_sequence2: 0x300, } page_table, function_table = EncodePageTableAndFunctionTable( function_unwinds, function_offset_table_offsets) self.assertEqual(5 * 4, len(page_table)) self.assertEqual((0, 2, 2, 2, 2), struct.unpack('5I', page_table)) self.assertEqual(6 * 2, len(function_table)) self.assertEqual((0, 0x100, 0x8000, 0x200, 0x8000, 0x300), struct.unpack('6H', function_table)) class MockReturnParser(UnwindInstructionsParser): def GetBreakpadInstructionsRegex(self): return re.compile(r'^RETURN$') def ParseFromMatch(self, address_offset, cfa_sp_offset, match): return AddressUnwind(address_offset, UnwindType.RETURN_TO_LR, 0, ()), 0 class MockEpilogueUnwindParser(UnwindInstructionsParser): def GetBreakpadInstructionsRegex(self): return re.compile(r'^EPILOGUE_UNWIND$') def ParseFromMatch(self, address_offset, cfa_sp_offset, match): return AddressUnwind(address_offset, UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, 0, ()), -100 class MockWildcardParser(UnwindInstructionsParser): def GetBreakpadInstructionsRegex(self): return re.compile(r'.*') def ParseFromMatch(self, address_offset, cfa_sp_offset, match): return AddressUnwind(address_offset, UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, 0, ()), -200 class _TestParseAddressCfi(unittest.TestCase): def testSuccessParse(self): address_unwind = AddressUnwind( address_offset=0x300, unwind_type=UnwindType.RETURN_TO_LR, sp_offset=0, registers=(), ) self.assertEqual((address_unwind, False, 0), ParseAddressCfi(AddressCfi(address=0x800, unwind_instructions='RETURN'), function_start_address=0x500, parsers=(MockReturnParser(), ), prev_cfa_sp_offset=0)) def testUnhandledAddress(self): self.assertEqual((None, False, 100), ParseAddressCfi(AddressCfi(address=0x800, unwind_instructions='UNKNOWN'), function_start_address=0x500, parsers=(MockReturnParser(), ), prev_cfa_sp_offset=100)) def testEpilogueUnwind(self): self.assertEqual( (None, True, -100), ParseAddressCfi(AddressCfi(address=0x800, unwind_instructions='EPILOGUE_UNWIND'), function_start_address=0x500, parsers=(MockEpilogueUnwindParser(), ), prev_cfa_sp_offset=100)) def testParsePrecedence(self): address_unwind = AddressUnwind( address_offset=0x300, unwind_type=UnwindType.RETURN_TO_LR, sp_offset=0, registers=(), ) self.assertEqual( (address_unwind, False, 0), ParseAddressCfi(AddressCfi(address=0x800, unwind_instructions='RETURN'), function_start_address=0x500, parsers=(MockReturnParser(), MockWildcardParser()), prev_cfa_sp_offset=0)) class _TestGenerateUnwinds(unittest.TestCase): def testSuccessUnwind(self): self.assertEqual( [ FunctionUnwind(address=0x100, size=1024, address_unwinds=( AddressUnwind( address_offset=0x0, unwind_type=UnwindType.RETURN_TO_LR, sp_offset=0, registers=(), ), AddressUnwind( address_offset=0x200, unwind_type=UnwindType.RETURN_TO_LR, sp_offset=0, registers=(), ), )) ], list( GenerateUnwinds([ FunctionCfi( size=1024, address_cfi=( AddressCfi(address=0x100, unwind_instructions='RETURN'), AddressCfi(address=0x300, unwind_instructions='RETURN'), )) ], parsers=[MockReturnParser()]))) def testUnhandledAddress(self): self.assertEqual( [ FunctionUnwind(address=0x100, size=1024, address_unwinds=(AddressUnwind( address_offset=0x0, unwind_type=UnwindType.RETURN_TO_LR, sp_offset=0, registers=(), ), )) ], list( GenerateUnwinds([ FunctionCfi(size=1024, address_cfi=( AddressCfi(address=0x100, unwind_instructions='RETURN'), AddressCfi(address=0x300, unwind_instructions='UNKNOWN'), )) ], parsers=[MockReturnParser()]))) def testEpilogueUnwind(self): self.assertEqual( [ FunctionUnwind(address=0x100, size=1024, address_unwinds=(AddressUnwind( address_offset=0x0, unwind_type=UnwindType.RETURN_TO_LR, sp_offset=0, registers=(), ), )) ], list( GenerateUnwinds([ FunctionCfi( size=1024, address_cfi=( AddressCfi(address=0x100, unwind_instructions='RETURN'), AddressCfi(address=0x300, unwind_instructions='EPILOGUE_UNWIND'), )) ], parsers=[ MockReturnParser(), MockEpilogueUnwindParser() ]))) def testInvalidInitialUnwindInstructionAsserts(self): self.assertRaises( AssertionError, lambda: list( GenerateUnwinds([ FunctionCfi(size=1024, address_cfi=( AddressCfi(address=0x100, unwind_instructions='UNKNOWN'), AddressCfi(address=0x200, unwind_instructions='RETURN'), )) ], parsers=[MockReturnParser()]))) class _TestEncodeUnwindInfo(unittest.TestCase): def testEncodeTables(self): page_table = struct.pack('I', 0) function_table = struct.pack('4H', 1, 2, 3, 4) function_offset_table = bytes([1, 2]) unwind_instruction_table = bytes([1, 2, 3]) unwind_info = EncodeUnwindInfo( page_table, function_table, function_offset_table, unwind_instruction_table, ) self.assertEqual( 32 + len(page_table) + len(function_table) + len(function_offset_table) + len(unwind_instruction_table), len(unwind_info)) # Header. self.assertEqual((32, 1, 36, 2, 44, 2, 46, 3), struct.unpack('8I', unwind_info[:32])) # Body. self.assertEqual( page_table + function_table + function_offset_table + unwind_instruction_table, unwind_info[32:]) def testUnalignedTables(self): self.assertRaises( AssertionError, lambda: EncodeUnwindInfo(bytes([1]), b'', b'', b'')) self.assertRaises( AssertionError, lambda: EncodeUnwindInfo(b'', bytes([1]), b'', b'')) class _TestGenerateUnwindTables(unittest.TestCase): def testGenerateUnwindTables(self): """This is an integration test that hooks everything together. """ address_unwind_sequence0 = ( EncodedAddressUnwind(0x20, bytes([0, 0xb0])), EncodedAddressUnwind(0x0, bytes([0xb0])), ) address_unwind_sequence1 = ( EncodedAddressUnwind(0x20, bytes([1, 0xb0])), EncodedAddressUnwind(0x0, bytes([0xb0])), ) address_unwind_sequence2 = ( EncodedAddressUnwind(0x200, bytes([2, 0xb0])), EncodedAddressUnwind(0x0, bytes([0xb0])), ) (page_table, function_table, function_offset_table, unwind_instruction_table) = GenerateUnwindTables([ EncodedFunctionUnwind(page_number=0, page_offset=0, address_unwinds=TRIVIAL_UNWIND), EncodedFunctionUnwind(page_number=0, page_offset=0x1000, address_unwinds=address_unwind_sequence0), EncodedFunctionUnwind(page_number=1, page_offset=0x2000, address_unwinds=address_unwind_sequence1), EncodedFunctionUnwind(page_number=3, page_offset=0x1000, address_unwinds=address_unwind_sequence2), ]) # Complete instruction sequences and their frequencies. # [0xb0]: 4 # [0, 0xb0]: 1 # [1, 0xb0]: 1 # [2, 0xb0]: 1 self.assertEqual(bytes([0xb0, 2, 0xb0, 1, 0xb0, 0, 0xb0]), unwind_instruction_table) self.assertEqual( bytes([ # Trivial unwind. 0, 0, # Address unwind sequence 0. 0x10, 5, 0, 0, # Address unwind sequence 1. 0x10, 3, 0, 0, # Address unwind sequence 2. 0x80, 2, 1, 0, 0, ]), function_offset_table) self.assertEqual(8 * 2, len(function_table)) self.assertEqual((0, 0, 0x1000, 2, 0x2000, 6, 0x1000, 10), struct.unpack('8H', function_table)) self.assertEqual(4 * 4, len(page_table)) self.assertEqual((0, 2, 3, 3), struct.unpack('4I', page_table))