1""" 2Objective-C runtime wrapper for use by LLDB Python formatters 3 4Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5See https://llvm.org/LICENSE.txt for license information. 6SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7""" 8import lldb 9import lldb.formatters.cache 10import lldb.formatters.attrib_fromdict 11import functools 12import lldb.formatters.Logger 13 14 15class Utilities: 16 @staticmethod 17 def read_ascii(process, pointer, max_len=128): 18 logger = lldb.formatters.Logger.Logger() 19 error = lldb.SBError() 20 content = None 21 try: 22 content = process.ReadCStringFromMemory(pointer, max_len, error) 23 except: 24 pass 25 if content is None or len(content) == 0 or error.fail: 26 return None 27 return content 28 29 @staticmethod 30 def is_valid_pointer(pointer, pointer_size, allow_tagged=0, allow_NULL=0): 31 logger = lldb.formatters.Logger.Logger() 32 if pointer is None: 33 return 0 34 if pointer == 0: 35 return allow_NULL 36 if allow_tagged and (pointer % 2) == 1: 37 return 1 38 return (pointer % pointer_size) == 0 39 40 # Objective-C runtime has a rule that pointers in a class_t will only have bits 0 thru 46 set 41 # so if any pointer has bits 47 thru 63 high we know that this is not a 42 # valid isa 43 @staticmethod 44 def is_allowed_pointer(pointer): 45 logger = lldb.formatters.Logger.Logger() 46 if pointer is None: 47 return 0 48 return (pointer & 0xFFFF800000000000) == 0 49 50 @staticmethod 51 def read_child_of(valobj, offset, type): 52 logger = lldb.formatters.Logger.Logger() 53 if offset == 0 and type.GetByteSize() == valobj.GetByteSize(): 54 return valobj.GetValueAsUnsigned() 55 child = valobj.CreateChildAtOffset("childUNK", offset, type) 56 if child is None or child.IsValid() == 0: 57 return None 58 return child.GetValueAsUnsigned() 59 60 @staticmethod 61 def is_valid_identifier(name): 62 logger = lldb.formatters.Logger.Logger() 63 if name is None: 64 return None 65 if len(name) == 0: 66 return None 67 # technically, the ObjC runtime does not enforce any rules about what name a class can have 68 # in practice, the commonly used byte values for a class name are the letters, digits and some 69 # symbols: $, %, -, _, . 70 # WARNING: this means that you cannot use this runtime implementation if you need to deal 71 # with class names that use anything but what is allowed here 72 ok_values = dict.fromkeys( 73 "$%_.-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890" 74 ) 75 return all(c in ok_values for c in name) 76 77 @staticmethod 78 def check_is_osx_lion(target): 79 logger = lldb.formatters.Logger.Logger() 80 # assume the only thing that has a Foundation.framework is a Mac 81 # assume anything < Lion does not even exist 82 try: 83 mod = target.module["Foundation"] 84 except: 85 mod = None 86 if mod is None or mod.IsValid() == 0: 87 return None 88 ver = mod.GetVersion() 89 if ver is None or ver == []: 90 return None 91 return ver[0] < 900 92 93 # a utility method that factors out code common to almost all the formatters 94 # takes in an SBValue and a metrics object 95 # returns a class_data and a wrapper (or None, if the runtime alone can't 96 # decide on a wrapper) 97 @staticmethod 98 def prepare_class_detection(valobj, statistics): 99 logger = lldb.formatters.Logger.Logger() 100 class_data = ObjCRuntime(valobj) 101 if class_data.is_valid() == 0: 102 statistics.metric_hit("invalid_pointer", valobj) 103 wrapper = InvalidPointer_Description(valobj.GetValueAsUnsigned(0) == 0) 104 return class_data, wrapper 105 class_data = class_data.read_class_data() 106 if class_data.is_valid() == 0: 107 statistics.metric_hit("invalid_isa", valobj) 108 wrapper = InvalidISA_Description() 109 return class_data, wrapper 110 if class_data.is_kvo(): 111 class_data = class_data.get_superclass() 112 if class_data.class_name() == "_NSZombie_OriginalClass": 113 wrapper = ThisIsZombie_Description() 114 return class_data, wrapper 115 return class_data, None 116 117 118class RoT_Data: 119 def __init__(self, rot_pointer, params): 120 logger = lldb.formatters.Logger.Logger() 121 if Utilities.is_valid_pointer( 122 rot_pointer.GetValueAsUnsigned(), params.pointer_size, allow_tagged=0 123 ): 124 self.sys_params = params 125 self.valobj = rot_pointer 126 # self.flags = Utilities.read_child_of(self.valobj,0,self.sys_params.uint32_t) 127 # self.instanceStart = Utilities.read_child_of(self.valobj,4,self.sys_params.uint32_t) 128 self.instanceSize = None # lazy fetching 129 offset = 24 if self.sys_params.is_64_bit else 16 130 # self.ivarLayoutPtr = Utilities.read_child_of(self.valobj,offset,self.sys_params.addr_ptr_type) 131 self.namePointer = Utilities.read_child_of( 132 self.valobj, offset, self.sys_params.types_cache.addr_ptr_type 133 ) 134 self.valid = 1 # self.check_valid() 135 else: 136 logger >> "Marking as invalid - rot is invalid" 137 self.valid = 0 138 if self.valid: 139 self.name = Utilities.read_ascii( 140 self.valobj.GetTarget().GetProcess(), self.namePointer 141 ) 142 if not (Utilities.is_valid_identifier(self.name)): 143 logger >> "Marking as invalid - name is invalid" 144 self.valid = 0 145 146 # perform sanity checks on the contents of this class_ro_t 147 def check_valid(self): 148 self.valid = 1 149 # misaligned pointers seem to be possible for this field 150 # if not(Utilities.is_valid_pointer(self.namePointer,self.sys_params.pointer_size,allow_tagged=0)): 151 # self.valid = 0 152 # pass 153 154 def __str__(self): 155 logger = lldb.formatters.Logger.Logger() 156 return ( 157 "instanceSize = " 158 + hex(self.instance_size()) 159 + "\n" 160 + "namePointer = " 161 + hex(self.namePointer) 162 + " --> " 163 + self.name 164 ) 165 166 def is_valid(self): 167 return self.valid 168 169 def instance_size(self, align=0): 170 logger = lldb.formatters.Logger.Logger() 171 if self.is_valid() == 0: 172 return None 173 if self.instanceSize is None: 174 self.instanceSize = Utilities.read_child_of( 175 self.valobj, 8, self.sys_params.types_cache.uint32_t 176 ) 177 if align: 178 unalign = self.instance_size(0) 179 if self.sys_params.is_64_bit: 180 return ((unalign + 7) & ~7) % 0x100000000 181 else: 182 return ((unalign + 3) & ~3) % 0x100000000 183 else: 184 return self.instanceSize 185 186 187class RwT_Data: 188 def __init__(self, rwt_pointer, params): 189 logger = lldb.formatters.Logger.Logger() 190 if Utilities.is_valid_pointer( 191 rwt_pointer.GetValueAsUnsigned(), params.pointer_size, allow_tagged=0 192 ): 193 self.sys_params = params 194 self.valobj = rwt_pointer 195 # self.flags = Utilities.read_child_of(self.valobj,0,self.sys_params.uint32_t) 196 # self.version = Utilities.read_child_of(self.valobj,4,self.sys_params.uint32_t) 197 self.roPointer = Utilities.read_child_of( 198 self.valobj, 8, self.sys_params.types_cache.addr_ptr_type 199 ) 200 self.check_valid() 201 else: 202 logger >> "Marking as invalid - rwt is invald" 203 self.valid = 0 204 if self.valid: 205 self.rot = self.valobj.CreateValueFromData( 206 "rot", 207 lldb.SBData.CreateDataFromUInt64Array( 208 self.sys_params.endianness, 209 self.sys_params.pointer_size, 210 [self.roPointer], 211 ), 212 self.sys_params.types_cache.addr_ptr_type, 213 ) 214 # self.rot = self.valobj.CreateValueFromAddress("rot",self.roPointer,self.sys_params.types_cache.addr_ptr_type).AddressOf() 215 self.data = RoT_Data(self.rot, self.sys_params) 216 217 # perform sanity checks on the contents of this class_rw_t 218 def check_valid(self): 219 logger = lldb.formatters.Logger.Logger() 220 self.valid = 1 221 if not ( 222 Utilities.is_valid_pointer( 223 self.roPointer, self.sys_params.pointer_size, allow_tagged=0 224 ) 225 ): 226 logger >> "Marking as invalid - ropointer is invalid" 227 self.valid = 0 228 229 def __str__(self): 230 logger = lldb.formatters.Logger.Logger() 231 return "roPointer = " + hex(self.roPointer) 232 233 def is_valid(self): 234 logger = lldb.formatters.Logger.Logger() 235 if self.valid: 236 return self.data.is_valid() 237 return 0 238 239 240class Class_Data_V2: 241 def __init__(self, isa_pointer, params): 242 logger = lldb.formatters.Logger.Logger() 243 if (isa_pointer is not None) and ( 244 Utilities.is_valid_pointer( 245 isa_pointer.GetValueAsUnsigned(), params.pointer_size, allow_tagged=0 246 ) 247 ): 248 self.sys_params = params 249 self.valobj = isa_pointer 250 self.check_valid() 251 else: 252 logger >> "Marking as invalid - isa is invalid or None" 253 self.valid = 0 254 if self.valid: 255 self.rwt = self.valobj.CreateValueFromData( 256 "rwt", 257 lldb.SBData.CreateDataFromUInt64Array( 258 self.sys_params.endianness, 259 self.sys_params.pointer_size, 260 [self.dataPointer], 261 ), 262 self.sys_params.types_cache.addr_ptr_type, 263 ) 264 # self.rwt = self.valobj.CreateValueFromAddress("rwt",self.dataPointer,self.sys_params.types_cache.addr_ptr_type).AddressOf() 265 self.data = RwT_Data(self.rwt, self.sys_params) 266 267 # perform sanity checks on the contents of this class_t 268 # this call tries to minimize the amount of data fetched- as soon as we have "proven" 269 # that we have an invalid object, we stop reading 270 def check_valid(self): 271 logger = lldb.formatters.Logger.Logger() 272 self.valid = 1 273 274 self.isaPointer = Utilities.read_child_of( 275 self.valobj, 0, self.sys_params.types_cache.addr_ptr_type 276 ) 277 if not ( 278 Utilities.is_valid_pointer( 279 self.isaPointer, self.sys_params.pointer_size, allow_tagged=0 280 ) 281 ): 282 logger >> "Marking as invalid - isaPointer is invalid" 283 self.valid = 0 284 return 285 if not (Utilities.is_allowed_pointer(self.isaPointer)): 286 logger >> "Marking as invalid - isaPointer is not allowed" 287 self.valid = 0 288 return 289 290 self.cachePointer = Utilities.read_child_of( 291 self.valobj, 292 2 * self.sys_params.pointer_size, 293 self.sys_params.types_cache.addr_ptr_type, 294 ) 295 if not ( 296 Utilities.is_valid_pointer( 297 self.cachePointer, self.sys_params.pointer_size, allow_tagged=0 298 ) 299 ): 300 logger >> "Marking as invalid - cachePointer is invalid" 301 self.valid = 0 302 return 303 if not (Utilities.is_allowed_pointer(self.cachePointer)): 304 logger >> "Marking as invalid - cachePointer is not allowed" 305 self.valid = 0 306 return 307 self.dataPointer = Utilities.read_child_of( 308 self.valobj, 309 4 * self.sys_params.pointer_size, 310 self.sys_params.types_cache.addr_ptr_type, 311 ) 312 if not ( 313 Utilities.is_valid_pointer( 314 self.dataPointer, self.sys_params.pointer_size, allow_tagged=0 315 ) 316 ): 317 logger >> "Marking as invalid - dataPointer is invalid" 318 self.valid = 0 319 return 320 if not (Utilities.is_allowed_pointer(self.dataPointer)): 321 logger >> "Marking as invalid - dataPointer is not allowed" 322 self.valid = 0 323 return 324 325 self.superclassIsaPointer = Utilities.read_child_of( 326 self.valobj, 327 1 * self.sys_params.pointer_size, 328 self.sys_params.types_cache.addr_ptr_type, 329 ) 330 if not ( 331 Utilities.is_valid_pointer( 332 self.superclassIsaPointer, 333 self.sys_params.pointer_size, 334 allow_tagged=0, 335 allow_NULL=1, 336 ) 337 ): 338 logger >> "Marking as invalid - superclassIsa is invalid" 339 self.valid = 0 340 return 341 if not (Utilities.is_allowed_pointer(self.superclassIsaPointer)): 342 logger >> "Marking as invalid - superclassIsa is not allowed" 343 self.valid = 0 344 return 345 346 # in general, KVO is implemented by transparently subclassing 347 # however, there could be exceptions where a class does something else 348 # internally to implement the feature - this method will have no clue that a class 349 # has been KVO'ed unless the standard implementation technique is used 350 def is_kvo(self): 351 logger = lldb.formatters.Logger.Logger() 352 if self.is_valid(): 353 if self.class_name().startswith("NSKVONotifying_"): 354 return 1 355 return 0 356 357 # some CF classes have a valid ObjC isa in their CFRuntimeBase 358 # but instead of being class-specific this isa points to a match-'em-all class 359 # which is __NSCFType (the versions without __ also exists and we are matching to it 360 # just to be on the safe side) 361 def is_cftype(self): 362 logger = lldb.formatters.Logger.Logger() 363 if self.is_valid(): 364 return self.class_name() == "__NSCFType" or self.class_name() == "NSCFType" 365 366 def get_superclass(self): 367 logger = lldb.formatters.Logger.Logger() 368 if self.is_valid(): 369 parent_isa_pointer = self.valobj.CreateChildAtOffset( 370 "parent_isa", 371 self.sys_params.pointer_size, 372 self.sys_params.addr_ptr_type, 373 ) 374 return Class_Data_V2(parent_isa_pointer, self.sys_params) 375 else: 376 return None 377 378 def class_name(self): 379 logger = lldb.formatters.Logger.Logger() 380 if self.is_valid(): 381 return self.data.data.name 382 else: 383 return None 384 385 def is_valid(self): 386 logger = lldb.formatters.Logger.Logger() 387 if self.valid: 388 return self.data.is_valid() 389 return 0 390 391 def __str__(self): 392 logger = lldb.formatters.Logger.Logger() 393 return ( 394 "isaPointer = " 395 + hex(self.isaPointer) 396 + "\n" 397 + "superclassIsaPointer = " 398 + hex(self.superclassIsaPointer) 399 + "\n" 400 + "cachePointer = " 401 + hex(self.cachePointer) 402 + "\n" 403 + "data = " 404 + hex(self.dataPointer) 405 ) 406 407 def is_tagged(self): 408 return 0 409 410 def instance_size(self, align=0): 411 logger = lldb.formatters.Logger.Logger() 412 if self.is_valid() == 0: 413 return None 414 return self.rwt.rot.instance_size(align) 415 416 417# runtime v1 is much less intricate than v2 and stores relevant 418# information directly in the class_t object 419 420 421class Class_Data_V1: 422 def __init__(self, isa_pointer, params): 423 logger = lldb.formatters.Logger.Logger() 424 if (isa_pointer is not None) and ( 425 Utilities.is_valid_pointer( 426 isa_pointer.GetValueAsUnsigned(), params.pointer_size, allow_tagged=0 427 ) 428 ): 429 self.valid = 1 430 self.sys_params = params 431 self.valobj = isa_pointer 432 self.check_valid() 433 else: 434 logger >> "Marking as invalid - isaPointer is invalid or None" 435 self.valid = 0 436 if self.valid: 437 self.name = Utilities.read_ascii( 438 self.valobj.GetTarget().GetProcess(), self.namePointer 439 ) 440 if not (Utilities.is_valid_identifier(self.name)): 441 logger >> "Marking as invalid - name is not valid" 442 self.valid = 0 443 444 # perform sanity checks on the contents of this class_t 445 def check_valid(self): 446 logger = lldb.formatters.Logger.Logger() 447 self.valid = 1 448 449 self.isaPointer = Utilities.read_child_of( 450 self.valobj, 0, self.sys_params.types_cache.addr_ptr_type 451 ) 452 if not ( 453 Utilities.is_valid_pointer( 454 self.isaPointer, self.sys_params.pointer_size, allow_tagged=0 455 ) 456 ): 457 logger >> "Marking as invalid - isaPointer is invalid" 458 self.valid = 0 459 return 460 461 self.superclassIsaPointer = Utilities.read_child_of( 462 self.valobj, 463 1 * self.sys_params.pointer_size, 464 self.sys_params.types_cache.addr_ptr_type, 465 ) 466 if not ( 467 Utilities.is_valid_pointer( 468 self.superclassIsaPointer, 469 self.sys_params.pointer_size, 470 allow_tagged=0, 471 allow_NULL=1, 472 ) 473 ): 474 logger >> "Marking as invalid - superclassIsa is invalid" 475 self.valid = 0 476 return 477 478 self.namePointer = Utilities.read_child_of( 479 self.valobj, 480 2 * self.sys_params.pointer_size, 481 self.sys_params.types_cache.addr_ptr_type, 482 ) 483 # if not(Utilities.is_valid_pointer(self.namePointer,self.sys_params.pointer_size,allow_tagged=0,allow_NULL=0)): 484 # self.valid = 0 485 # return 486 487 # in general, KVO is implemented by transparently subclassing 488 # however, there could be exceptions where a class does something else 489 # internally to implement the feature - this method will have no clue that a class 490 # has been KVO'ed unless the standard implementation technique is used 491 def is_kvo(self): 492 logger = lldb.formatters.Logger.Logger() 493 if self.is_valid(): 494 if self.class_name().startswith("NSKVONotifying_"): 495 return 1 496 return 0 497 498 # some CF classes have a valid ObjC isa in their CFRuntimeBase 499 # but instead of being class-specific this isa points to a match-'em-all class 500 # which is __NSCFType (the versions without __ also exists and we are matching to it 501 # just to be on the safe side) 502 def is_cftype(self): 503 logger = lldb.formatters.Logger.Logger() 504 if self.is_valid(): 505 return self.class_name() == "__NSCFType" or self.class_name() == "NSCFType" 506 507 def get_superclass(self): 508 logger = lldb.formatters.Logger.Logger() 509 if self.is_valid(): 510 parent_isa_pointer = self.valobj.CreateChildAtOffset( 511 "parent_isa", 512 self.sys_params.pointer_size, 513 self.sys_params.addr_ptr_type, 514 ) 515 return Class_Data_V1(parent_isa_pointer, self.sys_params) 516 else: 517 return None 518 519 def class_name(self): 520 logger = lldb.formatters.Logger.Logger() 521 if self.is_valid(): 522 return self.name 523 else: 524 return None 525 526 def is_valid(self): 527 return self.valid 528 529 def __str__(self): 530 logger = lldb.formatters.Logger.Logger() 531 return ( 532 "isaPointer = " 533 + hex(self.isaPointer) 534 + "\n" 535 + "superclassIsaPointer = " 536 + hex(self.superclassIsaPointer) 537 + "\n" 538 + "namePointer = " 539 + hex(self.namePointer) 540 + " --> " 541 + self.name 542 + "instanceSize = " 543 + hex(self.instanceSize()) 544 + "\n" 545 ) 546 547 def is_tagged(self): 548 return 0 549 550 def instance_size(self, align=0): 551 logger = lldb.formatters.Logger.Logger() 552 if self.is_valid() == 0: 553 return None 554 if self.instanceSize is None: 555 self.instanceSize = Utilities.read_child_of( 556 self.valobj, 557 5 * self.sys_params.pointer_size, 558 self.sys_params.types_cache.addr_ptr_type, 559 ) 560 if align: 561 unalign = self.instance_size(0) 562 if self.sys_params.is_64_bit: 563 return ((unalign + 7) & ~7) % 0x100000000 564 else: 565 return ((unalign + 3) & ~3) % 0x100000000 566 else: 567 return self.instanceSize 568 569 570# these are the only tagged pointers values for current versions 571# of OSX - they might change in future OS releases, and no-one is 572# advised to rely on these values, or any of the bitmasking formulas 573# in TaggedClass_Data. doing otherwise is at your own risk 574TaggedClass_Values_Lion = { 575 1: "NSNumber", 576 5: "NSManagedObject", 577 6: "NSDate", 578 7: "NSDateTS", 579} 580TaggedClass_Values_NMOS = { 581 0: "NSAtom", 582 3: "NSNumber", 583 4: "NSDateTS", 584 5: "NSManagedObject", 585 6: "NSDate", 586} 587 588 589class TaggedClass_Data: 590 def __init__(self, pointer, params): 591 logger = lldb.formatters.Logger.Logger() 592 global TaggedClass_Values_Lion, TaggedClass_Values_NMOS 593 self.valid = 1 594 self.name = None 595 self.sys_params = params 596 self.valobj = pointer 597 self.val = (pointer & ~0x0000000000000000FF) >> 8 598 self.class_bits = (pointer & 0xE) >> 1 599 self.i_bits = (pointer & 0xF0) >> 4 600 601 if self.sys_params.is_lion: 602 if self.class_bits in TaggedClass_Values_Lion: 603 self.name = TaggedClass_Values_Lion[self.class_bits] 604 else: 605 logger >> "Marking as invalid - not a good tagged pointer for Lion" 606 self.valid = 0 607 else: 608 if self.class_bits in TaggedClass_Values_NMOS: 609 self.name = TaggedClass_Values_NMOS[self.class_bits] 610 else: 611 logger >> "Marking as invalid - not a good tagged pointer for NMOS" 612 self.valid = 0 613 614 def is_valid(self): 615 return self.valid 616 617 def class_name(self): 618 logger = lldb.formatters.Logger.Logger() 619 if self.is_valid(): 620 return self.name 621 else: 622 return 0 623 624 def value(self): 625 return self.val if self.is_valid() else None 626 627 def info_bits(self): 628 return self.i_bits if self.is_valid() else None 629 630 def is_kvo(self): 631 return 0 632 633 def is_cftype(self): 634 return 0 635 636 # we would need to go around looking for the superclass or ask the runtime 637 # for now, we seem not to require support for this operation so we will merrily 638 # pretend to be at a root point in the hierarchy 639 def get_superclass(self): 640 return None 641 642 # anything that is handled here is tagged 643 def is_tagged(self): 644 return 1 645 646 # it seems reasonable to say that a tagged pointer is the size of a pointer 647 def instance_size(self, align=0): 648 logger = lldb.formatters.Logger.Logger() 649 if self.is_valid() == 0: 650 return None 651 return self.sys_params.pointer_size 652 653 654class InvalidClass_Data: 655 def __init__(self): 656 pass 657 658 def is_valid(self): 659 return 0 660 661 662class Version: 663 def __init__(self, major, minor, release, build_string): 664 self._major = major 665 self._minor = minor 666 self._release = release 667 self._build_string = build_string 668 669 def get_major(self): 670 return self._major 671 672 def get_minor(self): 673 return self._minor 674 675 def get_release(self): 676 return self._release 677 678 def get_build_string(self): 679 return self._build_string 680 681 major = property(get_major, None) 682 minor = property(get_minor, None) 683 release = property(get_release, None) 684 build_string = property(get_build_string, None) 685 686 def __lt__(self, other): 687 if self.major < other.major: 688 return 1 689 if self.minor < other.minor: 690 return 1 691 if self.release < other.release: 692 return 1 693 # build strings are not compared since they are heavily platform-dependent and might not always 694 # be available 695 return 0 696 697 def __eq__(self, other): 698 return ( 699 (self.major == other.major) 700 and (self.minor == other.minor) 701 and (self.release == other.release) 702 and (self.build_string == other.build_string) 703 ) 704 705 # Python 2.6 doesn't have functools.total_ordering, so we have to implement 706 # other comparators 707 def __gt__(self, other): 708 return other < self 709 710 def __le__(self, other): 711 return not other < self 712 713 def __ge__(self, other): 714 return not self < other 715 716 717runtime_version = lldb.formatters.cache.Cache() 718os_version = lldb.formatters.cache.Cache() 719types_caches = lldb.formatters.cache.Cache() 720isa_caches = lldb.formatters.cache.Cache() 721 722 723class SystemParameters: 724 def __init__(self, valobj): 725 logger = lldb.formatters.Logger.Logger() 726 self.adjust_for_architecture(valobj) 727 self.adjust_for_process(valobj) 728 729 def adjust_for_process(self, valobj): 730 logger = lldb.formatters.Logger.Logger() 731 global runtime_version 732 global os_version 733 global types_caches 734 global isa_caches 735 736 process = valobj.GetTarget().GetProcess() 737 # using the unique ID for added guarantees (see svn revision 172628 for 738 # further details) 739 self.pid = process.GetUniqueID() 740 741 if runtime_version.look_for_key(self.pid): 742 self.runtime_version = runtime_version.get_value(self.pid) 743 else: 744 self.runtime_version = ObjCRuntime.runtime_version(process) 745 runtime_version.add_item(self.pid, self.runtime_version) 746 747 if os_version.look_for_key(self.pid): 748 self.is_lion = os_version.get_value(self.pid) 749 else: 750 self.is_lion = Utilities.check_is_osx_lion(valobj.GetTarget()) 751 os_version.add_item(self.pid, self.is_lion) 752 753 if types_caches.look_for_key(self.pid): 754 self.types_cache = types_caches.get_value(self.pid) 755 else: 756 self.types_cache = lldb.formatters.attrib_fromdict.AttributesDictionary( 757 allow_reset=0 758 ) 759 self.types_cache.addr_type = valobj.GetType().GetBasicType( 760 lldb.eBasicTypeUnsignedLong 761 ) 762 self.types_cache.addr_ptr_type = self.types_cache.addr_type.GetPointerType() 763 self.types_cache.uint32_t = valobj.GetType().GetBasicType( 764 lldb.eBasicTypeUnsignedInt 765 ) 766 types_caches.add_item(self.pid, self.types_cache) 767 768 if isa_caches.look_for_key(self.pid): 769 self.isa_cache = isa_caches.get_value(self.pid) 770 else: 771 self.isa_cache = lldb.formatters.cache.Cache() 772 isa_caches.add_item(self.pid, self.isa_cache) 773 774 def adjust_for_architecture(self, valobj): 775 process = valobj.GetTarget().GetProcess() 776 self.pointer_size = process.GetAddressByteSize() 777 self.is_64_bit = self.pointer_size == 8 778 self.endianness = process.GetByteOrder() 779 self.is_little = self.endianness == lldb.eByteOrderLittle 780 self.cfruntime_size = 16 if self.is_64_bit else 8 781 782 # a simple helper function that makes it more explicit that one is calculating 783 # an offset that is made up of X pointers and Y bytes of additional data 784 # taking into account pointer size - if you know there is going to be some padding 785 # you can pass that in and it will be taken into account (since padding may be different between 786 # 32 and 64 bit versions, you can pass padding value for both, the right 787 # one will be used) 788 def calculate_offset(self, num_pointers=0, bytes_count=0, padding32=0, padding64=0): 789 value = bytes_count + num_pointers * self.pointer_size 790 return value + padding64 if self.is_64_bit else value + padding32 791 792 793class ObjCRuntime: 794 # the ObjC runtime has no explicit "version" field that we can use 795 # instead, we discriminate v1 from v2 by looking for the presence 796 # of a well-known section only present in v1 797 @staticmethod 798 def runtime_version(process): 799 logger = lldb.formatters.Logger.Logger() 800 if process.IsValid() == 0: 801 logger >> "No process - bailing out" 802 return None 803 target = process.GetTarget() 804 num_modules = target.GetNumModules() 805 module_objc = None 806 for idx in range(num_modules): 807 module = target.GetModuleAtIndex(idx) 808 if module.GetFileSpec().GetFilename() == "libobjc.A.dylib": 809 module_objc = module 810 break 811 if module_objc is None or module_objc.IsValid() == 0: 812 logger >> "no libobjc - bailing out" 813 return None 814 num_sections = module.GetNumSections() 815 section_objc = None 816 for idx in range(num_sections): 817 section = module.GetSectionAtIndex(idx) 818 if section.GetName() == "__OBJC": 819 section_objc = section 820 break 821 if section_objc is not None and section_objc.IsValid(): 822 logger >> "found __OBJC: v1" 823 return 1 824 logger >> "no __OBJC: v2" 825 return 2 826 827 @staticmethod 828 def runtime_from_isa(isa): 829 logger = lldb.formatters.Logger.Logger() 830 runtime = ObjCRuntime(isa) 831 runtime.isa = isa 832 return runtime 833 834 def __init__(self, valobj): 835 logger = lldb.formatters.Logger.Logger() 836 self.valobj = valobj 837 self.adjust_for_architecture() 838 self.sys_params = SystemParameters(self.valobj) 839 self.unsigned_value = self.valobj.GetValueAsUnsigned() 840 self.isa_value = None 841 842 def adjust_for_architecture(self): 843 pass 844 845 # an ObjC pointer can either be tagged or must be aligned 846 def is_tagged(self): 847 logger = lldb.formatters.Logger.Logger() 848 if self.valobj is None: 849 return 0 850 return Utilities.is_valid_pointer( 851 self.unsigned_value, self.sys_params.pointer_size, allow_tagged=1 852 ) and not ( 853 Utilities.is_valid_pointer( 854 self.unsigned_value, self.sys_params.pointer_size, allow_tagged=0 855 ) 856 ) 857 858 def is_valid(self): 859 logger = lldb.formatters.Logger.Logger() 860 if self.valobj is None: 861 return 0 862 if self.valobj.IsInScope() == 0: 863 return 0 864 return Utilities.is_valid_pointer( 865 self.unsigned_value, self.sys_params.pointer_size, allow_tagged=1 866 ) 867 868 def is_nil(self): 869 return self.unsigned_value == 0 870 871 def read_isa(self): 872 logger = lldb.formatters.Logger.Logger() 873 if self.isa_value is not None: 874 logger >> "using cached isa" 875 return self.isa_value 876 self.isa_pointer = self.valobj.CreateChildAtOffset( 877 "cfisa", 0, self.sys_params.types_cache.addr_ptr_type 878 ) 879 if self.isa_pointer is None or self.isa_pointer.IsValid() == 0: 880 logger >> "invalid isa - bailing out" 881 return None 882 self.isa_value = self.isa_pointer.GetValueAsUnsigned(1) 883 if self.isa_value == 1: 884 logger >> "invalid isa value - bailing out" 885 return None 886 return Ellipsis 887 888 def read_class_data(self): 889 logger = lldb.formatters.Logger.Logger() 890 global isa_cache 891 if self.is_tagged(): 892 # tagged pointers only exist in ObjC v2 893 if self.sys_params.runtime_version == 2: 894 logger >> "on v2 and tagged - maybe" 895 # not every odd-valued pointer is actually tagged. most are just plain wrong 896 # we could try and predetect this before even creating a TaggedClass_Data object 897 # but unless performance requires it, this seems a cleaner way 898 # to tackle the task 899 tentative_tagged = TaggedClass_Data( 900 self.unsigned_value, self.sys_params 901 ) 902 if tentative_tagged.is_valid(): 903 logger >> "truly tagged" 904 return tentative_tagged 905 else: 906 logger >> "not tagged - error" 907 return InvalidClass_Data() 908 else: 909 logger >> "on v1 and tagged - error" 910 return InvalidClass_Data() 911 if self.is_valid() == 0 or self.read_isa() is None: 912 return InvalidClass_Data() 913 data = self.sys_params.isa_cache.get_value(self.isa_value, default=None) 914 if data is not None: 915 return data 916 if self.sys_params.runtime_version == 2: 917 data = Class_Data_V2(self.isa_pointer, self.sys_params) 918 else: 919 data = Class_Data_V1(self.isa_pointer, self.sys_params) 920 if data is None: 921 return InvalidClass_Data() 922 if data.is_valid(): 923 self.sys_params.isa_cache.add_item(self.isa_value, data, ok_to_replace=1) 924 return data 925 926 927# these classes below can be used by the data formatters to provide a 928# consistent message that describes a given runtime-generated situation 929 930 931class SpecialSituation_Description: 932 def message(self): 933 return "" 934 935 936class InvalidPointer_Description(SpecialSituation_Description): 937 def __init__(self, nil): 938 self.is_nil = nil 939 940 def message(self): 941 if self.is_nil: 942 return '@"<nil>"' 943 else: 944 return "<invalid pointer>" 945 946 947class InvalidISA_Description(SpecialSituation_Description): 948 def __init__(self): 949 pass 950 951 def message(self): 952 return "<not an Objective-C object>" 953 954 955class ThisIsZombie_Description(SpecialSituation_Description): 956 def message(self): 957 return "<freed object>" 958