tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

create_unwind_table_tests.py (44120B)


      1 #!/usr/bin/env python3
      2 # Copyright 2021 The Chromium Authors
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 """Tests for create_unwind_table.py.
      6 
      7 This test suite contains tests for the custom unwind table creation for 32-bit
      8 arm builds.
      9 """
     10 
     11 import io
     12 import struct
     13 
     14 import unittest
     15 import unittest.mock
     16 import re
     17 
     18 from create_unwind_table import (
     19    AddressCfi, AddressUnwind, FilterToNonTombstoneCfi, FunctionCfi,
     20    FunctionUnwind, EncodeAddressUnwind, EncodeAddressUnwinds,
     21    EncodedAddressUnwind, EncodeAsBytes, EncodeFunctionOffsetTable,
     22    EncodedFunctionUnwind, EncodeFunctionUnwinds, EncodeStackPointerUpdate,
     23    EncodePop, EncodePageTableAndFunctionTable, EncodeUnwindInfo,
     24    EncodeUnwindInstructionTable, GenerateUnwinds, GenerateUnwindTables,
     25    NullParser, ParseAddressCfi, PushOrSubSpParser, ReadFunctionCfi,
     26    REFUSE_TO_UNWIND, StoreSpParser, TRIVIAL_UNWIND, Uleb128Encode,
     27    UnwindInstructionsParser, UnwindType, VPushParser)
     28 
     29 
     30 class _TestReadFunctionCfi(unittest.TestCase):
     31  def testFilterTombstone(self):
     32    input_lines = [
     33        'file name',
     34        'STACK CFI INIT 0 ',
     35        'STACK CFI 100 ',
     36        'STACK CFI INIT 1 ',
     37        'STACK CFI 200 ',
     38    ]
     39 
     40    f = io.StringIO(''.join(line + '\n' for line in input_lines))
     41 
     42    self.assertEqual([
     43        'STACK CFI INIT 1 \n',
     44        'STACK CFI 200 \n',
     45    ], list(FilterToNonTombstoneCfi(f)))
     46 
     47  def testReadFunctionCfiTombstoneFiltered(self):
     48    input_lines = [
     49        'STACK CFI INIT 0 50 .cfa: sp 0 + .ra: lr',  # Tombstone function.
     50        'STACK CFI 2 .cfa: sp 24 + .ra: .cfa - 4 + ^ r4: .cfa - 16 + ^ '
     51        'r5: .cfa - 12 + ^ r7: .cfa - 8 + ^',
     52        'STACK CFI INIT 15b6490 4 .cfa: sp 0 + .ra: lr',
     53    ]
     54 
     55    f = io.StringIO(''.join(line + '\n' for line in input_lines))
     56 
     57    self.assertEqual(
     58        [FunctionCfi(4, (AddressCfi(0x15b6490, '.cfa: sp 0 + .ra: lr'), ))],
     59        list(ReadFunctionCfi(f)))
     60 
     61  def testReadFunctionCfiSingleFunction(self):
     62    input_lines = [
     63        'STACK CFI INIT 15b6490 4 .cfa: sp 0 + .ra: lr',
     64        'STACK CFI 2 .cfa: sp 24 + .ra: .cfa - 4 + ^ r4: .cfa - 16 + ^ '
     65        'r5: .cfa - 12 + ^ r7: .cfa - 8 + ^',
     66    ]
     67 
     68    f = io.StringIO(''.join(line + '\n' for line in input_lines))
     69 
     70    self.assertEqual([
     71        FunctionCfi(4, (
     72            AddressCfi(0x15b6490, '.cfa: sp 0 + .ra: lr'),
     73            AddressCfi(
     74                0x2, '.cfa: sp 24 + .ra: .cfa - 4 + ^ r4: .cfa - 16 + ^ '
     75                'r5: .cfa - 12 + ^ r7: .cfa - 8 + ^'),
     76        ))
     77    ], list(ReadFunctionCfi(f)))
     78 
     79  def testReadFunctionCfiMultipleFunctions(self):
     80    input_lines = [
     81        'STACK CFI INIT 15b6490 4 .cfa: sp 0 + .ra: lr',
     82        'STACK CFI 2 .cfa: sp 24 + .ra: .cfa - 4 + ^ r4: .cfa - 16 + ^ '
     83        'r5: .cfa - 12 + ^ r7: .cfa - 8 + ^',
     84        'STACK CFI INIT 15b655a 26 .cfa: sp 0 + .ra: lr',
     85        'STACK CFI 15b655c .cfa: sp 8 + .ra: .cfa - 4 + ^ r4: .cfa - 8 + ^',
     86    ]
     87 
     88    f = io.StringIO(''.join(line + '\n' for line in input_lines))
     89 
     90    self.assertEqual([
     91        FunctionCfi(0x4, (
     92            AddressCfi(0x15b6490, '.cfa: sp 0 + .ra: lr'),
     93            AddressCfi(
     94                0x2, '.cfa: sp 24 + .ra: .cfa - 4 + ^ r4: .cfa - 16 + ^ '
     95                'r5: .cfa - 12 + ^ r7: .cfa - 8 + ^'),
     96        )),
     97        FunctionCfi(0x26, (
     98            AddressCfi(0x15b655a, '.cfa: sp 0 + .ra: lr'),
     99            AddressCfi(0x15b655c,
    100                       '.cfa: sp 8 + .ra: .cfa - 4 + ^ r4: .cfa - 8 + ^'),
    101        )),
    102    ], list(ReadFunctionCfi(f)))
    103 
    104 
    105 class _TestEncodeAsBytes(unittest.TestCase):
    106  def testOutOfBounds(self):
    107    self.assertRaises(ValueError, lambda: EncodeAsBytes(1024))
    108    self.assertRaises(ValueError, lambda: EncodeAsBytes(256))
    109    self.assertRaises(ValueError, lambda: EncodeAsBytes(-1))
    110 
    111  def testEncode(self):
    112    self.assertEqual(bytes([0]), EncodeAsBytes(0))
    113    self.assertEqual(bytes([255]), EncodeAsBytes(255))
    114    self.assertEqual(bytes([0, 1]), EncodeAsBytes(0, 1))
    115 
    116 
    117 class _TestUleb128Encode(unittest.TestCase):
    118  def testNegativeValue(self):
    119    self.assertRaises(ValueError, lambda: Uleb128Encode(-1))
    120 
    121  def testSingleByte(self):
    122    self.assertEqual(bytes([0]), Uleb128Encode(0))
    123    self.assertEqual(bytes([1]), Uleb128Encode(1))
    124    self.assertEqual(bytes([127]), Uleb128Encode(127))
    125 
    126  def testMultiBytes(self):
    127    self.assertEqual(bytes([0b10000000, 0b1]), Uleb128Encode(128))
    128    self.assertEqual(bytes([0b10000000, 0b10000000, 0b1]),
    129                     Uleb128Encode(128**2))
    130 
    131 
    132 class _TestEncodeStackPointerUpdate(unittest.TestCase):
    133  def testSingleByte(self):
    134    self.assertEqual(bytes([0b00000000 | 0]), EncodeStackPointerUpdate(4))
    135    self.assertEqual(bytes([0b01000000 | 0]), EncodeStackPointerUpdate(-4))
    136 
    137    self.assertEqual(bytes([0b00000000 | 0b00111111]),
    138                     EncodeStackPointerUpdate(0x100))
    139    self.assertEqual(bytes([0b01000000 | 0b00111111]),
    140                     EncodeStackPointerUpdate(-0x100))
    141 
    142    self.assertEqual(bytes([0b00000000 | 3]), EncodeStackPointerUpdate(16))
    143    self.assertEqual(bytes([0b01000000 | 3]), EncodeStackPointerUpdate(-16))
    144 
    145    self.assertEqual(bytes([0b00111111]), EncodeStackPointerUpdate(0x100))
    146 
    147    # 10110010 uleb128
    148    # vsp = vsp + 0x204 + (uleb128 << 2)
    149    self.assertEqual(bytes([0b10110010, 0b00000000]),
    150                     EncodeStackPointerUpdate(0x204))
    151    self.assertEqual(bytes([0b10110010, 0b00000001]),
    152                     EncodeStackPointerUpdate(0x208))
    153 
    154    # For vsp increments of 0x104-0x200, use 00xxxxxx twice.
    155    self.assertEqual(bytes([0b00111111, 0b00000000]),
    156                     EncodeStackPointerUpdate(0x104))
    157    self.assertEqual(bytes([0b00111111, 0b00111111]),
    158                     EncodeStackPointerUpdate(0x200))
    159    self.assertEqual(bytes([0b01111111, 0b01111111]),
    160                     EncodeStackPointerUpdate(-0x200))
    161 
    162    # Not multiple of 4.
    163    self.assertRaises(AssertionError, lambda: EncodeStackPointerUpdate(101))
    164    # offset=0 is meaningless.
    165    self.assertRaises(AssertionError, lambda: EncodeStackPointerUpdate(0))
    166 
    167 
    168 class _TestEncodePop(unittest.TestCase):
    169  def testSingleRegister(self):
    170    # Should reject registers outside r4 ~ r15 range.
    171    for r in 0, 1, 2, 3, 16:
    172      self.assertRaises(AssertionError, lambda: EncodePop([r]))
    173    # Should use
    174    # 1000iiii iiiiiiii
    175    # Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}.
    176    self.assertEqual(bytes([0b10000000, 0b00000001]), EncodePop([4]))
    177    self.assertEqual(bytes([0b10000000, 0b00001000]), EncodePop([7]))
    178    self.assertEqual(bytes([0b10000100, 0b00000000]), EncodePop([14]))
    179    self.assertEqual(bytes([0b10001000, 0b00000000]), EncodePop([15]))
    180 
    181  def testContinuousRegisters(self):
    182    # 10101nnn
    183    # Pop r4-r[4+nnn], r14.
    184    self.assertEqual(bytes([0b10101000]), EncodePop([4, 14]))
    185    self.assertEqual(bytes([0b10101001]), EncodePop([4, 5, 14]))
    186    self.assertEqual(bytes([0b10101111]),
    187                     EncodePop([4, 5, 6, 7, 8, 9, 10, 11, 14]))
    188 
    189  def testDiscontinuousRegisters(self):
    190    # 1000iiii iiiiiiii
    191    # Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}.
    192    self.assertEqual(bytes([0b10001000, 0b00000001]), EncodePop([4, 15]))
    193    self.assertEqual(bytes([0b10000100, 0b00011000]), EncodePop([7, 8, 14]))
    194    self.assertEqual(bytes([0b10000111, 0b11111111]),
    195                     EncodePop([4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]))
    196    self.assertEqual(bytes([0b10000100, 0b10111111]),
    197                     EncodePop([4, 5, 6, 7, 8, 9, 11, 14]))
    198 
    199 
    200 class _TestEncodeAddressUnwind(unittest.TestCase):
    201  def testReturnToLr(self):
    202    self.assertEqual(
    203        bytes([0b10110000]),
    204        EncodeAddressUnwind(
    205            AddressUnwind(address_offset=0,
    206                          unwind_type=UnwindType.RETURN_TO_LR,
    207                          sp_offset=0,
    208                          registers=tuple())))
    209 
    210  def testNoAction(self):
    211    self.assertEqual(
    212        bytes([]),
    213        EncodeAddressUnwind(
    214            AddressUnwind(address_offset=0,
    215                          unwind_type=UnwindType.NO_ACTION,
    216                          sp_offset=0,
    217                          registers=tuple())))
    218 
    219  def testUpdateSpAndOrPopRegisters(self):
    220    self.assertEqual(
    221        bytes([0b0, 0b10101000]),
    222        EncodeAddressUnwind(
    223            AddressUnwind(address_offset=0,
    224                          unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    225                          sp_offset=0x4,
    226                          registers=(4, 14))))
    227 
    228    self.assertEqual(
    229        bytes([0b0]),
    230        EncodeAddressUnwind(
    231            AddressUnwind(address_offset=0,
    232                          unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    233                          sp_offset=0x4,
    234                          registers=tuple())))
    235 
    236    self.assertEqual(
    237        bytes([0b10101000]),
    238        EncodeAddressUnwind(
    239            AddressUnwind(address_offset=0,
    240                          unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    241                          sp_offset=0,
    242                          registers=(4, 14))))
    243 
    244  def testRestoreSpFromRegisters(self):
    245    self.assertEqual(
    246        bytes([0b10010100, 0b0]),
    247        EncodeAddressUnwind(
    248            AddressUnwind(address_offset=0,
    249                          unwind_type=UnwindType.RESTORE_SP_FROM_REGISTER,
    250                          sp_offset=0x4,
    251                          registers=(4, ))))
    252 
    253    self.assertEqual(
    254        bytes([0b10010100]),
    255        EncodeAddressUnwind(
    256            AddressUnwind(address_offset=0,
    257                          unwind_type=UnwindType.RESTORE_SP_FROM_REGISTER,
    258                          sp_offset=0,
    259                          registers=(4, ))))
    260 
    261    self.assertRaises(
    262        AssertionError, lambda: EncodeAddressUnwind(
    263            AddressUnwind(address_offset=0,
    264                          unwind_type=UnwindType.RESTORE_SP_FROM_REGISTER,
    265                          sp_offset=0x4,
    266                          registers=tuple())))
    267 
    268 
    269 class _TestEncodeAddressUnwinds(unittest.TestCase):
    270  def testEncodeOrder(self):
    271    address_unwind1 = AddressUnwind(address_offset=0,
    272                                    unwind_type=UnwindType.RETURN_TO_LR,
    273                                    sp_offset=0,
    274                                    registers=tuple())
    275    address_unwind2 = AddressUnwind(
    276        address_offset=4,
    277        unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    278        sp_offset=0,
    279        registers=(4, 14))
    280 
    281    def MockEncodeAddressUnwind(address_unwind):
    282      return {
    283          address_unwind1: bytes([1]),
    284          address_unwind2: bytes([2]),
    285      }[address_unwind]
    286 
    287    with unittest.mock.patch("create_unwind_table.EncodeAddressUnwind",
    288                             side_effect=MockEncodeAddressUnwind):
    289      encoded_unwinds = EncodeAddressUnwinds((address_unwind1, address_unwind2))
    290      self.assertEqual((
    291          EncodedAddressUnwind(4,
    292                               bytes([2]) + bytes([1])),
    293          EncodedAddressUnwind(0, bytes([1])),
    294      ), encoded_unwinds)
    295 
    296 
    297 PAGE_SIZE = 1 << 17
    298 
    299 
    300 class _TestEncodeFunctionUnwinds(unittest.TestCase):
    301  @unittest.mock.patch('create_unwind_table.EncodeAddressUnwinds')
    302  def testEncodeOrder(self, MockEncodeAddressUnwinds):
    303    MockEncodeAddressUnwinds.return_value = EncodedAddressUnwind(0, b'\x00')
    304 
    305    self.assertEqual([
    306        EncodedFunctionUnwind(page_number=0,
    307                              page_offset=0,
    308                              address_unwinds=EncodedAddressUnwind(0, b'\x00')),
    309        EncodedFunctionUnwind(page_number=0,
    310                              page_offset=100 >> 1,
    311                              address_unwinds=EncodedAddressUnwind(0, b'\x00')),
    312    ],
    313                     list(
    314                         EncodeFunctionUnwinds([
    315                             FunctionUnwind(address=100,
    316                                            size=PAGE_SIZE - 100,
    317                                            address_unwinds=()),
    318                             FunctionUnwind(
    319                                 address=0, size=100, address_unwinds=()),
    320                         ],
    321                                               text_section_start_address=0)))
    322 
    323  @unittest.mock.patch('create_unwind_table.EncodeAddressUnwinds')
    324  def testFillingGaps(self, MockEncodeAddressUnwinds):
    325    MockEncodeAddressUnwinds.return_value = EncodedAddressUnwind(0, b'\x00')
    326 
    327    self.assertEqual([
    328        EncodedFunctionUnwind(page_number=0,
    329                              page_offset=0,
    330                              address_unwinds=EncodedAddressUnwind(0, b'\x00')),
    331        EncodedFunctionUnwind(
    332            page_number=0, page_offset=50 >> 1, address_unwinds=TRIVIAL_UNWIND),
    333        EncodedFunctionUnwind(page_number=0,
    334                              page_offset=100 >> 1,
    335                              address_unwinds=EncodedAddressUnwind(0, b'\x00')),
    336    ],
    337                     list(
    338                         EncodeFunctionUnwinds([
    339                             FunctionUnwind(
    340                                 address=0, size=50, address_unwinds=()),
    341                             FunctionUnwind(address=100,
    342                                            size=PAGE_SIZE - 100,
    343                                            address_unwinds=()),
    344                         ],
    345                                               text_section_start_address=0)))
    346 
    347  @unittest.mock.patch('create_unwind_table.EncodeAddressUnwinds')
    348  def testFillingLastPage(self, MockEncodeAddressUnwinds):
    349    MockEncodeAddressUnwinds.return_value = EncodedAddressUnwind(0, b'\x00')
    350 
    351    self.assertEqual(
    352        [
    353            EncodedFunctionUnwind(page_number=0,
    354                                  page_offset=0,
    355                                  address_unwinds=EncodedAddressUnwind(
    356                                      0, b'\x00')),
    357            EncodedFunctionUnwind(page_number=0,
    358                                  page_offset=100 >> 1,
    359                                  address_unwinds=EncodedAddressUnwind(
    360                                      0, b'\x00')),
    361            EncodedFunctionUnwind(page_number=0,
    362                                  page_offset=200 >> 1,
    363                                  address_unwinds=REFUSE_TO_UNWIND),
    364        ],
    365        list(
    366            EncodeFunctionUnwinds([
    367                FunctionUnwind(address=1100, size=100, address_unwinds=()),
    368                FunctionUnwind(address=1200, size=100, address_unwinds=()),
    369            ],
    370                                  text_section_start_address=1100)))
    371 
    372  @unittest.mock.patch('create_unwind_table.EncodeAddressUnwinds')
    373  def testFillingFirstPage(self, MockEncodeAddressUnwinds):
    374    MockEncodeAddressUnwinds.return_value = EncodedAddressUnwind(0, b'\x00')
    375 
    376    self.assertEqual(
    377        [
    378            EncodedFunctionUnwind(
    379                page_number=0, page_offset=0, address_unwinds=REFUSE_TO_UNWIND),
    380            EncodedFunctionUnwind(page_number=0,
    381                                  page_offset=100 >> 1,
    382                                  address_unwinds=EncodedAddressUnwind(
    383                                      0, b'\x00')),
    384            EncodedFunctionUnwind(page_number=0,
    385                                  page_offset=200 >> 1,
    386                                  address_unwinds=EncodedAddressUnwind(
    387                                      0, b'\x00')),
    388            EncodedFunctionUnwind(page_number=0,
    389                                  page_offset=300 >> 1,
    390                                  address_unwinds=REFUSE_TO_UNWIND),
    391        ],
    392        list(
    393            EncodeFunctionUnwinds([
    394                FunctionUnwind(address=1100, size=100, address_unwinds=()),
    395                FunctionUnwind(address=1200, size=100, address_unwinds=()),
    396            ],
    397                                  text_section_start_address=1000)))
    398 
    399  @unittest.mock.patch('create_unwind_table.EncodeAddressUnwinds')
    400  def testOverlappedFunctions(self, _):
    401    self.assertRaises(
    402        # Eval generator with `list`. Otherwise the code will not execute.
    403        AssertionError,
    404        lambda: list(
    405            EncodeFunctionUnwinds([
    406                FunctionUnwind(address=0, size=100, address_unwinds=()),
    407                FunctionUnwind(address=50, size=100, address_unwinds=()),
    408            ],
    409                                  text_section_start_address=0)))
    410 
    411 
    412 class _TestNullParser(unittest.TestCase):
    413  def testCfaChange(self):
    414    parser = NullParser()
    415    match = parser.GetBreakpadInstructionsRegex().search('.cfa: sp 0 + .ra: lr')
    416    self.assertIsNotNone(match)
    417 
    418    address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=0,
    419                                                              cfa_sp_offset=0,
    420                                                              match=match)
    421 
    422    self.assertEqual(0, new_cfa_sp_offset)
    423    self.assertEqual(
    424        AddressUnwind(address_offset=0,
    425                      unwind_type=UnwindType.RETURN_TO_LR,
    426                      sp_offset=0,
    427                      registers=()), address_unwind)
    428 
    429 
    430 class _TestPushOrSubSpParser(unittest.TestCase):
    431  def testCfaChange(self):
    432    parser = PushOrSubSpParser()
    433    match = parser.GetBreakpadInstructionsRegex().search('.cfa: sp 4 +')
    434    self.assertIsNotNone(match)
    435 
    436    address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20,
    437                                                              cfa_sp_offset=0,
    438                                                              match=match)
    439 
    440    self.assertEqual(4, new_cfa_sp_offset)
    441    self.assertEqual(
    442        AddressUnwind(address_offset=20,
    443                      unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    444                      sp_offset=4,
    445                      registers=()), address_unwind)
    446 
    447  def testCfaAndRaChangePopOnly(self):
    448    parser = PushOrSubSpParser()
    449    match = parser.GetBreakpadInstructionsRegex().search(
    450        '.cfa: sp 4 + .ra: .cfa -4 + ^')
    451    self.assertIsNotNone(match)
    452 
    453    address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20,
    454                                                              cfa_sp_offset=0,
    455                                                              match=match)
    456 
    457    self.assertEqual(4, new_cfa_sp_offset)
    458    self.assertEqual(
    459        AddressUnwind(address_offset=20,
    460                      unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    461                      sp_offset=0,
    462                      registers=(14, )), address_unwind)
    463 
    464  def testCfaAndRaChangePopAndSpUpdate(self):
    465    parser = PushOrSubSpParser()
    466    match = parser.GetBreakpadInstructionsRegex().search(
    467        '.cfa: sp 8 + .ra: .cfa -4 + ^')
    468    self.assertIsNotNone(match)
    469 
    470    address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20,
    471                                                              cfa_sp_offset=0,
    472                                                              match=match)
    473 
    474    self.assertEqual(8, new_cfa_sp_offset)
    475    self.assertEqual(
    476        AddressUnwind(address_offset=20,
    477                      unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    478                      sp_offset=4,
    479                      registers=(14, )), address_unwind)
    480 
    481  def testCfaAndRaAndRegistersChangePopOnly(self):
    482    parser = PushOrSubSpParser()
    483    match = parser.GetBreakpadInstructionsRegex().search(
    484        '.cfa: sp 12 + .ra: .cfa -4 + ^ r4: .cfa -12 + ^ r7: .cfa -8 + ^')
    485    self.assertIsNotNone(match)
    486 
    487    address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20,
    488                                                              cfa_sp_offset=0,
    489                                                              match=match)
    490 
    491    self.assertEqual(12, new_cfa_sp_offset)
    492    self.assertEqual(
    493        AddressUnwind(address_offset=20,
    494                      unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    495                      sp_offset=0,
    496                      registers=(4, 7, 14)), address_unwind)
    497 
    498  def testCfaAndRaAndRegistersChangePopAndSpUpdate(self):
    499    parser = PushOrSubSpParser()
    500    match = parser.GetBreakpadInstructionsRegex().search(
    501        '.cfa: sp 16 + .ra: .cfa -4 + ^ r4: .cfa -12 + ^ r7: .cfa -8 + ^')
    502    self.assertIsNotNone(match)
    503 
    504    address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20,
    505                                                              cfa_sp_offset=0,
    506                                                              match=match)
    507 
    508    self.assertEqual(16, new_cfa_sp_offset)
    509    self.assertEqual(
    510        AddressUnwind(address_offset=20,
    511                      unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    512                      sp_offset=4,
    513                      registers=(4, 7, 14)), address_unwind)
    514 
    515  def testRegistersChange(self):
    516    parser = PushOrSubSpParser()
    517    match = parser.GetBreakpadInstructionsRegex().search(
    518        'r4: .cfa -8 + ^ r7: .cfa -4 + ^')
    519    self.assertIsNotNone(match)
    520 
    521    address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20,
    522                                                              cfa_sp_offset=0,
    523                                                              match=match)
    524 
    525    self.assertEqual(0, new_cfa_sp_offset)
    526    self.assertEqual(
    527        AddressUnwind(address_offset=20,
    528                      unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    529                      sp_offset=0,
    530                      registers=(4, 7)), address_unwind)
    531 
    532  def testCfaAndRegistersChange(self):
    533    parser = PushOrSubSpParser()
    534    match = parser.GetBreakpadInstructionsRegex().search(
    535        '.cfa: sp 8 + r4: .cfa -8 + ^ r7: .cfa -4 + ^')
    536    self.assertIsNotNone(match)
    537 
    538    address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20,
    539                                                              cfa_sp_offset=0,
    540                                                              match=match)
    541 
    542    self.assertEqual(8, new_cfa_sp_offset)
    543    self.assertEqual(
    544        AddressUnwind(address_offset=20,
    545                      unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    546                      sp_offset=0,
    547                      registers=(4, 7)), address_unwind)
    548 
    549  def testRegistersOrdering(self):
    550    parser = PushOrSubSpParser()
    551    match = parser.GetBreakpadInstructionsRegex().search(
    552        'r10: .cfa -8 + ^ r7: .cfa -4 + ^')
    553    self.assertIsNotNone(match)
    554 
    555    address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20,
    556                                                              cfa_sp_offset=0,
    557                                                              match=match)
    558 
    559    self.assertEqual(0, new_cfa_sp_offset)
    560    self.assertEqual(
    561        AddressUnwind(address_offset=20,
    562                      unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    563                      sp_offset=0,
    564                      registers=(7, 10)), address_unwind)
    565 
    566  def testPoppingCallerSaveRegisters(self):
    567    """Regression test for pop unwinds that encode caller-save registers.
    568 
    569    Callee-save registers: r0 ~ r3.
    570    """
    571    parser = PushOrSubSpParser()
    572    match = parser.GetBreakpadInstructionsRegex().search(
    573        '.cfa: sp 16 + .ra: .cfa -4 + ^ '
    574        'r3: .cfa -16 + ^ r4: .cfa -12 + ^ r5: .cfa -8 + ^')
    575 
    576    self.assertIsNotNone(match)
    577 
    578    address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20,
    579                                                              cfa_sp_offset=0,
    580                                                              match=match)
    581 
    582    self.assertEqual(16, new_cfa_sp_offset)
    583    self.assertEqual(
    584        AddressUnwind(address_offset=20,
    585                      unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    586                      sp_offset=4,
    587                      registers=(4, 5, 14)), address_unwind)
    588 
    589 
    590 class _TestVPushParser(unittest.TestCase):
    591  def testCfaAndRegistersChange(self):
    592    parser = VPushParser()
    593    match = parser.GetBreakpadInstructionsRegex().search(
    594        '.cfa: sp 40 + unnamed_register264: .cfa -40 + ^ '
    595        'unnamed_register265: .cfa -32 + ^')
    596    self.assertIsNotNone(match)
    597 
    598    address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20,
    599                                                              cfa_sp_offset=24,
    600                                                              match=match)
    601 
    602    self.assertEqual(40, new_cfa_sp_offset)
    603    self.assertEqual(
    604        AddressUnwind(address_offset=20,
    605                      unwind_type=UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS,
    606                      sp_offset=16,
    607                      registers=()), address_unwind)
    608 
    609  def testRegistersChange(self):
    610    parser = VPushParser()
    611    match = parser.GetBreakpadInstructionsRegex().search(
    612        'unnamed_register264: .cfa -40 + ^ unnamed_register265: .cfa -32 + ^')
    613    self.assertIsNotNone(match)
    614 
    615    address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20,
    616                                                              cfa_sp_offset=24,
    617                                                              match=match)
    618 
    619    self.assertEqual(24, new_cfa_sp_offset)
    620    self.assertEqual(
    621        AddressUnwind(address_offset=20,
    622                      unwind_type=UnwindType.NO_ACTION,
    623                      sp_offset=0,
    624                      registers=()), address_unwind)
    625 
    626 
    627 class _TestStoreSpParser(unittest.TestCase):
    628  def testCfaAndRegistersChange(self):
    629    parser = StoreSpParser()
    630    match = parser.GetBreakpadInstructionsRegex().search('.cfa: r7 8 +')
    631    self.assertIsNotNone(match)
    632 
    633    address_unwind, new_cfa_sp_offset = parser.ParseFromMatch(address_offset=20,
    634                                                              cfa_sp_offset=12,
    635                                                              match=match)
    636 
    637    self.assertEqual(8, new_cfa_sp_offset)
    638    self.assertEqual(
    639        AddressUnwind(address_offset=20,
    640                      unwind_type=UnwindType.RESTORE_SP_FROM_REGISTER,
    641                      sp_offset=-4,
    642                      registers=(7, )), address_unwind)
    643 
    644 
    645 class _TestEncodeUnwindInstructionTable(unittest.TestCase):
    646  def testSingleEntry(self):
    647    table, offsets = EncodeUnwindInstructionTable([bytes([3])])
    648 
    649    self.assertEqual(bytes([3]), table)
    650    self.assertDictEqual({
    651        bytes([3]): 0,
    652    }, offsets)
    653 
    654  def testMultipleEntries(self):
    655    self.maxDiff = None
    656    # Result should be sorted by score descending.
    657    table, offsets = EncodeUnwindInstructionTable([
    658        bytes([1, 2, 3]),
    659        bytes([0, 3]),
    660        bytes([3]),
    661    ])
    662    self.assertEqual(bytes([3, 0, 3, 1, 2, 3]), table)
    663    self.assertDictEqual(
    664        {
    665            bytes([1, 2, 3]): 3,  # score = 1 / 3 = 0.67
    666            bytes([0, 3]): 1,  # score = 1 / 2 = 0.5
    667            bytes([3]): 0,  # score = 1 / 1 = 1
    668        },
    669        offsets)
    670 
    671    # When scores are same, sort by sequence descending.
    672    table, offsets = EncodeUnwindInstructionTable([
    673        bytes([3]),
    674        bytes([0, 3]),
    675        bytes([0, 3]),
    676        bytes([1, 2, 3]),
    677        bytes([1, 2, 3]),
    678        bytes([1, 2, 3]),
    679    ])
    680    self.assertEqual(bytes([3, 1, 2, 3, 0, 3]), table)
    681    self.assertDictEqual(
    682        {
    683            bytes([3]): 0,  # score = 1 / 1 = 1
    684            bytes([1, 2, 3]): 1,  # score = 3 / 3 = 1
    685            bytes([0, 3]): 4,  # score = 2 / 2 = 1
    686        },
    687        offsets)
    688 
    689 
    690 class _TestFunctionOffsetTable(unittest.TestCase):
    691  def testSingleEntry(self):
    692    self.maxDiff = None
    693    complete_instruction_sequence0 = bytes([3])
    694    complete_instruction_sequence1 = bytes([1, 3])
    695 
    696    sequence1 = (
    697        EncodedAddressUnwind(0x400, complete_instruction_sequence1),
    698        EncodedAddressUnwind(0x0, complete_instruction_sequence0),
    699    )
    700 
    701    address_unwind_sequences = [sequence1]
    702 
    703    table, offsets = EncodeFunctionOffsetTable(
    704        address_unwind_sequences, {
    705            complete_instruction_sequence0: 52,
    706            complete_instruction_sequence1: 50,
    707        })
    708 
    709    self.assertEqual(
    710        bytes([
    711            # (0x200, 50)
    712            128,
    713            4,
    714            50,
    715            # (0, 52)
    716            0,
    717            52,
    718        ]),
    719        table)
    720 
    721    self.assertDictEqual({
    722        sequence1: 0,
    723    }, offsets)
    724 
    725  def testMultipleEntry(self):
    726    self.maxDiff = None
    727    complete_instruction_sequence0 = bytes([3])
    728    complete_instruction_sequence1 = bytes([1, 3])
    729    complete_instruction_sequence2 = bytes([2, 3])
    730 
    731    sequence1 = (
    732        EncodedAddressUnwind(0x20, complete_instruction_sequence1),
    733        EncodedAddressUnwind(0x0, complete_instruction_sequence0),
    734    )
    735    sequence2 = (
    736        EncodedAddressUnwind(0x400, complete_instruction_sequence2),
    737        EncodedAddressUnwind(0x0, complete_instruction_sequence0),
    738    )
    739    address_unwind_sequences = [sequence1, sequence2]
    740 
    741    table, offsets = EncodeFunctionOffsetTable(
    742        address_unwind_sequences, {
    743            complete_instruction_sequence0: 52,
    744            complete_instruction_sequence1: 50,
    745            complete_instruction_sequence2: 80,
    746        })
    747 
    748    self.assertEqual(
    749        bytes([
    750            # (0x10, 50)
    751            0x10,
    752            50,
    753            # (0, 52)
    754            0,
    755            52,
    756            # (0x200, 80)
    757            128,
    758            4,
    759            80,
    760            # (0, 52)
    761            0,
    762            52,
    763        ]),
    764        table)
    765 
    766    self.assertDictEqual({
    767        sequence1: 0,
    768        sequence2: 4,
    769    }, offsets)
    770 
    771  def testDuplicatedEntry(self):
    772    self.maxDiff = None
    773    complete_instruction_sequence0 = bytes([3])
    774    complete_instruction_sequence1 = bytes([1, 3])
    775    complete_instruction_sequence2 = bytes([2, 3])
    776 
    777    sequence1 = (
    778        EncodedAddressUnwind(0x20, complete_instruction_sequence1),
    779        EncodedAddressUnwind(0x0, complete_instruction_sequence0),
    780    )
    781    sequence2 = (
    782        EncodedAddressUnwind(0x400, complete_instruction_sequence2),
    783        EncodedAddressUnwind(0x0, complete_instruction_sequence0),
    784    )
    785    sequence3 = sequence1
    786 
    787    address_unwind_sequences = [sequence1, sequence2, sequence3]
    788 
    789    table, offsets = EncodeFunctionOffsetTable(
    790        address_unwind_sequences, {
    791            complete_instruction_sequence0: 52,
    792            complete_instruction_sequence1: 50,
    793            complete_instruction_sequence2: 80,
    794        })
    795 
    796    self.assertEqual(
    797        bytes([
    798            # (0x10, 50)
    799            0x10,
    800            50,
    801            # (0, 52)
    802            0,
    803            52,
    804            # (0x200, 80)
    805            128,
    806            4,
    807            80,
    808            # (0, 52)
    809            0,
    810            52,
    811        ]),
    812        table)
    813 
    814    self.assertDictEqual({
    815        sequence1: 0,
    816        sequence2: 4,
    817    }, offsets)
    818 
    819 
    820 class _TestEncodePageTableAndFunctionTable(unittest.TestCase):
    821  def testMultipleFunctionUnwinds(self):
    822    address_unwind_sequence0 = (
    823        EncodedAddressUnwind(0x10, bytes([0, 3])),
    824        EncodedAddressUnwind(0x0, bytes([3])),
    825    )
    826    address_unwind_sequence1 = (
    827        EncodedAddressUnwind(0x10, bytes([1, 3])),
    828        EncodedAddressUnwind(0x0, bytes([3])),
    829    )
    830    address_unwind_sequence2 = (
    831        EncodedAddressUnwind(0x200, bytes([2, 3])),
    832        EncodedAddressUnwind(0x0, bytes([3])),
    833    )
    834 
    835    function_unwinds = [
    836        EncodedFunctionUnwind(page_number=0,
    837                              page_offset=0,
    838                              address_unwinds=address_unwind_sequence0),
    839        EncodedFunctionUnwind(page_number=0,
    840                              page_offset=0x8000,
    841                              address_unwinds=address_unwind_sequence1),
    842        EncodedFunctionUnwind(page_number=1,
    843                              page_offset=0x8000,
    844                              address_unwinds=address_unwind_sequence2),
    845    ]
    846 
    847    function_offset_table_offsets = {
    848        address_unwind_sequence0: 0x100,
    849        address_unwind_sequence1: 0x200,
    850        address_unwind_sequence2: 0x300,
    851    }
    852 
    853    page_table, function_table = EncodePageTableAndFunctionTable(
    854        function_unwinds, function_offset_table_offsets)
    855 
    856    self.assertEqual(2 * 4, len(page_table))
    857    self.assertEqual((0, 2), struct.unpack('2I', page_table))
    858 
    859    self.assertEqual(6 * 2, len(function_table))
    860    self.assertEqual((0, 0x100, 0x8000, 0x200, 0x8000, 0x300),
    861                     struct.unpack('6H', function_table))
    862 
    863  def testMultiPageFunction(self):
    864    address_unwind_sequence0 = (
    865        EncodedAddressUnwind(0x10, bytes([0, 3])),
    866        EncodedAddressUnwind(0x0, bytes([3])),
    867    )
    868    address_unwind_sequence1 = (
    869        EncodedAddressUnwind(0x10, bytes([1, 3])),
    870        EncodedAddressUnwind(0x0, bytes([3])),
    871    )
    872    address_unwind_sequence2 = (
    873        EncodedAddressUnwind(0x200, bytes([2, 3])),
    874        EncodedAddressUnwind(0x0, bytes([3])),
    875    )
    876 
    877    function_unwinds = [
    878        EncodedFunctionUnwind(page_number=0,
    879                              page_offset=0,
    880                              address_unwinds=address_unwind_sequence0),
    881        # Large function.
    882        EncodedFunctionUnwind(page_number=0,
    883                              page_offset=0x8000,
    884                              address_unwinds=address_unwind_sequence1),
    885        EncodedFunctionUnwind(page_number=4,
    886                              page_offset=0x8000,
    887                              address_unwinds=address_unwind_sequence2),
    888    ]
    889 
    890    function_offset_table_offsets = {
    891        address_unwind_sequence0: 0x100,
    892        address_unwind_sequence1: 0x200,
    893        address_unwind_sequence2: 0x300,
    894    }
    895 
    896    page_table, function_table = EncodePageTableAndFunctionTable(
    897        function_unwinds, function_offset_table_offsets)
    898 
    899    self.assertEqual(5 * 4, len(page_table))
    900    self.assertEqual((0, 2, 2, 2, 2), struct.unpack('5I', page_table))
    901 
    902    self.assertEqual(6 * 2, len(function_table))
    903    self.assertEqual((0, 0x100, 0x8000, 0x200, 0x8000, 0x300),
    904                     struct.unpack('6H', function_table))
    905 
    906 
    907 class MockReturnParser(UnwindInstructionsParser):
    908  def GetBreakpadInstructionsRegex(self):
    909    return re.compile(r'^RETURN$')
    910 
    911  def ParseFromMatch(self, address_offset, cfa_sp_offset, match):
    912    return AddressUnwind(address_offset, UnwindType.RETURN_TO_LR, 0, ()), 0
    913 
    914 
    915 class MockEpilogueUnwindParser(UnwindInstructionsParser):
    916  def GetBreakpadInstructionsRegex(self):
    917    return re.compile(r'^EPILOGUE_UNWIND$')
    918 
    919  def ParseFromMatch(self, address_offset, cfa_sp_offset, match):
    920    return AddressUnwind(address_offset,
    921                         UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, 0, ()), -100
    922 
    923 
    924 class MockWildcardParser(UnwindInstructionsParser):
    925  def GetBreakpadInstructionsRegex(self):
    926    return re.compile(r'.*')
    927 
    928  def ParseFromMatch(self, address_offset, cfa_sp_offset, match):
    929    return AddressUnwind(address_offset,
    930                         UnwindType.UPDATE_SP_AND_OR_POP_REGISTERS, 0, ()), -200
    931 
    932 
    933 class _TestParseAddressCfi(unittest.TestCase):
    934  def testSuccessParse(self):
    935    address_unwind = AddressUnwind(
    936        address_offset=0x300,
    937        unwind_type=UnwindType.RETURN_TO_LR,
    938        sp_offset=0,
    939        registers=(),
    940    )
    941 
    942    self.assertEqual((address_unwind, False, 0),
    943                     ParseAddressCfi(AddressCfi(address=0x800,
    944                                                unwind_instructions='RETURN'),
    945                                     function_start_address=0x500,
    946                                     parsers=(MockReturnParser(), ),
    947                                     prev_cfa_sp_offset=0))
    948 
    949  def testUnhandledAddress(self):
    950    self.assertEqual((None, False, 100),
    951                     ParseAddressCfi(AddressCfi(address=0x800,
    952                                                unwind_instructions='UNKNOWN'),
    953                                     function_start_address=0x500,
    954                                     parsers=(MockReturnParser(), ),
    955                                     prev_cfa_sp_offset=100))
    956 
    957  def testEpilogueUnwind(self):
    958    self.assertEqual(
    959        (None, True, -100),
    960        ParseAddressCfi(AddressCfi(address=0x800,
    961                                   unwind_instructions='EPILOGUE_UNWIND'),
    962                        function_start_address=0x500,
    963                        parsers=(MockEpilogueUnwindParser(), ),
    964                        prev_cfa_sp_offset=100))
    965 
    966  def testParsePrecedence(self):
    967    address_unwind = AddressUnwind(
    968        address_offset=0x300,
    969        unwind_type=UnwindType.RETURN_TO_LR,
    970        sp_offset=0,
    971        registers=(),
    972    )
    973 
    974    self.assertEqual(
    975        (address_unwind, False, 0),
    976        ParseAddressCfi(AddressCfi(address=0x800, unwind_instructions='RETURN'),
    977                        function_start_address=0x500,
    978                        parsers=(MockReturnParser(), MockWildcardParser()),
    979                        prev_cfa_sp_offset=0))
    980 
    981 
    982 class _TestGenerateUnwinds(unittest.TestCase):
    983  def testSuccessUnwind(self):
    984    self.assertEqual(
    985        [
    986            FunctionUnwind(address=0x100,
    987                           size=1024,
    988                           address_unwinds=(
    989                               AddressUnwind(
    990                                   address_offset=0x0,
    991                                   unwind_type=UnwindType.RETURN_TO_LR,
    992                                   sp_offset=0,
    993                                   registers=(),
    994                               ),
    995                               AddressUnwind(
    996                                   address_offset=0x200,
    997                                   unwind_type=UnwindType.RETURN_TO_LR,
    998                                   sp_offset=0,
    999                                   registers=(),
   1000                               ),
   1001                           ))
   1002        ],
   1003        list(
   1004            GenerateUnwinds([
   1005                FunctionCfi(
   1006                    size=1024,
   1007                    address_cfi=(
   1008                        AddressCfi(address=0x100, unwind_instructions='RETURN'),
   1009                        AddressCfi(address=0x300, unwind_instructions='RETURN'),
   1010                    ))
   1011            ],
   1012                            parsers=[MockReturnParser()])))
   1013 
   1014  def testUnhandledAddress(self):
   1015    self.assertEqual(
   1016        [
   1017            FunctionUnwind(address=0x100,
   1018                           size=1024,
   1019                           address_unwinds=(AddressUnwind(
   1020                               address_offset=0x0,
   1021                               unwind_type=UnwindType.RETURN_TO_LR,
   1022                               sp_offset=0,
   1023                               registers=(),
   1024                           ), ))
   1025        ],
   1026        list(
   1027            GenerateUnwinds([
   1028                FunctionCfi(size=1024,
   1029                            address_cfi=(
   1030                                AddressCfi(address=0x100,
   1031                                           unwind_instructions='RETURN'),
   1032                                AddressCfi(address=0x300,
   1033                                           unwind_instructions='UNKNOWN'),
   1034                            ))
   1035            ],
   1036                            parsers=[MockReturnParser()])))
   1037 
   1038  def testEpilogueUnwind(self):
   1039    self.assertEqual(
   1040        [
   1041            FunctionUnwind(address=0x100,
   1042                           size=1024,
   1043                           address_unwinds=(AddressUnwind(
   1044                               address_offset=0x0,
   1045                               unwind_type=UnwindType.RETURN_TO_LR,
   1046                               sp_offset=0,
   1047                               registers=(),
   1048                           ), ))
   1049        ],
   1050        list(
   1051            GenerateUnwinds([
   1052                FunctionCfi(
   1053                    size=1024,
   1054                    address_cfi=(
   1055                        AddressCfi(address=0x100, unwind_instructions='RETURN'),
   1056                        AddressCfi(address=0x300,
   1057                                   unwind_instructions='EPILOGUE_UNWIND'),
   1058                    ))
   1059            ],
   1060                            parsers=[
   1061                                MockReturnParser(),
   1062                                MockEpilogueUnwindParser()
   1063                            ])))
   1064 
   1065  def testInvalidInitialUnwindInstructionAsserts(self):
   1066    self.assertRaises(
   1067        AssertionError, lambda: list(
   1068            GenerateUnwinds([
   1069                FunctionCfi(size=1024,
   1070                            address_cfi=(
   1071                                AddressCfi(address=0x100,
   1072                                           unwind_instructions='UNKNOWN'),
   1073                                AddressCfi(address=0x200,
   1074                                           unwind_instructions='RETURN'),
   1075                            ))
   1076            ],
   1077                            parsers=[MockReturnParser()])))
   1078 
   1079 
   1080 class _TestEncodeUnwindInfo(unittest.TestCase):
   1081  def testEncodeTables(self):
   1082    page_table = struct.pack('I', 0)
   1083    function_table = struct.pack('4H', 1, 2, 3, 4)
   1084    function_offset_table = bytes([1, 2])
   1085    unwind_instruction_table = bytes([1, 2, 3])
   1086 
   1087    unwind_info = EncodeUnwindInfo(
   1088        page_table,
   1089        function_table,
   1090        function_offset_table,
   1091        unwind_instruction_table,
   1092    )
   1093 
   1094    self.assertEqual(
   1095        32 + len(page_table) + len(function_table) +
   1096        len(function_offset_table) + len(unwind_instruction_table),
   1097        len(unwind_info))
   1098    # Header.
   1099    self.assertEqual((32, 1, 36, 2, 44, 2, 46, 3),
   1100                     struct.unpack('8I', unwind_info[:32]))
   1101    # Body.
   1102    self.assertEqual(
   1103        page_table + function_table + function_offset_table +
   1104        unwind_instruction_table, unwind_info[32:])
   1105 
   1106  def testUnalignedTables(self):
   1107    self.assertRaises(
   1108        AssertionError, lambda: EncodeUnwindInfo(bytes([1]), b'', b'', b''))
   1109    self.assertRaises(
   1110        AssertionError, lambda: EncodeUnwindInfo(b'', bytes([1]), b'', b''))
   1111 
   1112 
   1113 class _TestGenerateUnwindTables(unittest.TestCase):
   1114  def testGenerateUnwindTables(self):
   1115    """This is an integration test that hooks everything together. """
   1116    address_unwind_sequence0 = (
   1117        EncodedAddressUnwind(0x20, bytes([0, 0xb0])),
   1118        EncodedAddressUnwind(0x0, bytes([0xb0])),
   1119    )
   1120    address_unwind_sequence1 = (
   1121        EncodedAddressUnwind(0x20, bytes([1, 0xb0])),
   1122        EncodedAddressUnwind(0x0, bytes([0xb0])),
   1123    )
   1124    address_unwind_sequence2 = (
   1125        EncodedAddressUnwind(0x200, bytes([2, 0xb0])),
   1126        EncodedAddressUnwind(0x0, bytes([0xb0])),
   1127    )
   1128 
   1129    (page_table, function_table, function_offset_table,
   1130     unwind_instruction_table) = GenerateUnwindTables([
   1131         EncodedFunctionUnwind(page_number=0,
   1132                               page_offset=0,
   1133                               address_unwinds=TRIVIAL_UNWIND),
   1134         EncodedFunctionUnwind(page_number=0,
   1135                               page_offset=0x1000,
   1136                               address_unwinds=address_unwind_sequence0),
   1137         EncodedFunctionUnwind(page_number=1,
   1138                               page_offset=0x2000,
   1139                               address_unwinds=address_unwind_sequence1),
   1140         EncodedFunctionUnwind(page_number=3,
   1141                               page_offset=0x1000,
   1142                               address_unwinds=address_unwind_sequence2),
   1143     ])
   1144 
   1145    # Complete instruction sequences and their frequencies.
   1146    # [0xb0]: 4
   1147    # [0, 0xb0]: 1
   1148    # [1, 0xb0]: 1
   1149    # [2, 0xb0]: 1
   1150    self.assertEqual(bytes([0xb0, 2, 0xb0, 1, 0xb0, 0, 0xb0]),
   1151                     unwind_instruction_table)
   1152 
   1153    self.assertEqual(
   1154        bytes([
   1155            # Trivial unwind.
   1156            0,
   1157            0,
   1158            # Address unwind sequence 0.
   1159            0x10,
   1160            5,
   1161            0,
   1162            0,
   1163            # Address unwind sequence 1.
   1164            0x10,
   1165            3,
   1166            0,
   1167            0,
   1168            # Address unwind sequence 2.
   1169            0x80,
   1170            2,
   1171            1,
   1172            0,
   1173            0,
   1174        ]),
   1175        function_offset_table)
   1176 
   1177    self.assertEqual(8 * 2, len(function_table))
   1178    self.assertEqual((0, 0, 0x1000, 2, 0x2000, 6, 0x1000, 10),
   1179                     struct.unpack('8H', function_table))
   1180 
   1181    self.assertEqual(4 * 4, len(page_table))
   1182    self.assertEqual((0, 2, 3, 3), struct.unpack('4I', page_table))