blob: 14fbc227ff2c4890455dc48d89809f92c9b1956a [file] [log] [blame]
#!/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))