# 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.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() 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.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() self.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.is_64_bit == 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.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() 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.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() 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.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() self.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 summary + " objects" return '' def __lldb_init_module(debugger,dict): debugger.HandleCommand("type summary add -F CFArray.CFArray_SummaryProvider NSArray CFArrayRef CFMutableArrayRef")