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