xref: /aosp_15_r20/external/cronet/build/android/gyp/create_unwind_table_tests.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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
7This test suite contains tests for the custom unwind table creation for 32-bit
8arm builds.
9"""
10
11import io
12import struct
13
14import unittest
15import unittest.mock
16import re
17
18from 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
30class _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
105class _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
117class _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
132class _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
168class _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
200class _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
269class _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
297PAGE_SIZE = 1 << 17
298
299
300class _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
412class _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
430class _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
590class _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
627class _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
645class _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
690class _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
820class _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
907class 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
915class 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
924class 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
933class _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
982class _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
1080class _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
1113class _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))
1183