diff options
Diffstat (limited to 'lldb/examples/summaries/cocoa')
-rw-r--r-- | lldb/examples/summaries/cocoa/CFArray.py | 321 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/CFBag.py | 131 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/CFBinaryHeap.py | 128 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/CFDictionary.py | 208 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/CFString.py | 302 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/NSBundle.py | 125 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/NSData.py | 118 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/NSException.py | 108 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/NSMachPort.py | 109 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/NSNotification.py | 104 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/NSNumber.py | 217 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/NSSet.py | 211 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/NSURL.py | 124 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/cache.py | 28 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/metrics.py | 62 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/objc_lldb.py | 132 | ||||
-rw-r--r-- | lldb/examples/summaries/cocoa/objc_runtime.py | 500 |
17 files changed, 2928 insertions, 0 deletions
diff --git a/lldb/examples/summaries/cocoa/CFArray.py b/lldb/examples/summaries/cocoa/CFArray.py new file mode 100644 index 00000000000..648737f7def --- /dev/null +++ b/lldb/examples/summaries/cocoa/CFArray.py @@ -0,0 +1,321 @@ +# synthetic children provider for NSArray +import lldb +import ctypes +import objc_runtime +import metrics + +statistics = metrics.Metrics() +statistics.add_metric('invalid_isa') +statistics.add_metric('invalid_pointer') +statistics.add_metric('unknown_class') +statistics.add_metric('code_notrun') + +# much less functional than the other two cases below +# just runs code to get to the count and then returns +# no children +class NSArrayKVC_SynthProvider: + + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj, dict): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def num_children(self): + stream = lldb.SBStream() + self.valobj.GetExpressionPath(stream) + num_children_vo = self.valobj.CreateValueFromExpression("count","(int)[" + stream.GetData() + " count]"); + return num_children_vo.GetValueAsUnsigned(0) + + def get_child_index(self,name): + if name == "len": + return self.num_children(); + else: + return None + + def get_child_at_index(self, index): + return None + + + +# much less functional than the other two cases below +# just runs code to get to the count and then returns +# no children +class NSArrayCF_SynthProvider: + + def adjust_for_architecture(self): + self.lp64 = (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.cfruntime_size = self.size_of_cfruntime_base() + + # CFRuntimeBase is defined as having an additional + # 4 bytes (padding?) on LP64 architectures + # to get its size we add up sizeof(pointer)+4 + # and then add 4 more bytes if we are on a 64bit system + def size_of_cfruntime_base(self): + if self.lp64 == True: + return 8+4+4; + else: + return 4+4; + + def __init__(self, valobj, dict): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def num_children(self): + num_children_vo = self.valobj.CreateChildAtOffset("count", + self.cfruntime_size, + self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)) + return num_children_vo.GetValueAsUnsigned(0) + + def get_child_index(self,name): + if name == "len": + return self.num_children(); + else: + return None + + def get_child_at_index(self, index): + return None + + +class NSArrayI_SynthProvider: + + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj, dict): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + # skip the isa pointer and get at the size + def num_children(self): + offset = self.pointer_size; + datatype = self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong) + count = self.valobj.CreateChildAtOffset("count", + offset, + datatype); + return int(count.GetValue(), 0) + + def get_child_index(self,name): + if name == "len": + return self.num_children(); + else: + return int(name.lstrip('[').rstrip(']'), 0) + + def get_child_at_index(self, index): + if index == self.num_children(): + return self.valobj.CreateValueFromExpression("len", + str(index)) + offset = 2 * self.pointer_size + self.id_type.GetByteSize()*index + return self.valobj.CreateChildAtOffset('[' + str(index) + ']', + offset, + self.id_type) + + +class NSArrayM_SynthProvider: + + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj, dict): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + # skip the isa pointer and get at the size + def num_children(self): + offset = self.pointer_size; + datatype = self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong) + count = self.valobj.CreateChildAtOffset("count", + offset, + datatype); + return int(count.GetValue(), 0) + + def get_child_index(self,name): + if name == "len": + return self.num_children(); + else: + return int(name.lstrip('[').rstrip(']'), 0) + + def data_offset(self): + offset = self.pointer_size; # isa + offset += self.pointer_size; # _used + offset += self.pointer_size; # _doHardRetain, _doWeakAccess, _size + offset += self.pointer_size; # _hasObjects, _hasStrongReferences, _offset + offset += self.pointer_size; # _mutations + return offset; + + # the _offset field is used to calculate the actual offset + # when reading a value out of the array. we need to read it + # to do so we read a whole pointer_size of data from the + # right spot, and then zero out the two LSB + def read_offset_field(self): + disp = self.pointer_size; # isa + disp += self.pointer_size; # _used + disp += self.pointer_size; # _doHardRetain, _doWeakAccess, _size + offset = self.valobj.CreateChildAtOffset("offset", + disp, + self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong)) + offset_value = int(offset.GetValue(), 0) + offset_value = ctypes.c_uint32((offset_value & 0xFFFFFFFC) >> 2).value + return offset_value + + # the _used field tells how many items are in the array + # but since this is a mutable array, it allocates more space + # for performance reasons. we need to get the real _size of + # the array to calculate the actual offset of each element + # in get_child_at_index() (see NSArray.m for details) + def read_size_field(self): + disp = self.pointer_size; # isa + disp += self.pointer_size; # _used + size = self.valobj.CreateChildAtOffset("size", + disp, + self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong)) + size_value = int(size.GetValue(), 0) + size_value = ctypes.c_uint32((size_value & 0xFFFFFFFA) >> 2).value + return size_value + + def get_child_at_index(self, index): + if index == self.num_children(): + return self.valobj.CreateValueFromExpression("len", + str(index)) + size = self.read_size_field() + offset = self.read_offset_field() + phys_idx = offset + index + if size <= phys_idx: + phys_idx -=size; + # we still need to multiply by element size to do a correct pointer read + phys_idx *= self.id_type.GetByteSize() + list_ptr = self.valobj.CreateChildAtOffset("_list", + self.data_offset(), + self.id_type.GetBasicType(lldb.eBasicTypeUnsignedLongLong)) + list_addr = int(list_ptr.GetValue(), 0) + return self.valobj.CreateValueFromAddress('[' + str(index) + ']', + list_addr + phys_idx, + self.id_type) + +# this is the actual synth provider, but is just a wrapper that checks +# whether valobj is an instance of __NSArrayI or __NSArrayM and sets up an +# appropriate backend layer to do the computations +class NSArray_SynthProvider: + + def adjust_for_architecture(self): + self.lp64 = (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.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def __init__(self, valobj, dict): + self.valobj = valobj; + self.adjust_for_architecture() + self.wrapper = self.make_wrapper(valobj,dict) + self.invalid = (self.wrapper == None) + + def get_child_at_index(self, index): + if self.wrapper == None: + return None; + return self.wrapper.get_child_at_index(index) + + def get_child_index(self,name): + if self.wrapper == None: + return None; + return self.wrapper.get_child_index(name) + + def num_children(self): + if self.wrapper == None: + return 0; + return self.wrapper.num_children() + + def update(self): + if self.wrapper == None: + return None; + return self.wrapper.update() + + def read_ascii(self, pointer): + process = self.valobj.GetTarget().GetProcess() + error = lldb.SBError() + pystr = '' + # cannot do the read at once because there is no length byte + while True: + content = process.ReadMemory(pointer, 1, error) + new_bytes = bytearray(content) + b0 = new_bytes[0] + pointer = pointer + 1 + if b0 == 0: + break + pystr = pystr + chr(b0) + return pystr + + # this code acts as our defense against NULL and unitialized + # NSArray pointers, which makes it much longer than it would be otherwise + def make_wrapper(self,valobj,dict): + global statistics + class_data = objc_runtime.ObjCRuntime(valobj) + if class_data.is_valid() == False: + statistics.metric_hit('invalid_pointer',valobj) + wrapper = None + return + class_data = class_data.read_class_data() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + if class_data.is_kvo(): + class_data = class_data.get_superclass() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + + name_string = class_data.class_name() + if name_string == '__NSArrayI': + wrapper = NSArrayI_SynthProvider(valobj, dict) + statistics.metric_hit('code_notrun',valobj) + elif name_string == '__NSArrayM': + wrapper = NSArrayM_SynthProvider(valobj, dict) + statistics.metric_hit('code_notrun',valobj) + elif name_string == '__NSCFArray': + wrapper = NSArrayCF_SynthProvider(valobj, dict) + statistics.metric_hit('code_notrun',valobj) + else: + wrapper = NSArrayKVC_SynthProvider(valobj, dict) + statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string) + return wrapper; + +def CFArray_SummaryProvider (valobj,dict): + provider = NSArray_SynthProvider(valobj,dict); + if provider.invalid == False: + try: + summary = str(provider.num_children()); + except: + summary = None + if summary == None: + summary = 'no valid array here' + return 'size='+summary + return '' + +def __lldb_init_module(debugger,dict): + debugger.HandleCommand("type summary add -F CFArray.CFArray_SummaryProvider NSArray CFArrayRef CFMutableArrayRef") diff --git a/lldb/examples/summaries/cocoa/CFBag.py b/lldb/examples/summaries/cocoa/CFBag.py new file mode 100644 index 00000000000..596114bd5df --- /dev/null +++ b/lldb/examples/summaries/cocoa/CFBag.py @@ -0,0 +1,131 @@ +# summary provider for CFBag +import lldb +import ctypes +import objc_runtime +import metrics + +statistics = metrics.Metrics() +statistics.add_metric('invalid_isa') +statistics.add_metric('invalid_pointer') +statistics.add_metric('unknown_class') +statistics.add_metric('code_notrun') + +# despite the similary to synthetic children providers, these classes are not +# trying to provide anything but the length for an CFBag, so they need not +# obey the interface specification for synthetic children providers +class CFBagRef_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + if self.lp64: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + else: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt) + + # 12 bytes on i386 + # 20 bytes on x64 + # most probably 2 pointers and 4 bytes of data + def offset(self): + if self.lp64: + return 20 + else: + return 12 + + def length(self): + size = self.valobj.CreateChildAtOffset("count", + self.offset(), + self.NSUInteger) + return size.GetValueAsUnsigned(0) + + +class CFBagUnknown_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def length(self): + stream = lldb.SBStream() + self.valobj.GetExpressionPath(stream) + num_children_vo = self.valobj.CreateValueFromExpression("count","(int)CFBagGetCount(" + stream.GetData() + " )"); + return num_children_vo.GetValueAsUnsigned(0) + + +def GetSummary_Impl(valobj): + global statistics + class_data = objc_runtime.ObjCRuntime(valobj) + if class_data.is_valid() == False: + statistics.metric_hit('invalid_pointer',valobj) + wrapper = None + return + class_data = class_data.read_class_data() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + if class_data.is_kvo(): + class_data = class_data.get_superclass() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + + name_string = class_data.class_name() + actual_name = name_string + if name_string == '__NSCFType': + # CFBag does not expose an actual NSWrapper type, so we have to check that this is + # an NSCFType and then check we are a pointer-to __CFBag + valobj_type = valobj.GetType() + if valobj_type.IsValid() and valobj_type.IsPointerType(): + pointee_type = valobj_type.GetPointeeType() + actual_name = pointee_type.GetName() + if actual_name == '__CFBag' or \ + actual_name == 'const struct __CFBag': + wrapper = CFBagRef_SummaryProvider(valobj) + statistics.metric_hit('code_notrun',valobj) + return wrapper + wrapper = CFBagUnknown_SummaryProvider(valobj) + statistics.metric_hit('unknown_class',str(valobj) + " seen as " + actual_name) + return wrapper; + +def CFBag_SummaryProvider (valobj,dict): + provider = GetSummary_Impl(valobj); + if provider != None: + try: + summary = provider.length(); + except: + summary = None + # for some reason, one needs to clear some bits for the count + # to be correct when using CF(Mutable)BagRef on x64 + # the bit mask was derived through experimentation + # (if counts start looking weird, then most probably + # the mask needs to be changed) + if summary == None: + summary = 'no valid set here' + else: + if provider.lp64: + summary = summary & ~0x1fff000000000000 + if summary == 1: + return '1 item' + return str(summary) + " items" + return '' + +def __lldb_init_module(debugger,dict): + debugger.HandleCommand("type summary add -F CFBag.CFBag_SummaryProvider CFBagRef CFMutableBagRef") diff --git a/lldb/examples/summaries/cocoa/CFBinaryHeap.py b/lldb/examples/summaries/cocoa/CFBinaryHeap.py new file mode 100644 index 00000000000..d0542abfcc7 --- /dev/null +++ b/lldb/examples/summaries/cocoa/CFBinaryHeap.py @@ -0,0 +1,128 @@ +# summary provider for CFBinaryHeap +import lldb +import ctypes +import objc_runtime +import metrics + +statistics = metrics.Metrics() +statistics.add_metric('invalid_isa') +statistics.add_metric('invalid_pointer') +statistics.add_metric('unknown_class') +statistics.add_metric('code_notrun') + +# despite the similary to synthetic children providers, these classes are not +# trying to provide anything but the length for an CFBinaryHeap, so they need not +# obey the interface specification for synthetic children providers +class CFBinaryHeapRef_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + if self.lp64: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + else: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt) + + # 8 bytes on i386 + # 16 bytes on x64 + # most probably 2 pointers + def offset(self): + if self.lp64: + return 16 + else: + return 8 + + def length(self): + size = self.valobj.CreateChildAtOffset("count", + self.offset(), + self.NSUInteger) + return size.GetValueAsUnsigned(0) + + +class CFBinaryHeapUnknown_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def length(self): + stream = lldb.SBStream() + self.valobj.GetExpressionPath(stream) + num_children_vo = self.valobj.CreateValueFromExpression("count","(int)CFBinaryHeapGetCount(" + stream.GetData() + " )"); + return num_children_vo.GetValueAsUnsigned(0) + + +def GetSummary_Impl(valobj): + global statistics + class_data = objc_runtime.ObjCRuntime(valobj) + if class_data.is_valid() == False: + statistics.metric_hit('invalid_pointer',valobj) + wrapper = None + return + class_data = class_data.read_class_data() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + if class_data.is_kvo(): + class_data = class_data.get_superclass() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + + name_string = class_data.class_name() + if name_string == '__NSCFType': + # CFBinaryHeap does not expose an actual NSWrapper type, so we have to check that this is + # an NSCFType and then check we are a pointer-to CFBinaryHeap + valobj_type = valobj.GetType() + if valobj_type.IsValid() and valobj_type.IsPointerType(): + pointee_type = valobj_type.GetPointeeType() + if pointee_type.GetName() == '__CFBinaryHeap': + wrapper = CFBinaryHeapRef_SummaryProvider(valobj) + statistics.metric_hit('code_notrun',valobj) + return wrapper + wrapper = CFBinaryHeapUnknown_SummaryProvider(valobj) + statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string) + return wrapper; + +def CFBinaryHeap_SummaryProvider (valobj,dict): + provider = GetSummary_Impl(valobj); + if provider != None: + try: + summary = provider.length(); + except: + summary = None + # for some reason, one needs to clear some bits for the count + # to be correct when using CF(Mutable)BagRef on x64 + # the bit mask was derived through experimentation + # (if counts start looking weird, then most probably + # the mask needs to be changed) + if summary == None: + summary = 'no valid set here' + else: + if provider.lp64: + summary = summary & ~0x1fff000000000000 + if summary == 1: + return '1 item' + return str(summary) + " items" + return '' + +def __lldb_init_module(debugger,dict): + debugger.HandleCommand("type summary add -F CFBinaryHeap.CFBinaryHeap_SummaryProvider CFBinaryHeapRef") diff --git a/lldb/examples/summaries/cocoa/CFDictionary.py b/lldb/examples/summaries/cocoa/CFDictionary.py new file mode 100644 index 00000000000..944e05aca59 --- /dev/null +++ b/lldb/examples/summaries/cocoa/CFDictionary.py @@ -0,0 +1,208 @@ +# summary provider for NSDictionary +import lldb +import ctypes +import objc_runtime +import metrics + +statistics = metrics.Metrics() +statistics.add_metric('invalid_isa') +statistics.add_metric('invalid_pointer') +statistics.add_metric('unknown_class') +statistics.add_metric('code_notrun') + +# despite the similary to synthetic children providers, these classes are not +# trying to provide anything but the count for an NSDictionary, so they need not +# obey the interface specification for synthetic children providers +class NSCFDictionary_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + if self.lp64: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + else: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt) + + # empirically determined on both 32 and 64bit desktop Mac OS X + # probably boils down to 2 pointers and 4 bytes of data, but + # the description of __CFDictionary is not readily available so most + # of this is guesswork, plain and simple + def offset(self): + if self.lp64: + return 20 + else: + return 12 + + def num_children(self): + num_children_vo = self.valobj.CreateChildAtOffset("count", + self.offset(), + self.NSUInteger) + return num_children_vo.GetValueAsUnsigned(0) + + +class NSDictionaryI_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + if self.lp64: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + else: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt) + + # we just need to skip the ISA and the count immediately follows + def offset(self): + if self.lp64: + return 8 + else: + return 4 + + def num_children(self): + num_children_vo = self.valobj.CreateChildAtOffset("count", + self.offset(), + self.NSUInteger) + value = num_children_vo.GetValueAsUnsigned(0) + if value != None: + # the MSB on immutable dictionaries seems to be taken by the LSB of capacity + # not sure if it is a bug or some weird sort of feature, but masking it out + # gets the count right (unless, of course, someone's dictionaries grow + # too large - but I have not tested this) + if self.lp64: + value = value & ~0xFF00000000000000 + else: + value = value & ~0xFF000000 + return value + +class NSDictionaryM_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + if self.lp64: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + else: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt) + + # we just need to skip the ISA and the count immediately follows + def offset(self): + if self.lp64: + return 8 + else: + return 4 + + def num_children(self): + num_children_vo = self.valobj.CreateChildAtOffset("count", + self.offset(), + self.NSUInteger) + return num_children_vo.GetValueAsUnsigned(0) + + +class NSDictionaryUnknown_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def num_children(self): + stream = lldb.SBStream() + self.valobj.GetExpressionPath(stream) + num_children_vo = self.valobj.CreateValueFromExpression("count","(int)[" + stream.GetData() + " count]"); + return num_children_vo.GetValueAsUnsigned(0) + + +def GetSummary_Impl(valobj): + global statistics + class_data = objc_runtime.ObjCRuntime(valobj) + if class_data.is_valid() == False: + statistics.metric_hit('invalid_pointer',valobj) + wrapper = None + return + class_data = class_data.read_class_data() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + if class_data.is_kvo(): + class_data = class_data.get_superclass() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + + name_string = class_data.class_name() + if name_string == '__NSCFDictionary': + wrapper = NSCFDictionary_SummaryProvider(valobj) + statistics.metric_hit('code_notrun',valobj) + elif name_string == '__NSDictionaryI': + wrapper = NSDictionaryI_SummaryProvider(valobj) + statistics.metric_hit('code_notrun',valobj) + elif name_string == '__NSDictionaryM': + wrapper = NSDictionaryM_SummaryProvider(valobj) + statistics.metric_hit('code_notrun',valobj) + else: + wrapper = NSDictionaryUnknown_SummaryProvider(valobj) + statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string) + return wrapper; + +def CFDictionary_SummaryProvider (valobj,dict): + provider = GetSummary_Impl(valobj); + if provider != None: + try: + summary = str(provider.num_children()); + except: + summary = None + if summary == None: + summary = 'no valid dictionary here' + return summary + " key/value pairs" + return '' + +def CFDictionary_SummaryProvider2 (valobj,dict): + provider = GetSummary_Impl(valobj); + if provider != None: + try: + summary = (provider.num_children()); + except: + summary = None + if summary == None: + summary = 'no valid dictionary here' + # needed on OSX Mountain Lion + elif provider.lp64: + summary = int(summary) & ~0x0f1f000000000000 + return str(summary) + " key/value pairs" + return '' + +def __lldb_init_module(debugger,dict): + debugger.HandleCommand("type summary add -F CFDictionary.CFDictionary_SummaryProvider NSDictionary") + debugger.HandleCommand("type summary add -F CFDictionary.CFDictionary_SummaryProvider2 CFDictionaryRef CFMutableDictionaryRef") diff --git a/lldb/examples/summaries/cocoa/CFString.py b/lldb/examples/summaries/cocoa/CFString.py new file mode 100644 index 00000000000..478f972e529 --- /dev/null +++ b/lldb/examples/summaries/cocoa/CFString.py @@ -0,0 +1,302 @@ +# synthetic children and summary provider for CFString +# (and related NSString class) +import lldb + +def CFString_SummaryProvider (valobj,dict): + provider = CFStringSynthProvider(valobj,dict); + if provider.invalid == False: + try: + summary = provider.get_child_at_index(provider.get_child_index("content")).GetSummary(); + except: + summary = None + if summary == None: + summary = 'no valid string here' + return '@'+summary + return '' + +def CFAttributedString_SummaryProvider (valobj,dict): + offset = valobj.GetTarget().GetProcess().GetAddressByteSize() + pointee = valobj.GetValueAsUnsigned(0) + summary = 'no valid string here' + if pointee != None and pointee != 0: + pointee = pointee + offset + child_ptr = valobj.CreateValueFromAddress("string_ptr",pointee,valobj.GetType()) + child = child_ptr.CreateValueFromAddress("string_data",child_ptr.GetValueAsUnsigned(),valobj.GetType()).AddressOf() + provider = CFStringSynthProvider(child,dict); + if provider.invalid == False: + try: + summary = provider.get_child_at_index(provider.get_child_index("content")).GetSummary(); + except: + summary = 'no valid string here' + if summary == None: + summary = 'no valid string here' + return '@'+summary + + +def __lldb_init_module(debugger,dict): + debugger.HandleCommand("type summary add -F CFString.CFString_SummaryProvider NSString CFStringRef CFMutableStringRef") + debugger.HandleCommand("type summary add -F CFString.CFAttributedString_SummaryProvider NSAttributedString") + +class CFStringSynthProvider: + def __init__(self,valobj,dict): + self.valobj = valobj; + self.update() + + # children other than "content" are for debugging only and must not be used in production code + def num_children(self): + if self.invalid: + return 0; + return 6; + + def read_unicode(self, pointer): + process = self.valobj.GetTarget().GetProcess() + error = lldb.SBError() + pystr = u'' + # cannot do the read at once because the length value has + # a weird encoding. better play it safe here + while True: + content = process.ReadMemory(pointer, 2, error) + new_bytes = bytearray(content) + b0 = new_bytes[0] + b1 = new_bytes[1] + pointer = pointer + 2 + if b0 == 0 and b1 == 0: + break + # rearrange bytes depending on endianness + # (do we really need this or is Cocoa going to + # use Windows-compatible little-endian even + # if the target is big endian?) + if self.is_little: + value = b1 * 256 + b0 + else: + value = b0 * 256 + b1 + pystr = pystr + unichr(value) + return pystr + + # handle the special case strings + # only use the custom code for the tested LP64 case + def handle_special(self): + if self.lp64 == False: + # for 32bit targets, use safe ObjC code + return self.handle_unicode_string_safe() + offset = 12 + pointer = self.valobj.GetValueAsUnsigned(0) + offset + pystr = self.read_unicode(pointer) + return self.valobj.CreateValueFromExpression("content", + "(char*)\"" + pystr.encode('utf-8') + "\"") + + # last resort call, use ObjC code to read; the final aim is to + # be able to strip this call away entirely and only do the read + # ourselves + def handle_unicode_string_safe(self): + return self.valobj.CreateValueFromExpression("content", + "(char*)\"" + self.valobj.GetObjectDescription() + "\""); + + def handle_unicode_string(self): + # step 1: find offset + if self.inline: + pointer = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base(); + if self.explicit == False: + # untested, use the safe code path + return self.handle_unicode_string_safe(); + else: + # not sure why 8 bytes are skipped here + # (lldb) mem read -c 50 0x00000001001154f0 + # 0x1001154f0: 98 1a 85 71 ff 7f 00 00 90 07 00 00 01 00 00 00 ...q?........... + # 0x100115500: 03 00 00 00 00 00 00 00 *c3 03 78 00 78 00 00 00 ........?.x.x... + # 0x100115510: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + # 0x100115520: 00 00 .. + # content begins at * (i.e. 8 bytes into variants, skipping void* buffer in + # __notInlineImmutable1 entirely, while the length byte is correctly located + # for an inline string) + pointer = pointer + 8; + else: + pointer = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base(); + # read 8 bytes here and make an address out of them + try: + vopointer = self.valobj.CreateChildAtOffset("dummy", + pointer,self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar).GetPointerType()); + pointer = vopointer.GetValueAsUnsigned(0) + except: + return self.valobj.CreateValueFromExpression("content", + '(char*)"@\"invalid NSString\""') + # step 2: read Unicode data at pointer + pystr = self.read_unicode(pointer) + # step 3: return it + return self.valobj.CreateValueFromExpression("content", + "(char*)\"" + pystr.encode('utf-8') + "\"") + + def handle_inline_explicit(self): + if self.lp64: + offset = 24 + else: + offset = 12 + offset = offset + self.valobj.GetValueAsUnsigned(0) + return self.valobj.CreateValueFromExpression("content", + "(char*)(" + str(offset) + ")") + + def handle_mutable_string(self): + if self.lp64: + offset = 16 + else: + offset = 8 + data = self.valobj.CreateChildAtOffset("content", + offset, self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar).GetPointerType()); + data_value = data.GetValueAsUnsigned(0) + data_value = data_value + 1 + return self.valobj.CreateValueFromExpression("content", "(char*)(" + str(data_value) + ")") + + def handle_UTF8_inline(self): + offset = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base(); + if self.explicit == False: + offset = offset + 1; + return self.valobj.CreateValueFromAddress("content", + offset, self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar)).AddressOf(); + + def handle_UTF8_not_inline(self): + offset = self.size_of_cfruntime_base(); + return self.valobj.CreateChildAtOffset("content", + offset,self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar).GetPointerType()); + + def get_child_at_index(self,index): + if index == 0: + return self.valobj.CreateValueFromExpression("mutable", + str(int(self.mutable))); + if index == 1: + return self.valobj.CreateValueFromExpression("inline", + str(int(self.inline))); + if index == 2: + return self.valobj.CreateValueFromExpression("explicit", + str(int(self.explicit))); + if index == 3: + return self.valobj.CreateValueFromExpression("unicode", + str(int(self.unicode))); + if index == 4: + return self.valobj.CreateValueFromExpression("special", + str(int(self.special))); + if index == 5: + # we are handling the several possible combinations of flags. + # for each known combination we have a function that knows how to + # go fetch the data from memory instead of running code. if a string is not + # correctly displayed, one should start by finding a combination of flags that + # makes it different from these known cases, and provide a new reader function + # if this is not possible, a new flag might have to be made up (like the "special" flag + # below, which is not a real flag in CFString), or alternatively one might need to use + # the ObjC runtime helper to detect the new class and deal with it accordingly + if self.mutable == True: + return self.handle_mutable_string() + elif self.inline == True and self.explicit == True and \ + self.unicode == False and self.special == False and \ + self.mutable == False: + return self.handle_inline_explicit() + elif self.unicode == True: + return self.handle_unicode_string(); + elif self.special == True: + return self.handle_special(); + elif self.inline == True: + return self.handle_UTF8_inline(); + else: + return self.handle_UTF8_not_inline(); + + def get_child_index(self,name): + if name == "content": + return self.num_children() - 1; + if name == "mutable": + return 0; + if name == "inline": + return 1; + if name == "explicit": + return 2; + if name == "unicode": + return 3; + if name == "special": + return 4; + + def is_64bit(self): + return self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8 + + def is_little_endian(self): + return self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle + + # CFRuntimeBase is defined as having an additional + # 4 bytes (padding?) on LP64 architectures + # to get its size we add up sizeof(pointer)+4 + # and then add 4 more bytes if we are on a 64bit system + def size_of_cfruntime_base(self): + if self.lp64 == True: + return 8+4+4; + else: + return 4+4; + + # the info bits are part of the CFRuntimeBase structure + # to get at them we have to skip a uintptr_t and then get + # at the least-significant byte of a 4 byte array. If we are + # on big-endian this means going to byte 3, if we are on + # little endian (OSX & iOS), this means reading byte 0 + def offset_of_info_bits(self): + if self.lp64 == True: + offset = 8; + else: + offset = 4; + if self.is_little == False: + offset = offset + 3; + return offset; + + def read_info_bits(self): + cfinfo = self.valobj.CreateChildAtOffset("cfinfo", + self.offset_of_info_bits(), + self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar)); + cfinfo.SetFormat(11) + info = cfinfo.GetValue(); + if info != None: + self.invalid = False; + return int(info,0); + else: + self.invalid = True; + return None; + + # calculating internal flag bits of the CFString object + # this stuff is defined and discussed in CFString.c + def is_mutable(self): + return (self.info_bits & 1) == 1; + + def is_inline(self): + return (self.info_bits & 0x60) == 0; + + # this flag's name is ambiguous, it turns out + # we must skip a length byte to get at the data + # when this flag is False + def has_explicit_length(self): + return (self.info_bits & (1 | 4)) != 4; + + # probably a subclass of NSString. obtained this from [str pathExtension] + # here info_bits = 0 and Unicode data at the start of the padding word + # in the long run using the isa value might be safer as a way to identify this + # instead of reading the info_bits + def is_special_case(self): + return self.info_bits == 0; + + def is_unicode(self): + return (self.info_bits & 0x10) == 0x10; + + # preparing ourselves to read into memory + # by adjusting architecture-specific info + def adjust_for_architecture(self): + self.lp64 = self.is_64bit(); + self.is_little = self.is_little_endian(); + + # reading info bits out of the CFString and computing + # useful values to get at the real data + def compute_flags(self): + self.info_bits = self.read_info_bits(); + if self.info_bits == None: + return; + self.mutable = self.is_mutable(); + self.inline = self.is_inline(); + self.explicit = self.has_explicit_length(); + self.unicode = self.is_unicode(); + self.special = self.is_special_case(); + + def update(self): + self.adjust_for_architecture(); + self.compute_flags();
\ No newline at end of file diff --git a/lldb/examples/summaries/cocoa/NSBundle.py b/lldb/examples/summaries/cocoa/NSBundle.py new file mode 100644 index 00000000000..5e6b11a6fc0 --- /dev/null +++ b/lldb/examples/summaries/cocoa/NSBundle.py @@ -0,0 +1,125 @@ +# summary provider for NSBundle +import lldb +import ctypes +import objc_runtime +import metrics +import NSURL + +statistics = metrics.Metrics() +statistics.add_metric('invalid_isa') +statistics.add_metric('invalid_pointer') +statistics.add_metric('unknown_class') +statistics.add_metric('code_notrun') + +# despite the similary to synthetic children providers, these classes are not +# trying to provide anything but a summary for an NSURL, so they need not +# obey the interface specification for synthetic children providers +class NSBundleKnown_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + if self.lp64: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + self.pointer_size = 8 + else: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt) + self.pointer_size = 4 + self.NSString = self.valobj.GetTarget().FindFirstType('NSString') + self.NSURL = self.valobj.GetTarget().FindFirstType('NSURL') + + # we need to skip the ISA, plus four other values + # that are luckily each a pointer in size + # which makes our computation trivial :-) + def offset(self): + return 5 * self.pointer_size + + def url_text(self): + global statistics + text = self.valobj.CreateChildAtOffset("text", + self.offset(), + self.NSString.GetPointerType()) + my_string = text.GetSummary() + if (my_string == None) or (my_string == ''): + statistics.metric_hit('unknown_class',str(self.valobj) + " triggered unkown pointer location") + return NSBundleUnknown_SummaryProvider(self.valobj).url_text() + else: + statistics.metric_hit('code_notrun',self.valobj) + return my_string + + +class NSBundleUnknown_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def url_text(self): + stream = lldb.SBStream() + self.valobj.GetExpressionPath(stream) + expr = "(NSString*)[" + stream.GetData() + " bundlePath]" + url_text_vo = self.valobj.CreateValueFromExpression("path",expr); + return url_text_vo.GetSummary() + + +def GetSummary_Impl(valobj): + global statistics + class_data = objc_runtime.ObjCRuntime(valobj) + if class_data.is_valid() == False: + statistics.metric_hit('invalid_pointer',valobj) + wrapper = None + return + class_data = class_data.read_class_data() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + if class_data.is_kvo(): + class_data = class_data.get_superclass() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + + name_string = class_data.class_name() + if name_string == 'NSBundle': + wrapper = NSBundleKnown_SummaryProvider(valobj) + # [NSBundle mainBundle] does return an object that is + # not correctly filled out for our purposes, so we still + # end up having to run code in that case + #statistics.metric_hit('code_notrun',valobj) + else: + wrapper = NSBundleUnknown_SummaryProvider(valobj) + statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string) + return wrapper; + +def NSBundle_SummaryProvider (valobj,dict): + provider = GetSummary_Impl(valobj); + if provider != None: + try: + summary = provider.url_text(); + except: + summary = None + if summary == None or summary == '': + summary = 'no valid NSBundle here' + return summary + return '' + +def __lldb_init_module(debugger,dict): + debugger.HandleCommand("type summary add -F NSBundle.NSBundle_SummaryProvider NSBundle") diff --git a/lldb/examples/summaries/cocoa/NSData.py b/lldb/examples/summaries/cocoa/NSData.py new file mode 100644 index 00000000000..362bc81cd8d --- /dev/null +++ b/lldb/examples/summaries/cocoa/NSData.py @@ -0,0 +1,118 @@ +# summary provider for NSData +import lldb +import ctypes +import objc_runtime +import metrics + +statistics = metrics.Metrics() +statistics.add_metric('invalid_isa') +statistics.add_metric('invalid_pointer') +statistics.add_metric('unknown_class') +statistics.add_metric('code_notrun') + +# despite the similary to synthetic children providers, these classes are not +# trying to provide anything but the length for an NSData, so they need not +# obey the interface specification for synthetic children providers +class NSConcreteData_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + if self.lp64: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + else: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt) + + # one pointer is the ISA + # then there are 32 bit worth of flags and other data + # however, on 64bit systems these are padded to be a full + # machine word long, which means we actually have two pointers + # worth of data to skip + def offset(self): + if self.lp64: + return 16 + else: + return 8 + + def length(self): + size = self.valobj.CreateChildAtOffset("count", + self.offset(), + self.NSUInteger) + return size.GetValueAsUnsigned(0) + + +class NSDataUnknown_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def length(self): + stream = lldb.SBStream() + self.valobj.GetExpressionPath(stream) + num_children_vo = self.valobj.CreateValueFromExpression("count","(int)[" + stream.GetData() + " length]"); + return num_children_vo.GetValueAsUnsigned(0) + + +def GetSummary_Impl(valobj): + global statistics + class_data = objc_runtime.ObjCRuntime(valobj) + if class_data.is_valid() == False: + statistics.metric_hit('invalid_pointer',valobj) + wrapper = None + return + class_data = class_data.read_class_data() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + if class_data.is_kvo(): + class_data = class_data.get_superclass() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + + name_string = class_data.class_name() + if name_string == 'NSConcreteData' or \ + name_string == 'NSConcreteMutableData' or \ + name_string == '__NSCFData': + wrapper = NSConcreteData_SummaryProvider(valobj) + statistics.metric_hit('code_notrun',valobj) + else: + wrapper = NSDataUnknown_SummaryProvider(valobj) + statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string) + return wrapper; + +def NSData_SummaryProvider (valobj,dict): + provider = GetSummary_Impl(valobj); + if provider != None: + try: + summary = provider.length(); + except: + summary = None + if summary == None: + summary = 'no valid data here' + if summary == 1: + return '1 byte' + return str(summary) + " bytes" + return '' + +def __lldb_init_module(debugger,dict): + debugger.HandleCommand("type summary add -F NSData.NSData_SummaryProvider NSData CFDataRef CFMutableDataRef") diff --git a/lldb/examples/summaries/cocoa/NSException.py b/lldb/examples/summaries/cocoa/NSException.py new file mode 100644 index 00000000000..e5f0418eed9 --- /dev/null +++ b/lldb/examples/summaries/cocoa/NSException.py @@ -0,0 +1,108 @@ +# summary provider for class NSException +import objc_runtime +import metrics +import CFString +import lldb + +statistics = metrics.Metrics() +statistics.add_metric('invalid_isa') +statistics.add_metric('invalid_pointer') +statistics.add_metric('unknown_class') +statistics.add_metric('code_notrun') + +class NSKnownException_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + + # skip the ISA and go to the name pointer + def offset(self): + if self.lp64: + return 8 + else: + return 4 + + def description(self): + name_ptr = self.valobj.CreateChildAtOffset("name", + self.offset(), + self.id_type) + reason_ptr = self.valobj.CreateChildAtOffset("reason", + 2*self.offset(), + self.id_type) + return CFString.CFString_SummaryProvider(name_ptr,None) + " " + CFString.CFString_SummaryProvider(reason_ptr,None) + + +class NSUnknownException_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def description(self): + stream = lldb.SBStream() + self.valobj.GetExpressionPath(stream) + name_vo = self.valobj.CreateValueFromExpression("name","(NSString*)[" + stream.GetData() + " name]"); + reason_vo = self.valobj.CreateValueFromExpression("reason","(NSString*)[" + stream.GetData() + " reason]"); + return CFString.CFString_SummaryProvider(name_vo,None) + ' ' + CFString.CFString_SummaryProvider(reason_vo,None) + + +def GetSummary_Impl(valobj): + global statistics + class_data = objc_runtime.ObjCRuntime(valobj) + if class_data.is_valid() == False: + statistics.metric_hit('invalid_pointer',valobj) + wrapper = None + return + class_data = class_data.read_class_data() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + if class_data.is_kvo(): + class_data = class_data.get_superclass() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + + name_string = class_data.class_name() + if name_string == 'NSException': + wrapper = NSKnownException_SummaryProvider(valobj) + statistics.metric_hit('code_notrun',valobj) + else: + wrapper = NSUnknownException_SummaryProvider(valobj) + statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string) + return wrapper; + +def NSException_SummaryProvider (valobj,dict): + provider = GetSummary_Impl(valobj); + if provider != None: + try: + summary = provider.description(); + except: + summary = None + if summary == None: + summary = 'no valid exception here' + return str(summary) + return '' + +def __lldb_init_module(debugger,dict): + debugger.HandleCommand("type summary add -F NSException.NSException_SummaryProvider NSException") diff --git a/lldb/examples/summaries/cocoa/NSMachPort.py b/lldb/examples/summaries/cocoa/NSMachPort.py new file mode 100644 index 00000000000..ca714bc38ae --- /dev/null +++ b/lldb/examples/summaries/cocoa/NSMachPort.py @@ -0,0 +1,109 @@ +# summary provider for NSData +import lldb +import ctypes +import objc_runtime +import metrics + +statistics = metrics.Metrics() +statistics.add_metric('invalid_isa') +statistics.add_metric('invalid_pointer') +statistics.add_metric('unknown_class') +statistics.add_metric('code_notrun') + +# despite the similary to synthetic children providers, these classes are not +# trying to provide anything but the port number of an NSMachPort, so they need not +# obey the interface specification for synthetic children providers +class NSMachPortKnown_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + + # one pointer is the ISA + # then we have one other internal pointer, plus + # 4 bytes worth of flags. hence, these values + def offset(self): + if self.lp64: + return 20 + else: + return 12 + + def port(self): + vport = self.valobj.CreateChildAtOffset("port", + self.offset(), + self.NSUInteger) + return vport.GetValueAsUnsigned(0) + + +class NSMachPortUnknown_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def port(self): + stream = lldb.SBStream() + self.valobj.GetExpressionPath(stream) + num_children_vo = self.valobj.CreateValueFromExpression("port","(int)[" + stream.GetData() + " machPort]"); + return num_children_vo.GetValueAsUnsigned(0) + + +def GetSummary_Impl(valobj): + global statistics + class_data = objc_runtime.ObjCRuntime(valobj) + if class_data.is_valid() == False: + statistics.metric_hit('invalid_pointer',valobj) + wrapper = None + return + class_data = class_data.read_class_data() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + if class_data.is_kvo(): + class_data = class_data.get_superclass() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + + name_string = class_data.class_name() + if name_string == 'NSMachPort': + wrapper = NSMachPortKnown_SummaryProvider(valobj) + statistics.metric_hit('code_notrun',valobj) + else: + wrapper = NSMachPortUnknown_SummaryProvider(valobj) + statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string) + return wrapper; + +def NSMachPort_SummaryProvider (valobj,dict): + provider = GetSummary_Impl(valobj); + if provider != None: + try: + summary = provider.port(); + except: + summary = None + if summary == None: + summary = 'no valid mach port here' + return 'mach port: ' + str(summary) + return '' + +def __lldb_init_module(debugger,dict): + debugger.HandleCommand("type summary add -F NSMachPort.NSMachPort_SummaryProvider NSMachPort") diff --git a/lldb/examples/summaries/cocoa/NSNotification.py b/lldb/examples/summaries/cocoa/NSNotification.py new file mode 100644 index 00000000000..6170110354c --- /dev/null +++ b/lldb/examples/summaries/cocoa/NSNotification.py @@ -0,0 +1,104 @@ +# summary provider for class NSNotification +import objc_runtime +import metrics +import CFString +import lldb + +statistics = metrics.Metrics() +statistics.add_metric('invalid_isa') +statistics.add_metric('invalid_pointer') +statistics.add_metric('unknown_class') +statistics.add_metric('code_notrun') + +class NSConcreteNotification_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + + # skip the ISA and go to the name pointer + def offset(self): + if self.lp64: + return 8 + else: + return 4 + + def name(self): + string_ptr = self.valobj.CreateChildAtOffset("name", + self.offset(), + self.id_type) + return CFString.CFString_SummaryProvider(string_ptr,None) + + +class NSNotificationUnknown_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def name(self): + stream = lldb.SBStream() + self.valobj.GetExpressionPath(stream) + name_vo = self.valobj.CreateValueFromExpression("name","(NSString*)[" + stream.GetData() + " name]"); + return CFString.CFString_SummaryProvider(name_vo,None) + + +def GetSummary_Impl(valobj): + global statistics + class_data = objc_runtime.ObjCRuntime(valobj) + if class_data.is_valid() == False: + statistics.metric_hit('invalid_pointer',valobj) + wrapper = None + return + class_data = class_data.read_class_data() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + if class_data.is_kvo(): + class_data = class_data.get_superclass() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + + name_string = class_data.class_name() + if name_string == 'NSConcreteNotification': + wrapper = NSConcreteNotification_SummaryProvider(valobj) + statistics.metric_hit('code_notrun',valobj) + else: + wrapper = NSNotificationUnknown_SummaryProvider(valobj) + statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string) + return wrapper; + +def NSNotification_SummaryProvider (valobj,dict): + provider = GetSummary_Impl(valobj); + if provider != None: + try: + summary = provider.name(); + except: + summary = None + if summary == None: + summary = 'no valid notification here' + return str(summary) + return '' + +def __lldb_init_module(debugger,dict): + debugger.HandleCommand("type summary add -F NSNotification.NSNotification_SummaryProvider NSNotification") diff --git a/lldb/examples/summaries/cocoa/NSNumber.py b/lldb/examples/summaries/cocoa/NSNumber.py new file mode 100644 index 00000000000..81f3d28dbc1 --- /dev/null +++ b/lldb/examples/summaries/cocoa/NSNumber.py @@ -0,0 +1,217 @@ +# summary provider for NSNumber +import lldb +import ctypes +import objc_runtime +import metrics +import struct + +statistics = metrics.Metrics() +statistics.add_metric('invalid_isa') +statistics.add_metric('invalid_pointer') +statistics.add_metric('unknown_class') +statistics.add_metric('code_notrun') + +# despite the similary to synthetic children providers, these classes are not +# trying to provide anything but the port number of an NSNumber, so they need not +# obey the interface specification for synthetic children providers +class NSTaggedNumber_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj, info_bits, data): + self.valobj = valobj; + self.update(); + self.info_bits = info_bits + self.data = data + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + self.char = self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar) + self.short = self.valobj.GetType().GetBasicType(lldb.eBasicTypeShort) + self.ushort = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedShort) + self.int = self.valobj.GetType().GetBasicType(lldb.eBasicTypeInt) + self.long = self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong) + self.ulong = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + self.longlong = self.valobj.GetType().GetBasicType(lldb.eBasicTypeLongLong) + self.ulonglong = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLongLong) + self.float = self.valobj.GetType().GetBasicType(lldb.eBasicTypeFloat) + self.double = self.valobj.GetType().GetBasicType(lldb.eBasicTypeDouble) + + def value(self): + # in spite of the plenty of types made available by the public NSNumber API + # only a bunch of these are actually used in the internal implementation + # unfortunately, the original type information appears to be lost + # so we try to at least recover the proper magnitude of the data + if self.info_bits == 0: + return '(char)' + str(self.data % 256) + if self.info_bits == 4: + return '(short)' + str(self.data % (256*256)) + if self.info_bits == 8: + return '(int)' + str(self.data % (256*256*256*256)) + if self.info_bits == 12: + return '(long)' + str(self.data) + else: + return 'absurd value:(info=' + str(self.info_bits) + ", value = " + str(self.data) + ')' + + +class NSUntaggedNumber_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + self.char = self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar) + self.short = self.valobj.GetType().GetBasicType(lldb.eBasicTypeShort) + self.ushort = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedShort) + self.int = self.valobj.GetType().GetBasicType(lldb.eBasicTypeInt) + self.long = self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong) + self.ulong = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + self.longlong = self.valobj.GetType().GetBasicType(lldb.eBasicTypeLongLong) + self.ulonglong = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLongLong) + self.float = self.valobj.GetType().GetBasicType(lldb.eBasicTypeFloat) + self.double = self.valobj.GetType().GetBasicType(lldb.eBasicTypeDouble) + + def value(self): + global statistics + # we need to skip the ISA, then the next byte tells us what to read + # we then skip one other full pointer worth of data and then fetch the contents + # if we are fetching an int64 value, one more pointer must be skipped to get at our data + data_type_vo = self.valobj.CreateChildAtOffset("dt", + self.pointer_size, + self.char) + data_type = ((data_type_vo.GetValueAsUnsigned(0) % 256) & 0x1F) + data_offset = 2 * self.pointer_size + if data_type == 0B00001: + data_vo = self.valobj.CreateChildAtOffset("data", + data_offset, + self.char) + statistics.metric_hit('code_notrun',self.valobj) + return '(char)' + str(data_vo.GetValueAsUnsigned(0)) + elif data_type == 0B0010: + data_vo = self.valobj.CreateChildAtOffset("data", + data_offset, + self.short) + statistics.metric_hit('code_notrun',self.valobj) + return '(short)' + str(data_vo.GetValueAsUnsigned(0) % (256*256)) + # IF tagged pointers are possible on 32bit+v2 runtime + # (of which the only existing instance should be iOS) + # then values of this type might be tagged + elif data_type == 0B0011: + data_vo = self.valobj.CreateChildAtOffset("data", + data_offset, + self.int) + statistics.metric_hit('code_notrun',self.valobj) + return '(int)' + str(data_vo.GetValueAsUnsigned(0) % (256*256*256*256)) + # apparently, on lp64 architectures, these are the only values that will ever + # be represented by a non tagged pointers + elif data_type == 0B10001 or data_type == 0B0100: + data_offset = data_offset + self.pointer_size + data_vo = self.valobj.CreateChildAtOffset("data", + data_offset, + self.longlong) + statistics.metric_hit('code_notrun',self.valobj) + return '(long)' + str(data_vo.GetValueAsUnsigned(0)) + elif data_type == 0B0101: + data_vo = self.valobj.CreateChildAtOffset("data", + data_offset, + self.longlong) + data_plain = int(str(data_vo.GetValueAsUnsigned(0) & 0x00000000FFFFFFFF)) + packed = struct.pack('I', data_plain) + data_float = struct.unpack('f', packed)[0] + statistics.metric_hit('code_notrun',self.valobj) + return '(float)' + str(data_float) + elif data_type == 0B0110: + data_vo = self.valobj.CreateChildAtOffset("data", + data_offset, + self.longlong) + data_plain = data_vo.GetValueAsUnsigned(0) + data_double = struct.unpack('d', struct.pack('Q', data_plain))[0] + statistics.metric_hit('code_notrun',self.valobj) + return '(double)' + str(data_double) + statistics.metric_hit('unknown_class',str(self.valobj) + " had unknown data_type " + str(data_type)) + return 'absurd: dt = ' + str(data_type) + + +class NSUnknownNumber_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def value(self): + stream = lldb.SBStream() + self.valobj.GetExpressionPath(stream) + expr = "(NSString*)[" + stream.GetData() + " stringValue]" + num_children_vo = self.valobj.CreateValueFromExpression("str",expr); + return num_children_vo.GetSummary() + +def GetSummary_Impl(valobj): + global statistics + class_data = objc_runtime.ObjCRuntime(valobj) + if class_data.is_valid() == False: + statistics.metric_hit('invalid_pointer',valobj) + wrapper = None + return + class_data = class_data.read_class_data() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + if class_data.is_kvo(): + class_data = class_data.get_superclass() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + + name_string = class_data.class_name() + if name_string == 'NSNumber' or name_string == '__NSCFNumber': + if class_data.is_tagged(): + wrapper = NSTaggedNumber_SummaryProvider(valobj,class_data.info_bits(),class_data.value()) + statistics.metric_hit('code_notrun',valobj) + else: + # the wrapper might be unable to decipher what is into the NSNumber + # and then have to run code on it + wrapper = NSUntaggedNumber_SummaryProvider(valobj) + else: + wrapper = NSUnknownNumber_SummaryProvider(valobj) + statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string) + return wrapper; + + +def NSNumber_SummaryProvider (valobj,dict): + provider = GetSummary_Impl(valobj); + if provider != None: + #try: + summary = provider.value(); + #except: + # summary = None + if summary == None: + summary = 'no valid number here' + return str(summary) + return '' + + +def __lldb_init_module(debugger,dict): + debugger.HandleCommand("type summary add -F NSNumber.NSNumber_SummaryProvider NSNumber") + diff --git a/lldb/examples/summaries/cocoa/NSSet.py b/lldb/examples/summaries/cocoa/NSSet.py new file mode 100644 index 00000000000..3ca561cd8f8 --- /dev/null +++ b/lldb/examples/summaries/cocoa/NSSet.py @@ -0,0 +1,211 @@ +# summary provider for NSSet +import lldb +import ctypes +import objc_runtime +import metrics + +statistics = metrics.Metrics() +statistics.add_metric('invalid_isa') +statistics.add_metric('invalid_pointer') +statistics.add_metric('unknown_class') +statistics.add_metric('code_notrun') + +# despite the similary to synthetic children providers, these classes are not +# trying to provide anything but the port number of an NSMachPort, so they need not +# obey the interface specification for synthetic children providers +class NSCFSet_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + + # one pointer is the ISA + # then we have one other internal pointer, plus + # 4 bytes worth of flags. hence, these values + def offset(self): + if self.lp64: + return 20 + else: + return 12 + + def count(self): + vcount = self.valobj.CreateChildAtOffset("count", + self.offset(), + self.NSUInteger) + return vcount.GetValueAsUnsigned(0) + + +class NSSetUnknown_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def count(self): + stream = lldb.SBStream() + self.valobj.GetExpressionPath(stream) + expr = "(int)[" + stream.GetData() + " count]" + num_children_vo = self.valobj.CreateValueFromExpression("count",expr); + return num_children_vo.GetValueAsUnsigned(0) + +class NSSetI_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + if self.lp64: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + else: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt) + + # we just need to skip the ISA and the count immediately follows + def offset(self): + if self.lp64: + return 8 + else: + return 4 + + def count(self): + num_children_vo = self.valobj.CreateChildAtOffset("count", + self.offset(), + self.NSUInteger) + value = num_children_vo.GetValueAsUnsigned(0) + if value != None: + # the MSB on immutable sets seems to be taken by some other data + # not sure if it is a bug or some weird sort of feature, but masking it out + # gets the count right (unless, of course, someone's dictionaries grow + # too large - but I have not tested this) + if self.lp64: + value = value & ~0xFF00000000000000 + else: + value = value & ~0xFF000000 + return value + +class NSSetM_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + if self.lp64: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + else: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt) + + # we just need to skip the ISA and the count immediately follows + def offset(self): + if self.lp64: + return 8 + else: + return 4 + + def count(self): + num_children_vo = self.valobj.CreateChildAtOffset("count", + self.offset(), + self.NSUInteger) + return num_children_vo.GetValueAsUnsigned(0) + + +def GetSummary_Impl(valobj): + global statistics + class_data = objc_runtime.ObjCRuntime(valobj) + if class_data.is_valid() == False: + statistics.metric_hit('invalid_pointer',valobj) + wrapper = None + return + class_data = class_data.read_class_data() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + if class_data.is_kvo(): + class_data = class_data.get_superclass() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + + name_string = class_data.class_name() + if name_string == '__NSCFSet': + wrapper = NSCFSet_SummaryProvider(valobj) + statistics.metric_hit('code_notrun',valobj) + elif name_string == '__NSSetI': + wrapper = NSSetI_SummaryProvider(valobj) + statistics.metric_hit('code_notrun',valobj) + elif name_string == '__NSSetM': + wrapper = NSSetM_SummaryProvider(valobj) + statistics.metric_hit('code_notrun',valobj) + else: + wrapper = NSSetUnknown_SummaryProvider(valobj) + statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string) + return wrapper; + + +def NSSet_SummaryProvider (valobj,dict): + provider = GetSummary_Impl(valobj); + if provider != None: + #try: + summary = provider.count(); + #except: + # summary = None + if summary == None: + summary = 'no valid set here' + return str(summary) + ' objects' + return '' + +def NSSet_SummaryProvider2 (valobj,dict): + provider = GetSummary_Impl(valobj); + if provider != None: + try: + summary = provider.count(); + except: + summary = None + # for some reason, one needs to clear some bits for the count returned + # to be correct when using directly CF*SetRef as compared to NS*Set + # this only happens on 64bit, and the bit mask was derived through + # experimentation (if counts start looking weird, then most probably + # the mask needs to be changed) + if summary == None: + summary = 'no valid set here' + else: + if provider.lp64: + summary = int(summary) & ~0x1fff000000000000 + return str(summary) + ' objects' + return '' + + +def __lldb_init_module(debugger,dict): + debugger.HandleCommand("type summary add -F NSSet.NSSet_SummaryProvider NSSet") + debugger.HandleCommand("type summary add -F NSSet.NSSet_SummaryProvider2 CFSetRef CFMutableSetRef") diff --git a/lldb/examples/summaries/cocoa/NSURL.py b/lldb/examples/summaries/cocoa/NSURL.py new file mode 100644 index 00000000000..6c15b402213 --- /dev/null +++ b/lldb/examples/summaries/cocoa/NSURL.py @@ -0,0 +1,124 @@ +# summary provider for NSURL +import lldb +import ctypes +import objc_runtime +import metrics +import CFString + +statistics = metrics.Metrics() +statistics.add_metric('invalid_isa') +statistics.add_metric('invalid_pointer') +statistics.add_metric('unknown_class') +statistics.add_metric('code_notrun') + +# despite the similary to synthetic children providers, these classes are not +# trying to provide anything but a summary for an NSURL, so they need not +# obey the interface specification for synthetic children providers +class NSURLKnown_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update(); + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + if self.lp64: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) + self.pointer_size = 8 + else: + self.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt) + self.pointer_size = 4 + self.NSString = self.valobj.GetTarget().FindFirstType('NSString') + self.NSURL = self.valobj.GetTarget().FindFirstType('NSURL') + + # one pointer is the ISA + # then there is one more pointer and 8 bytes of plain data + # (which are also present on a 32-bit system) + # plus another pointer, and then the real data + def offset(self): + if self.lp64: + return 24 + else: + return 16 + + def url_text(self): + text = self.valobj.CreateChildAtOffset("text", + self.offset(), + self.NSString.GetPointerType()) + base = self.valobj.CreateChildAtOffset("base", + self.offset()+self.pointer_size, + self.NSURL.GetPointerType()) + my_string = CFString.CFString_SummaryProvider(text,None) + if base.GetValueAsUnsigned(0) != 0: + my_string = my_string + " (base path: " + NSURL_SummaryProvider(base,None) + ")" + return my_string + + +class NSURLUnknown_SummaryProvider: + def adjust_for_architecture(self): + self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) + self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) + self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() + + def __init__(self, valobj): + self.valobj = valobj; + self.update() + + def update(self): + self.adjust_for_architecture(); + self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) + + def url_text(self): + stream = lldb.SBStream() + self.valobj.GetExpressionPath(stream) + url_text_vo = self.valobj.CreateValueFromExpression("url","(NSString*)[" + stream.GetData() + " description]"); + return CFString.CFString_SummaryProvider(url_text_vo,None) + + +def GetSummary_Impl(valobj): + global statistics + class_data = objc_runtime.ObjCRuntime(valobj) + if class_data.is_valid() == False: + statistics.metric_hit('invalid_pointer',valobj) + wrapper = None + return + class_data = class_data.read_class_data() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + if class_data.is_kvo(): + class_data = class_data.get_superclass() + if class_data.is_valid() == False: + statistics.metric_hit('invalid_isa',valobj) + wrapper = None + return + + name_string = class_data.class_name() + if name_string == 'NSURL': + wrapper = NSURLKnown_SummaryProvider(valobj) + statistics.metric_hit('code_notrun',valobj) + else: + wrapper = NSURLUnknown_SummaryProvider(valobj) + statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string) + return wrapper; + +def NSURL_SummaryProvider (valobj,dict): + provider = GetSummary_Impl(valobj); + if provider != None: + try: + summary = provider.url_text(); + except: + summary = None + if summary == None or summary == '': + summary = 'no valid NSURL here' + return summary + return '' + +def __lldb_init_module(debugger,dict): + debugger.HandleCommand("type summary add -F NSURL.NSURL_SummaryProvider NSURL CFURLRef") diff --git a/lldb/examples/summaries/cocoa/cache.py b/lldb/examples/summaries/cocoa/cache.py new file mode 100644 index 00000000000..01a79a45196 --- /dev/null +++ b/lldb/examples/summaries/cocoa/cache.py @@ -0,0 +1,28 @@ +import metrics + +class Cache: + def __init__(self): + self.data = {} + self.statistics = metrics.Metrics() + self.statistics.add_metric('hit') + self.statistics.add_metric('miss') + + def look_for_key(self,key): + if key in self.data: + return True + return False + + def add_item(self,key,value,ok_to_replace=True): + if not(ok_to_replace) and self.look_for_key(key): + return False + self.data[key] = value + return True + + def get_value(self,key,default=None): + if self.look_for_key(key): + self.statistics.metric_hit('hit',key) + return self.data[key] + else: + self.statistics.metric_hit('miss',key) + return default + diff --git a/lldb/examples/summaries/cocoa/metrics.py b/lldb/examples/summaries/cocoa/metrics.py new file mode 100644 index 00000000000..bf9a025d183 --- /dev/null +++ b/lldb/examples/summaries/cocoa/metrics.py @@ -0,0 +1,62 @@ +import lldb + +class Counter: + def __init__(self): + self.count = 0 + self.list = [] + def update(self,name): + self.count = self.count + 1 + self.list.append(str(name)) + def __str__(self): + return str(self.count) + " times, for items [" + str(self.list) + "]" + +class MetricsPrinter_Verbose: + def __init__(self,metrics): + self.metrics = metrics + def __str__(self): + string = "" + for key,value in self.metrics.metrics.items(): + string = string + "metric " + str(key) + ": " + str(value) + "\n" + return string + +class MetricsPrinter_Compact: + def __init__(self,metrics): + self.metrics = metrics + def __str__(self): + string = "" + for key,value in self.metrics.metrics.items(): + string = string + "metric " + str(key) + " was hit " + str(value.count) + " times\n" + return string + +class Metrics: + def __init__(self): + self.metrics = {} + + def add_metric(self,name): + self.metrics[name] = Counter() + + def metric_hit(self,metric,trigger): + self.metrics[metric].update(trigger) + + def __getitem__(self,key): + return self.metrics[key] + + def __getattr__(self,name): + if name == 'compact': + return MetricsPrinter_Compact(self) + if name == 'verbose': + return MetricsPrinter_Verbose(self) + raise AttributeError("%r object has no attribute %r" % + (type(self).__name__, name)) + + def __str__(self): + return str(self.verbose) + + def metric_success(self,metric): + total_count = 0 + metric_count = self[metric].count + for key,value in self.metrics.items(): + total_count = total_count + value.count + if total_count > 0: + return metric_count / float(total_count) + return 0 diff --git a/lldb/examples/summaries/cocoa/objc_lldb.py b/lldb/examples/summaries/cocoa/objc_lldb.py new file mode 100644 index 00000000000..a81be013159 --- /dev/null +++ b/lldb/examples/summaries/cocoa/objc_lldb.py @@ -0,0 +1,132 @@ +""" +Objective-C runtime wrapper - Replicates the behavior of AppleObjCRuntimeV2.cpp in Python code +for the benefit of synthetic children providers and Python summaries + +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 + +class ObjCRuntime: + + def __init__(self,valobj = None): + self.valobj = valobj; + self.adjust_for_architecture() + + def adjust_for_architecture(self): + self.lp64 = (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() + + def is_tagged(self): + if valobj is None: + return None + ptr_value = self.valobj.GetPointerValue() + if (ptr_value % 2) == 1: + return True + else: + return False + + def read_ascii(self, pointer): + process = self.valobj.GetTarget().GetProcess() + error = lldb.SBError() + pystr = '' + # cannot do the read at once because there is no length byte + while True: + content = process.ReadMemory(pointer, 1, error) + new_bytes = bytearray(content) + b0 = new_bytes[0] + pointer = pointer + 1 + if b0 == 0: + break + pystr = pystr + chr(b0) + return pystr + + def read_isa(self): + # read ISA pointer + isa_pointer = self.valobj.CreateChildAtOffset("cfisa", + 0, + self.addr_ptr_type) + if isa_pointer == None or isa_pointer.IsValid() == False: + return None; + if isa_pointer.GetValue() == None: + return None; + isa = int(isa_pointer.GetValue(), 0) + if isa == 0 or isa == None: + return None; + return isa + + + def get_parent_class(self, isa = None): + if isa is None: + isa = self.read_isa() + if isa is None: + return None + # read superclass pointer + rw_pointer = isa + self.pointer_size + rw_object = self.valobj.CreateValueFromAddress("parent_isa", + rw_pointer, + self.addr_type) + if rw_object == None or rw_object.IsValid() == False: + return None; + if rw_object.GetValue() == None: + return None; + rw = int(rw_object.GetValue(), 0) + if rw == 0 or rw == None: + return None; + return rw + + def get_class_name(self, isa = None): + if isa is None: + isa = self.read_isa() + if isa is None: + return None + # read rw pointer + rw_pointer = isa + 4 * self.pointer_size + rw_object = self.valobj.CreateValueFromAddress("rw", + rw_pointer, + self.addr_type) + if rw_object == None or rw_object.IsValid() == False: + return None; + if rw_object.GetValue() == None: + return None; + rw = int(rw_object.GetValue(), 0) + if rw == 0 or rw == None: + return None; + + # read data pointer + data_pointer = rw + 8 + data_object = self.valobj.CreateValueFromAddress("data", + data_pointer, + self.addr_type) + if data_object == None or data_object.IsValid() == False: + return None; + if data_object.GetValue() == None: + return None; + data = int(data_object.GetValue(), 0) + if data == 0 or data == None: + return None; + + # read ro pointer + ro_pointer = data + 12 + self.pointer_size + if self.lp64: + ro_pointer += 4 + ro_object = self.valobj.CreateValueFromAddress("ro", + ro_pointer, + self.addr_type) + if ro_object == None or ro_object.IsValid() == False: + return None; + if ro_object.GetValue() == None: + return None; + name_pointer = int(ro_object.GetValue(), 0) + if name_pointer == 0 or name_pointer == None: + return None; + + # now read the actual name and compare it to known stuff + name_string = self.read_ascii(name_pointer) + if (name_string.startswith("NSKVONotify")): + return self.get_class_name(self.get_parent_class()) + return name_string diff --git a/lldb/examples/summaries/cocoa/objc_runtime.py b/lldb/examples/summaries/cocoa/objc_runtime.py new file mode 100644 index 00000000000..6766bf307cc --- /dev/null +++ b/lldb/examples/summaries/cocoa/objc_runtime.py @@ -0,0 +1,500 @@ +# a wrapper for the Objective-C runtime for use by LLDB +import lldb +import cache + +class Utilities: + @staticmethod + def read_ascii(process, pointer,max_len=128,when_over_return_none=True): + error = lldb.SBError() + pystr = '' + count = 0 + # there is not any length byte to tell us how much to read + # however, there are most probably ways to optimize this + # in order to avoid doing the read byte-by-byte (caching is + # still occurring, but we could just fetch a larger chunk + # of memory instead of going one byte at a time) + while True: + content = process.ReadMemory(pointer, 1, error) + new_bytes = bytearray(content) + if new_bytes == None or len(new_bytes) == 0: + break + b0 = new_bytes[0] + pointer = pointer + 1 + if b0 == 0: + break + count = count + 1 + if count > max_len: + if when_over_return_none: + return None + else: + return pystr + pystr = pystr + chr(b0) + return pystr + + @staticmethod + def is_valid_pointer(pointer, pointer_size, allow_tagged): + if pointer == None: + return False + if pointer == 0: + return False + if allow_tagged: + if (pointer % 2) == 1: + return True + return ((pointer % pointer_size) == 0) + + # Objective-C runtime has a rule that pointers in a class_t will only have bits 0 thru 46 set + # so if any pointer has bits 47 thru 63 high we know that this is not a valid isa + @staticmethod + def is_allowed_pointer(pointer): + if pointer == None: + return False + mask = 0xFFFF800000000000 + if (pointer & mask) != 0: + return False + return True + + @staticmethod + def read_child_of(valobj,offset,type): + child = valobj.CreateChildAtOffset("childUNK",offset,type) + if child == None or child.IsValid() == False: + return None; + return child.GetValueAsUnsigned() + + @staticmethod + def is_valid_identifier(name): + if name is None: + return None + if len(name) == 0: + return None + return True + # the rules below make perfect sense for compile-time class names, but the runtime is free + # to create classes with arbitrary names to implement functionality (e.g -poseAsClass) + # hence, we cannot really outlaw anything appearing in an identifier + #ok_first = dict.fromkeys("$ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_") + #ok_others = dict.fromkeys("$ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_1234567890") + #if name[0] not in ok_first: + # return False + #return all(c in ok_others for c in name[1:]) + +class RoT_Data: + def __init__(self,rot_pointer,params): + if (Utilities.is_valid_pointer(rot_pointer.GetValueAsUnsigned(),params.pointer_size, allow_tagged=False)): + 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) + if self.sys_params.lp64: + self.reserved = Utilities.read_child_of(self.valobj,12,self.sys_params.uint32_t) + offset = 16 + else: + self.reserved = 0 + offset = 12 + self.ivarLayoutPtr = Utilities.read_child_of(self.valobj,offset,self.sys_params.addr_ptr_type) + offset = offset + self.sys_params.pointer_size + self.namePointer = Utilities.read_child_of(self.valobj,offset,self.sys_params.addr_ptr_type) + self.check_valid() + else: + self.valid = False + if self.valid: + self.name = Utilities.read_ascii(self.valobj.GetTarget().GetProcess(),self.namePointer) + if not(Utilities.is_valid_identifier(self.name)): + self.valid = False + + # perform sanity checks on the contents of this class_rw_t + def check_valid(self): + self.valid = True + # misaligned pointers seem to be possible for this field + #if not(Utilities.is_valid_pointer(self.namePointer,self.sys_params.pointer_size,allow_tagged=False)): + # self.valid = False + # pass + + def __str__(self): + return 'flags = ' + str(self.flags) + "\n" + \ + "instanceStart = " + hex(self.instanceStart) + "\n" + \ + "instanceSize = " + hex(self.instanceSize) + "\n" + \ + "reserved = " + hex(self.reserved) + "\n" + \ + "ivarLayoutPtr = " + hex(self.ivarLayoutPtr) + "\n" + \ + "namePointer = " + hex(self.namePointer) + " --> " + self.name + + def is_valid(self): + return self.valid + + +class RwT_Data: + def __init__(self,rwt_pointer,params): + if (Utilities.is_valid_pointer(rwt_pointer.GetValueAsUnsigned(),params.pointer_size, allow_tagged=False)): + self.sys_params = params + 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.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.data = RoT_Data(self.rot,self.sys_params) + + # perform sanity checks on the contents of this class_rw_t + def check_valid(self): + self.valid = True + if not(Utilities.is_valid_pointer(self.roPointer,self.sys_params.pointer_size,allow_tagged=False)): + self.valid = False + + def __str__(self): + return 'flags = ' + str(self.flags) + "\n" + \ + "version = " + hex(self.version) + "\n" + \ + "roPointer = " + hex(self.roPointer) + + def is_valid(self): + if self.valid: + return self.data.is_valid() + return False + +class Class_Data_V2: + def __init__(self,isa_pointer,params): + 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.data = RwT_Data(self.rwt,self.sys_params) + + # perform sanity checks on the contents of this class_t + def check_valid(self): + self.valid = True + 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.cachePointer,self.sys_params.pointer_size,allow_tagged=False)): + self.valid = False + return + 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)): + self.valid = False + return + if not(Utilities.is_allowed_pointer(self.isaPointer)): + 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)): + self.valid = False + return + if not(Utilities.is_allowed_pointer(self.vtablePointer)): + self.valid = False + return + if not(Utilities.is_allowed_pointer(self.dataPointer)): + self.valid = False + return + + def is_kvo(self): + if self.is_valid(): + if self.data.data.name.startswith("NSKVONotify"): + return True + else: + return None + + def get_superclass(self): + if self.is_valid(): + parent_isa_pointer = self.valobj.CreateChildAtOffset("parent_isa", + self.sys_params.pointer_size, + self.sys_params.addr_ptr_type) + return Class_Data_V2(parent_isa_pointer,self.sys_params) + else: + return None + + def class_name(self): + if self.is_valid(): + return self.data.data.name + else: + return None + + def is_valid(self): + if self.valid: + return self.data.is_valid() + return False + + def __str__(self): + return 'isaPointer = ' + hex(self.isaPointer) + "\n" + \ + "superclassIsaPointer = " + hex(self.superclassIsaPointer) + "\n" + \ + "cachePointer = " + hex(self.cachePointer) + "\n" + \ + "vtablePointer = " + hex(self.vtablePointer) + "\n" + \ + "data = " + hex(self.dataPointer) + + def is_tagged(self): + return False + +# runtime v1 is much less intricate than v2 and stores relevant information directly in the class_t object +# idea for improvement: read additional information just to check for faulty pointers (not too worried about it +# since v1 is only used for 32bit desktop development) +class Class_Data_V1: + def __init__(self,isa_pointer,params): + 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.namePointer = Utilities.read_child_of(self.valobj,2*self.sys_params.pointer_size,self.sys_params.addr_ptr_type) + self.check_valid() + else: + self.valid = False + if self.valid: + self.name = Utilities.read_ascii(self.valobj.GetTarget().GetProcess(),self.namePointer) + if not(Utilities.is_valid_identifier(self.name)): + self.valid = False + + # perform sanity checks on the contents of this class_t + def check_valid(self): + self.valid = True + 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)): + # self.valid = False + # return + + def is_kvo(self): + if self.is_valid(): + if self.name.startswith("NSKVONotify"): + return True + else: + return None + + def get_superclass(self): + if self.is_valid(): + parent_isa_pointer = self.valobj.CreateChildAtOffset("parent_isa", + self.sys_params.pointer_size, + self.sys_params.addr_ptr_type) + return Class_Data_V1(parent_isa_pointer,self.sys_params) + else: + return None + + def class_name(self): + if self.is_valid(): + return self.name + else: + return None + + def is_valid(self): + return self.valid + + def __str__(self): + return 'isaPointer = ' + hex(self.isaPointer) + "\n" + \ + "superclassIsaPointer = " + hex(self.superclassIsaPointer) + "\n" + \ + "namePointer = " + hex(self.namePointer) + " --> " + self.name + + def is_tagged(self): + return False + +class TaggedClass_Data: + def __init__(self,pointer,params): + self.valid = True + self.name = None + self.sys_params = params + self.valobj = pointer + self.val = (pointer & ~0x0000000000000000FF) >> 8 + self.class_bits = (pointer & 0xE) >> 1 + self.i_bits = (pointer & 0xF0) >> 4 + # ignoring the LSB, NSNumber gets marked + # as 3 on Zin and as 1 on Lion - I can either make + # a difference or accept false positives + # ToT LLDB + some knowledge of framework versioning + # would let me do the right thing - for now I just + # act dumb and accept false positives + if self.class_bits == 0 or \ + self.class_bits == 5 or \ + self.class_bits == 7 or \ + self.class_bits == 9: + self.valid = False + return + elif self.class_bits == 3 or self.class_bits == 1: + self.name = 'NSNumber' + elif self.class_bits == 11: + self.name = 'NSManagedObject' + elif self.class_bits == 13: + self.name = 'NSDate' + elif self.class_bits == 15: + self.name = 'NSDateTS' # not sure what this is + else: + self.valid = False + + def is_valid(self): + return self.valid + + def class_name(self): + if self.is_valid(): + return self.name + else: + return None + + def value(self): + return self.val if self.is_valid() else None + + def info_bits(self): + return self.i_bits if self.is_valid() else None + + def is_kvo(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 + def get_superclass(self): + return None + + # anything that is handled here is tagged + def is_tagged(self): + return True + +class InvalidClass_Data: + def __init__(self): + pass + def is_valid(self): + return False + +runtime_version = cache.Cache() + +class SystemParameters: + def __init__(self,valobj): + self.adjust_for_architecture(valobj) + + def adjust_for_architecture(self,valobj): + self.process = valobj.GetTarget().GetProcess() + self.lp64 = (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) + global runtime_version + pid = self.process.GetProcessID() + if runtime_version.look_for_key(pid) == None: + self.runtime_version = runtime_version.get_value(pid) + else: + self.runtime_version = ObjCRuntime.runtime_version(self.process) + runtime_version.add_item(pid,self.runtime_version) + +isa_cache = cache.Cache() + +class ObjCRuntime: + + # the ObjC runtime has no explicit "version" field that we can use + # instead, we discriminate v1 from v2 by looking for the presence + # of a well-known section only present in v1 + @staticmethod + def runtime_version(process): + if process.IsValid() == False: + return None + target = process.GetTarget() + num_modules = target.GetNumModules() + module_objc = None + for idx in range(num_modules): + module = target.GetModuleAtIndex(idx) + if module.GetFileSpec().GetFilename() == 'libobjc.A.dylib': + module_objc = module + break + if module_objc == None or module_objc.IsValid() == False: + return None + num_sections = module.GetNumSections() + section_objc = None + for idx in range(num_sections): + section = module.GetSectionAtIndex(idx) + if section.GetName() == '__OBJC': + section_objc = section + break + if section_objc != None and section_objc.IsValid(): + return 1 + return 2 + + def __init__(self,valobj): + self.valobj = valobj + self.adjust_for_architecture() + self.sys_params = SystemParameters(self.valobj) + + def adjust_for_architecture(self): + self.lp64 = (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() + +# 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.valobj.GetValueAsUnsigned(),self.pointer_size, allow_tagged=True) and not(Utilities.is_valid_pointer(self.valobj.GetValueAsUnsigned(),self.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.valobj.GetValueAsUnsigned(),self.pointer_size, allow_tagged=True) + + def read_isa(self): + isa_pointer = self.valobj.CreateChildAtOffset("cfisa", + 0, + self.addr_ptr_type) + if isa_pointer == None or isa_pointer.IsValid() == False: + return None; + if isa_pointer.GetValueAsUnsigned(1) == 1: + return None; + return isa_pointer + + def read_class_data(self): + global isa_cache + if self.is_tagged(): + # tagged pointers only exist in ObjC v2 + if self.sys_params.runtime_version == 2: + # not every odd-valued pointer is actually tagged. most are just plain wrong + # we could try and predetect this before even creating a TaggedClass_Data object + # but unless performance requires it, this seems a cleaner way to tackle the task + tentative_tagged = TaggedClass_Data(self.valobj.GetValueAsUnsigned(0),self.sys_params) + return tentative_tagged if tentative_tagged.is_valid() else InvalidClass_Data() + else: + return InvalidClass_Data() + if self.is_valid() == False: + return InvalidClass_Data() + isa = self.read_isa() + if isa == None: + return InvalidClass_Data() + isa_value = isa.GetValueAsUnsigned(1) + if isa_value == 1: + return InvalidClass_Data() + data = isa_cache.get_value(isa_value,default=None) + if data != None: + return data + if self.sys_params.runtime_version == 2: + data = Class_Data_V2(isa,self.sys_params) + else: + data = Class_Data_V1(isa,self.sys_params) + if data == None: + return InvalidClass_Data() + if data.is_valid(): + isa_cache.add_item(isa_value,data,ok_to_replace=True) + return data |