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