Home | History | Annotate | Download | only in cocoa
      1 """
      2 Objective-C runtime wrapper for use by LLDB Python formatters
      3 
      4 part of The LLVM Compiler Infrastructure
      5 This file is distributed under the University of Illinois Open Source
      6 License. See LICENSE.TXT for details.
      7 """
      8 import lldb
      9 import lldb.formatters.cache
     10 import lldb.formatters.attrib_fromdict
     11 import functools
     12 import lldb.formatters.Logger
     13 
     14 class 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 
    113 class 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 
    167 class 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 
    204 class 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
    325 class 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
    433 TaggedClass_Values_Lion = {1 : 'NSNumber', \
    434                            5: 'NSManagedObject', \
    435                            6: 'NSDate', \
    436                            7: 'NSDateTS' };
    437 TaggedClass_Values_NMOS = {0: 'NSAtom', \
    438                            3 : 'NSNumber', \
    439                            4: 'NSDateTS', \
    440                            5: 'NSManagedObject', \
    441                            6: 'NSDate' };
    442 
    443 class 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 
    509 class InvalidClass_Data:
    510 	def __init__(self):
    511 		pass
    512 	def is_valid(self):
    513 		return 0
    514 
    515 
    516 class 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 
    566 runtime_version = lldb.formatters.cache.Cache()
    567 os_version = lldb.formatters.cache.Cache()
    568 types_caches = lldb.formatters.cache.Cache()
    569 isa_caches = lldb.formatters.cache.Cache()
    570 
    571 class 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 
    631 class 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
    756 class SpecialSituation_Description:
    757 	def message(self):
    758 		return ''
    759 
    760 class 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 
    771 class InvalidISA_Description(SpecialSituation_Description):
    772 
    773 	def __init__(self):
    774 		pass
    775 
    776 	def message(self):
    777 		return '<not an Objective-C object>'
    778 
    779 class ThisIsZombie_Description(SpecialSituation_Description):
    780 	def message(self):
    781 		return '<freed object>'