summaryrefslogtreecommitdiffstats
path: root/lldb/examples/summaries/cocoa/objc_runtime.py
diff options
context:
space:
mode:
Diffstat (limited to 'lldb/examples/summaries/cocoa/objc_runtime.py')
-rw-r--r--lldb/examples/summaries/cocoa/objc_runtime.py266
1 files changed, 181 insertions, 85 deletions
diff --git a/lldb/examples/summaries/cocoa/objc_runtime.py b/lldb/examples/summaries/cocoa/objc_runtime.py
index 0af07f0a3be..ddad00a2230 100644
--- a/lldb/examples/summaries/cocoa/objc_runtime.py
+++ b/lldb/examples/summaries/cocoa/objc_runtime.py
@@ -1,6 +1,14 @@
-# a wrapper for the Objective-C runtime for use by LLDB
+"""
+Objective-C runtime wrapper for use by LLDB Python formatters
+
+part of The LLVM Compiler Infrastructure
+This file is distributed under the University of Illinois Open Source
+License. See LICENSE.TXT for details.
+"""
import lldb
import cache
+import attrib_fromdict
+import functools
class Utilities:
@staticmethod
@@ -72,11 +80,11 @@ class RoT_Data:
self.sys_params = params
self.valobj = rot_pointer
#self.flags = Utilities.read_child_of(self.valobj,0,self.sys_params.uint32_t)
- self.instanceStart = Utilities.read_child_of(self.valobj,4,self.sys_params.uint32_t)
- self.instanceSize = Utilities.read_child_of(self.valobj,8,self.sys_params.uint32_t)
+ #self.instanceStart = Utilities.read_child_of(self.valobj,4,self.sys_params.uint32_t)
+ self.instanceSize = None # lazy fetching
offset = 24 if self.sys_params.is_64_bit else 16
#self.ivarLayoutPtr = Utilities.read_child_of(self.valobj,offset,self.sys_params.addr_ptr_type)
- self.namePointer = Utilities.read_child_of(self.valobj,offset,self.sys_params.addr_ptr_type)
+ self.namePointer = Utilities.read_child_of(self.valobj,offset,self.sys_params.types_cache.addr_ptr_type)
self.check_valid()
else:
self.valid = False
@@ -85,7 +93,7 @@ class RoT_Data:
if not(Utilities.is_valid_identifier(self.name)):
self.valid = False
- # perform sanity checks on the contents of this class_rw_t
+ # perform sanity checks on the contents of this class_ro_t
def check_valid(self):
self.valid = True
# misaligned pointers seem to be possible for this field
@@ -95,13 +103,25 @@ class RoT_Data:
def __str__(self):
return \
- "instanceStart = " + hex(self.instanceStart) + "\n" + \
- "instanceSize = " + hex(self.instanceSize) + "\n" + \
+ "instanceSize = " + hex(self.instance_size()) + "\n" + \
"namePointer = " + hex(self.namePointer) + " --> " + self.name
def is_valid(self):
return self.valid
+ def instance_size(self,align=False):
+ if self.is_valid() == False:
+ return None
+ if self.instanceSize == None:
+ self.instanceSize = Utilities.read_child_of(self.valobj,8,self.sys_params.types_cache.uint32_t)
+ if align:
+ unalign = self.instance_size(False)
+ if self.sys_params.is_64_bit:
+ return ((unalign + 7) & ~7) % 0x100000000
+ else:
+ return ((unalign + 3) & ~3) % 0x100000000
+ else:
+ return self.instanceSize
class RwT_Data:
def __init__(self,rwt_pointer,params):
@@ -110,12 +130,12 @@ class RwT_Data:
self.valobj = rwt_pointer
#self.flags = Utilities.read_child_of(self.valobj,0,self.sys_params.uint32_t)
#self.version = Utilities.read_child_of(self.valobj,4,self.sys_params.uint32_t)
- self.roPointer = Utilities.read_child_of(self.valobj,8,self.sys_params.addr_ptr_type)
+ self.roPointer = Utilities.read_child_of(self.valobj,8,self.sys_params.types_cache.addr_ptr_type)
self.check_valid()
else:
self.valid = False
if self.valid:
- self.rot = self.valobj.CreateValueFromAddress("rot",self.roPointer,self.sys_params.addr_ptr_type).AddressOf()
+ self.rot = self.valobj.CreateValueFromAddress("rot",self.roPointer,self.sys_params.types_cache.addr_ptr_type).AddressOf()
self.data = RoT_Data(self.rot,self.sys_params)
# perform sanity checks on the contents of this class_rw_t
@@ -138,53 +158,56 @@ class Class_Data_V2:
if (isa_pointer != None) and (Utilities.is_valid_pointer(isa_pointer.GetValueAsUnsigned(),params.pointer_size, allow_tagged=False)):
self.sys_params = params
self.valobj = isa_pointer
- self.isaPointer = Utilities.read_child_of(self.valobj,0,self.sys_params.addr_ptr_type)
- self.superclassIsaPointer = Utilities.read_child_of(self.valobj,1*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
- self.cachePointer = Utilities.read_child_of(self.valobj,2*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
- self.vtablePointer = Utilities.read_child_of(self.valobj,3*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
- self.dataPointer = Utilities.read_child_of(self.valobj,4*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
self.check_valid()
else:
self.valid = False
if self.valid:
- self.rwt = self.valobj.CreateValueFromAddress("rwt",self.dataPointer,self.sys_params.addr_ptr_type).AddressOf()
+ self.rwt = self.valobj.CreateValueFromAddress("rwt",self.dataPointer,self.sys_params.types_cache.addr_ptr_type).AddressOf()
self.data = RwT_Data(self.rwt,self.sys_params)
# perform sanity checks on the contents of this class_t
+ # this call tries to minimize the amount of data fetched- as soon as we have "proven"
+ # that we have an invalid object, we stop reading
def check_valid(self):
self.valid = True
+
+ self.isaPointer = Utilities.read_child_of(self.valobj,0,self.sys_params.types_cache.addr_ptr_type)
if not(Utilities.is_valid_pointer(self.isaPointer,self.sys_params.pointer_size,allow_tagged=False)):
self.valid = False
return
- if not(Utilities.is_valid_pointer(self.superclassIsaPointer,self.sys_params.pointer_size,allow_tagged=False)):
- # NULL is a valid value for superclass (it means we have reached NSObject)
- if self.superclassIsaPointer != 0:
- self.valid = False
- return
+ if not(Utilities.is_allowed_pointer(self.isaPointer)):
+ self.valid = False
+ return
+
+ self.cachePointer = Utilities.read_child_of(self.valobj,2*self.sys_params.pointer_size,self.sys_params.types_cache.addr_ptr_type)
if not(Utilities.is_valid_pointer(self.cachePointer,self.sys_params.pointer_size,allow_tagged=False)):
self.valid = False
return
+ if not(Utilities.is_allowed_pointer(self.cachePointer)):
+ self.valid = False
+ return
+
+ self.vtablePointer = Utilities.read_child_of(self.valobj,3*self.sys_params.pointer_size,self.sys_params.types_cache.addr_ptr_type)
if not(Utilities.is_valid_pointer(self.vtablePointer,self.sys_params.pointer_size,allow_tagged=False)):
self.valid = False
return
- if not(Utilities.is_valid_pointer(self.dataPointer,self.sys_params.pointer_size,allow_tagged=False)):
+ if not(Utilities.is_allowed_pointer(self.vtablePointer)):
self.valid = False
return
- if not(Utilities.is_allowed_pointer(self.isaPointer)):
+
+ self.dataPointer = Utilities.read_child_of(self.valobj,4*self.sys_params.pointer_size,self.sys_params.types_cache.addr_ptr_type)
+ if not(Utilities.is_valid_pointer(self.dataPointer,self.sys_params.pointer_size,allow_tagged=False)):
self.valid = False
return
- if not(Utilities.is_allowed_pointer(self.superclassIsaPointer)):
- # NULL is a valid value for superclass (it means we have reached NSObject)
- if self.superclassIsaPointer != 0:
- self.valid = False
- return
- if not(Utilities.is_allowed_pointer(self.cachePointer)):
+ if not(Utilities.is_allowed_pointer(self.dataPointer)):
self.valid = False
return
- if not(Utilities.is_allowed_pointer(self.vtablePointer)):
+
+ self.superclassIsaPointer = Utilities.read_child_of(self.valobj,1*self.sys_params.pointer_size,self.sys_params.types_cache.addr_ptr_type)
+ if not(Utilities.is_valid_pointer(self.superclassIsaPointer,self.sys_params.pointer_size,allow_tagged=False, allow_NULL=True)):
self.valid = False
return
- if not(Utilities.is_allowed_pointer(self.dataPointer)):
+ if not(Utilities.is_allowed_pointer(self.superclassIsaPointer)):
self.valid = False
return
@@ -198,6 +221,14 @@ class Class_Data_V2:
return True
return False
+ # some CF classes have a valid ObjC isa in their CFRuntimeBase
+ # but instead of being class-specific this isa points to a match-'em-all class
+ # which is __NSCFType (the versions without __ also exists and we are matching to it
+ # just to be on the safe side)
+ def is_cftype(self):
+ if self.is_valid():
+ return self.name == '__NSCFType' or self.name == 'NSCFType'
+
def get_superclass(self):
if self.is_valid():
parent_isa_pointer = self.valobj.CreateChildAtOffset("parent_isa",
@@ -231,14 +262,7 @@ class Class_Data_V2:
def instance_size(self,align=False):
if self.is_valid() == False:
return None
- if align:
- unalign = self.instance_size(False)
- if self.sys_params.is_64_bit:
- return ((unalign + 7) & ~7) % 0x100000000
- else:
- return ((unalign + 3) & ~3) % 0x100000000
- else:
- return self.rwt.rot.instanceSize
+ return self.rwt.rot.instance_size(align)
# runtime v1 is much less intricate than v2 and stores relevant information directly in the class_t object
class Class_Data_V1:
@@ -247,12 +271,7 @@ class Class_Data_V1:
self.valid = True
self.sys_params = params
self.valobj = isa_pointer
- self.isaPointer = Utilities.read_child_of(self.valobj,0,self.sys_params.addr_ptr_type)
- self.superclassIsaPointer = Utilities.read_child_of(self.valobj,1*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
- self.namePointer = Utilities.read_child_of(self.valobj,2*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
- self.version = Utilities.read_child_of(self.valobj,3*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
- self.info = Utilities.read_child_of(self.valobj,4*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
- self.instanceSize = Utilities.read_child_of(self.valobj,5*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
+ self.check_valid()
else:
self.valid = False
if self.valid:
@@ -263,18 +282,22 @@ class Class_Data_V1:
# perform sanity checks on the contents of this class_t
def check_valid(self):
self.valid = True
+
+ self.isaPointer = Utilities.read_child_of(self.valobj,0,self.sys_params.types_cache.addr_ptr_type)
if not(Utilities.is_valid_pointer(self.isaPointer,self.sys_params.pointer_size,allow_tagged=False)):
self.valid = False
return
- if not(Utilities.is_valid_pointer(self.superclassIsaPointer,self.sys_params.pointer_size,allow_tagged=False)):
- # NULL is a valid value for superclass (it means we have reached NSObject)
- if self.superclassIsaPointer != 0:
- self.valid = False
- return
- if not(Utilities.is_valid_pointer(self.namePointer,self.sys_params.pointer_size,allow_tagged=False,allow_NULL=True)):
+
+ self.superclassIsaPointer = Utilities.read_child_of(self.valobj,1*self.sys_params.pointer_size,self.sys_params.types_cache.addr_ptr_type)
+ if not(Utilities.is_valid_pointer(self.superclassIsaPointer,self.sys_params.pointer_size,allow_tagged=False,allow_NULL=True)):
self.valid = False
return
+ self.namePointer = Utilities.read_child_of(self.valobj,2*self.sys_params.pointer_size,self.sys_params.types_cache.addr_ptr_type)
+ #if not(Utilities.is_valid_pointer(self.namePointer,self.sys_params.pointer_size,allow_tagged=False,allow_NULL=False)):
+ # self.valid = False
+ # return
+
# in general, KVO is implemented by transparently subclassing
# however, there could be exceptions where a class does something else
# internally to implement the feature - this method will have no clue that a class
@@ -285,6 +308,14 @@ class Class_Data_V1:
return True
return False
+ # some CF classes have a valid ObjC isa in their CFRuntimeBase
+ # but instead of being class-specific this isa points to a match-'em-all class
+ # which is __NSCFType (the versions without __ also exists and we are matching to it
+ # just to be on the safe side)
+ def is_cftype(self):
+ if self.is_valid():
+ return self.name == '__NSCFType' or self.name == 'NSCFType'
+
def get_superclass(self):
if self.is_valid():
parent_isa_pointer = self.valobj.CreateChildAtOffset("parent_isa",
@@ -307,9 +338,7 @@ class Class_Data_V1:
return 'isaPointer = ' + hex(self.isaPointer) + "\n" + \
"superclassIsaPointer = " + hex(self.superclassIsaPointer) + "\n" + \
"namePointer = " + hex(self.namePointer) + " --> " + self.name + \
- "version = " + hex(self.version) + "\n" + \
- "info = " + hex(self.info) + "\n" + \
- "instanceSize = " + hex(self.instanceSize) + "\n"
+ "instanceSize = " + hex(self.instanceSize()) + "\n"
def is_tagged(self):
return False
@@ -317,6 +346,8 @@ class Class_Data_V1:
def instance_size(self,align=False):
if self.is_valid() == False:
return None
+ if self.instanceSize == None:
+ self.instanceSize = Utilities.read_child_of(self.valobj,5*self.sys_params.pointer_size,self.sys_params.types_cache.addr_ptr_type)
if align:
unalign = self.instance_size(False)
if self.sys_params.is_64_bit:
@@ -381,6 +412,9 @@ class TaggedClass_Data:
def is_kvo(self):
return False
+ def is_cftype(self):
+ return False
+
# we would need to go around looking for the superclass or ask the runtime
# for now, we seem not to require support for this operation so we will merrily
# pretend to be at a root point in the hierarchy
@@ -395,7 +429,7 @@ class TaggedClass_Data:
def instance_size(self,align=False):
if self.is_valid() == False:
return None
- return 8 if self.sys_params.is_64_bit else 4
+ return self.sys_params.pointer_size
class InvalidClass_Data:
@@ -404,40 +438,106 @@ class InvalidClass_Data:
def is_valid(self):
return False
+@functools.total_ordering
+class Version:
+ def __init__(self, major, minor, release, build_string):
+ self._major = major
+ self._minor = minor
+ self._release = release
+ self._build_string = build_string
+
+ def get_major(self):
+ return self._major
+ def get_minor(self):
+ return self._minor
+ def get_release(self):
+ return self._release
+ def get_build_string(self):
+ return self._build_string
+
+ major = property(get_major,None)
+ minor = property(get_minor,None)
+ release = property(get_release,None)
+ build_string = property(get_build_string,None)
+
+ def __lt__(self,other):
+ if (self.major < other.major):
+ return True
+ if (self.minor < other.minor):
+ return True
+ if (self.release < other.release):
+ return True
+ # build strings are not compared since they are heavily platform-dependent and might not always
+ # be available
+ return False
+
+ def __eq__(self,other):
+ return (self.major == other.major) and \
+ (self.minor == other.minor) and \
+ (self.release == other.release) and \
+ (self.build_string == other.build_string)
+
runtime_version = cache.Cache()
os_version = cache.Cache()
+types_caches = cache.Cache()
+isa_caches = cache.Cache()
-# TODO: make more extensive use of this class in the individual formatters
-# instead of recalculating the same information - since we are building this object
-# it makes sense to pass it around to the formatters
class SystemParameters:
def __init__(self,valobj):
self.adjust_for_architecture(valobj)
+ self.adjust_for_process(valobj)
- def adjust_for_architecture(self,valobj):
- self.process = valobj.GetTarget().GetProcess()
- self.is_64_bit = (self.process.GetAddressByteSize() == 8)
- self.is_little = (self.process.GetByteOrder() == lldb.eByteOrderLittle)
- self.pointer_size = self.process.GetAddressByteSize()
- self.addr_type = valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)
- self.addr_ptr_type = self.addr_type.GetPointerType()
- self.uint32_t = valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)
+ def adjust_for_process(self, valobj):
global runtime_version
global os_version
- pid = self.process.GetProcessID()
- if runtime_version.look_for_key(pid):
- self.runtime_version = runtime_version.get_value(pid)
+ global types_caches
+ global isa_caches
+
+ process = valobj.GetTarget().GetProcess()
+ self.pid = process.GetProcessID()
+
+ if runtime_version.look_for_key(self.pid):
+ self.runtime_version = runtime_version.get_value(self.pid)
else:
- self.runtime_version = ObjCRuntime.runtime_version(self.process)
- runtime_version.add_item(pid,self.runtime_version)
- if os_version.look_for_key(pid):
- self.is_lion = os_version.get_value(pid)
+ self.runtime_version = ObjCRuntime.runtime_version(process)
+ runtime_version.add_item(self.pid,self.runtime_version)
+
+ if os_version.look_for_key(self.pid):
+ self.is_lion = os_version.get_value(self.pid)
else:
self.is_lion = Utilities.check_is_osx_lion(valobj.GetTarget())
- os_version.add_item(pid,self.is_lion)
+ os_version.add_item(self.pid,self.is_lion)
+
+ if types_caches.look_for_key(self.pid):
+ self.types_cache = types_caches.get_value(self.pid)
+ else:
+ self.types_cache = attrib_fromdict.AttributesDictionary(allow_reset=False)
+ self.types_cache.addr_type = valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)
+ self.types_cache.addr_ptr_type = self.types_cache.addr_type.GetPointerType()
+ self.types_cache.uint32_t = valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)
+ types_caches.add_item(self.pid,self.types_cache)
+
+ if isa_caches.look_for_key(self.pid):
+ self.isa_cache = isa_caches.get_value(self.pid)
+ else:
+ self.isa_cache = cache.Cache()
+ isa_caches.add_item(self.pid,self.isa_cache)
+
+ def adjust_for_architecture(self,valobj):
+ process = valobj.GetTarget().GetProcess()
+ self.pointer_size = process.GetAddressByteSize()
+ self.is_64_bit = (self.pointer_size == 8)
+ self.is_little = (process.GetByteOrder() == lldb.eByteOrderLittle)
self.cfruntime_size = 16 if self.is_64_bit else 8
-isa_cache = cache.Cache()
+ # a simple helper function that makes it more explicit that one is calculating
+ # an offset that is made up of X pointers and Y bytes of additional data
+ # taking into account pointer size - if you know there is going to be some padding
+ # you can pass that in and it will be taken into account (since padding may be different between
+ # 32 and 64 bit versions, you can pass padding value for both, the right one will be used)
+ def calculate_offset(self, num_pointers = 0, bytes_count = 0, padding32 = 0, padding64 = 0):
+ value = bytes_count + num_pointers*self.pointer_size
+ return value + padding64 if self.is_64_bit else value + padding32
class ObjCRuntime:
@@ -477,32 +577,28 @@ class ObjCRuntime:
self.isa_value = None
def adjust_for_architecture(self):
- self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8)
- self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle)
- self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
- self.addr_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)
- self.addr_ptr_type = self.addr_type.GetPointerType()
+ pass
# an ObjC pointer can either be tagged or must be aligned
def is_tagged(self):
if self.valobj is None:
return False
- return (Utilities.is_valid_pointer(self.unsigned_value,self.pointer_size, allow_tagged=True) and \
- not(Utilities.is_valid_pointer(self.unsigned_value,self.pointer_size, allow_tagged=False)))
+ return (Utilities.is_valid_pointer(self.unsigned_value,self.sys_params.pointer_size, allow_tagged=True) and \
+ not(Utilities.is_valid_pointer(self.unsigned_value,self.sys_params.pointer_size, allow_tagged=False)))
def is_valid(self):
if self.valobj is None:
return False
if self.valobj.IsInScope() == False:
return False
- return Utilities.is_valid_pointer(self.unsigned_value,self.pointer_size, allow_tagged=True)
+ return Utilities.is_valid_pointer(self.unsigned_value,self.sys_params.pointer_size, allow_tagged=True)
def read_isa(self):
if self.isa_value != None:
return self.isa_value
isa_pointer = self.valobj.CreateChildAtOffset("cfisa",
0,
- self.addr_ptr_type)
+ self.sys_params.types_cache.addr_ptr_type)
if isa_pointer == None or isa_pointer.IsValid() == False:
return None;
if isa_pointer.GetValueAsUnsigned(1) == 1:
@@ -530,7 +626,7 @@ class ObjCRuntime:
isa_value = isa.GetValueAsUnsigned(1)
if isa_value == 1:
return InvalidClass_Data()
- data = isa_cache.get_value(isa_value,default=None)
+ data = self.sys_params.isa_cache.get_value(isa_value,default=None)
if data != None:
return data
if self.sys_params.runtime_version == 2:
@@ -540,6 +636,6 @@ class ObjCRuntime:
if data == None:
return InvalidClass_Data()
if data.is_valid():
- isa_cache.add_item(isa_value,data,ok_to_replace=True)
+ self.sys_params.isa_cache.add_item(isa_value,data,ok_to_replace=True)
return data
OpenPOWER on IntegriCloud