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