1# synthetic children provider for NSArray 2import lldb 3import ctypes 4import objc_runtime 5import metrics 6 7statistics = metrics.Metrics() 8statistics.add_metric('invalid_isa') 9statistics.add_metric('invalid_pointer') 10statistics.add_metric('unknown_class') 11statistics.add_metric('code_notrun') 12 13# much less functional than the other two cases below 14# just runs code to get to the count and then returns 15# no children 16class NSArrayKVC_SynthProvider: 17 18 def adjust_for_architecture(self): 19 self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) 20 self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) 21 self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() 22 23 def __init__(self, valobj, dict): 24 self.valobj = valobj; 25 self.update() 26 27 def update(self): 28 self.adjust_for_architecture(); 29 self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) 30 31 def num_children(self): 32 stream = lldb.SBStream() 33 self.valobj.GetExpressionPath(stream) 34 num_children_vo = self.valobj.CreateValueFromExpression("count","(int)[" + stream.GetData() + " count]"); 35 return num_children_vo.GetValueAsUnsigned(0) 36 37 def get_child_index(self,name): 38 if name == "len": 39 return self.num_children(); 40 else: 41 return None 42 43 def get_child_at_index(self, index): 44 return None 45 46 47 48# much less functional than the other two cases below 49# just runs code to get to the count and then returns 50# no children 51class NSArrayCF_SynthProvider: 52 53 def adjust_for_architecture(self): 54 self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) 55 self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) 56 self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() 57 self.cfruntime_size = self.size_of_cfruntime_base() 58 59 # CFRuntimeBase is defined as having an additional 60 # 4 bytes (padding?) on LP64 architectures 61 # to get its size we add up sizeof(pointer)+4 62 # and then add 4 more bytes if we are on a 64bit system 63 def size_of_cfruntime_base(self): 64 if self.is_64_bit == True: 65 return 8+4+4; 66 else: 67 return 4+4; 68 69 def __init__(self, valobj, dict): 70 self.valobj = valobj; 71 self.update() 72 73 def update(self): 74 self.adjust_for_architecture(); 75 self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) 76 77 def num_children(self): 78 num_children_vo = self.valobj.CreateChildAtOffset("count", 79 self.cfruntime_size, 80 self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)) 81 return num_children_vo.GetValueAsUnsigned(0) 82 83 def get_child_index(self,name): 84 if name == "len": 85 return self.num_children(); 86 else: 87 return None 88 89 def get_child_at_index(self, index): 90 return None 91 92 93class NSArrayI_SynthProvider: 94 95 def adjust_for_architecture(self): 96 self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) 97 self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) 98 self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() 99 100 def __init__(self, valobj, dict): 101 self.valobj = valobj; 102 self.update() 103 104 def update(self): 105 self.adjust_for_architecture(); 106 self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) 107 108 # skip the isa pointer and get at the size 109 def num_children(self): 110 offset = self.pointer_size; 111 datatype = self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong) 112 count = self.valobj.CreateChildAtOffset("count", 113 offset, 114 datatype); 115 return int(count.GetValue(), 0) 116 117 def get_child_index(self,name): 118 if name == "len": 119 return self.num_children(); 120 else: 121 return int(name.lstrip('[').rstrip(']'), 0) 122 123 def get_child_at_index(self, index): 124 if index == self.num_children(): 125 return self.valobj.CreateValueFromExpression("len", 126 str(index)) 127 offset = 2 * self.pointer_size + self.id_type.GetByteSize()*index 128 return self.valobj.CreateChildAtOffset('[' + str(index) + ']', 129 offset, 130 self.id_type) 131 132 133class NSArrayM_SynthProvider: 134 135 def adjust_for_architecture(self): 136 self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) 137 self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) 138 self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() 139 140 def __init__(self, valobj, dict): 141 self.valobj = valobj; 142 self.update(); 143 144 def update(self): 145 self.adjust_for_architecture(); 146 self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) 147 148 # skip the isa pointer and get at the size 149 def num_children(self): 150 offset = self.pointer_size; 151 datatype = self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong) 152 count = self.valobj.CreateChildAtOffset("count", 153 offset, 154 datatype); 155 return int(count.GetValue(), 0) 156 157 def get_child_index(self,name): 158 if name == "len": 159 return self.num_children(); 160 else: 161 return int(name.lstrip('[').rstrip(']'), 0) 162 163 def data_offset(self): 164 offset = self.pointer_size; # isa 165 offset += self.pointer_size; # _used 166 offset += self.pointer_size; # _doHardRetain, _doWeakAccess, _size 167 offset += self.pointer_size; # _hasObjects, _hasStrongReferences, _offset 168 offset += self.pointer_size; # _mutations 169 return offset; 170 171 # the _offset field is used to calculate the actual offset 172 # when reading a value out of the array. we need to read it 173 # to do so we read a whole pointer_size of data from the 174 # right spot, and then zero out the two LSB 175 def read_offset_field(self): 176 disp = self.pointer_size; # isa 177 disp += self.pointer_size; # _used 178 disp += self.pointer_size; # _doHardRetain, _doWeakAccess, _size 179 offset = self.valobj.CreateChildAtOffset("offset", 180 disp, 181 self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong)) 182 offset_value = int(offset.GetValue(), 0) 183 offset_value = ctypes.c_uint32((offset_value & 0xFFFFFFFC) >> 2).value 184 return offset_value 185 186 # the _used field tells how many items are in the array 187 # but since this is a mutable array, it allocates more space 188 # for performance reasons. we need to get the real _size of 189 # the array to calculate the actual offset of each element 190 # in get_child_at_index() (see NSArray.m for details) 191 def read_size_field(self): 192 disp = self.pointer_size; # isa 193 disp += self.pointer_size; # _used 194 size = self.valobj.CreateChildAtOffset("size", 195 disp, 196 self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong)) 197 size_value = int(size.GetValue(), 0) 198 size_value = ctypes.c_uint32((size_value & 0xFFFFFFFA) >> 2).value 199 return size_value 200 201 def get_child_at_index(self, index): 202 if index == self.num_children(): 203 return self.valobj.CreateValueFromExpression("len", 204 str(index)) 205 size = self.read_size_field() 206 offset = self.read_offset_field() 207 phys_idx = offset + index 208 if size <= phys_idx: 209 phys_idx -=size; 210 # we still need to multiply by element size to do a correct pointer read 211 phys_idx *= self.id_type.GetByteSize() 212 list_ptr = self.valobj.CreateChildAtOffset("_list", 213 self.data_offset(), 214 self.id_type.GetBasicType(lldb.eBasicTypeUnsignedLongLong)) 215 list_addr = int(list_ptr.GetValue(), 0) 216 return self.valobj.CreateValueFromAddress('[' + str(index) + ']', 217 list_addr + phys_idx, 218 self.id_type) 219 220# this is the actual synth provider, but is just a wrapper that checks 221# whether valobj is an instance of __NSArrayI or __NSArrayM and sets up an 222# appropriate backend layer to do the computations 223class NSArray_SynthProvider: 224 225 def adjust_for_architecture(self): 226 self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8) 227 self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle) 228 self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() 229 self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID) 230 231 def __init__(self, valobj, dict): 232 self.valobj = valobj; 233 self.adjust_for_architecture() 234 self.wrapper = self.make_wrapper(valobj,dict) 235 self.invalid = (self.wrapper == None) 236 237 def get_child_at_index(self, index): 238 if self.wrapper == None: 239 return None; 240 return self.wrapper.get_child_at_index(index) 241 242 def get_child_index(self,name): 243 if self.wrapper == None: 244 return None; 245 return self.wrapper.get_child_index(name) 246 247 def num_children(self): 248 if self.wrapper == None: 249 return 0; 250 return self.wrapper.num_children() 251 252 def update(self): 253 if self.wrapper == None: 254 return None; 255 return self.wrapper.update() 256 257 def read_ascii(self, pointer): 258 process = self.valobj.GetTarget().GetProcess() 259 error = lldb.SBError() 260 pystr = '' 261 # cannot do the read at once because there is no length byte 262 while True: 263 content = process.ReadMemory(pointer, 1, error) 264 new_bytes = bytearray(content) 265 b0 = new_bytes[0] 266 pointer = pointer + 1 267 if b0 == 0: 268 break 269 pystr = pystr + chr(b0) 270 return pystr 271 272 # this code acts as our defense against NULL and unitialized 273 # NSArray pointers, which makes it much longer than it would be otherwise 274 def make_wrapper(self,valobj,dict): 275 global statistics 276 class_data = objc_runtime.ObjCRuntime(valobj) 277 if class_data.is_valid() == False: 278 statistics.metric_hit('invalid_pointer',valobj) 279 wrapper = None 280 return 281 class_data = class_data.read_class_data() 282 if class_data.is_valid() == False: 283 statistics.metric_hit('invalid_isa',valobj) 284 wrapper = None 285 return 286 if class_data.is_kvo(): 287 class_data = class_data.get_superclass() 288 if class_data.is_valid() == False: 289 statistics.metric_hit('invalid_isa',valobj) 290 wrapper = None 291 return 292 293 name_string = class_data.class_name() 294 if name_string == '__NSArrayI': 295 wrapper = NSArrayI_SynthProvider(valobj, dict) 296 statistics.metric_hit('code_notrun',valobj) 297 elif name_string == '__NSArrayM': 298 wrapper = NSArrayM_SynthProvider(valobj, dict) 299 statistics.metric_hit('code_notrun',valobj) 300 elif name_string == '__NSCFArray': 301 wrapper = NSArrayCF_SynthProvider(valobj, dict) 302 statistics.metric_hit('code_notrun',valobj) 303 else: 304 wrapper = NSArrayKVC_SynthProvider(valobj, dict) 305 statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string) 306 return wrapper; 307 308def CFArray_SummaryProvider (valobj,dict): 309 provider = NSArray_SynthProvider(valobj,dict); 310 if provider.invalid == False: 311 try: 312 summary = str(provider.num_children()); 313 except: 314 summary = None 315 if summary == None: 316 summary = 'no valid array here' 317 return summary + " objects" 318 return '' 319 320def __lldb_init_module(debugger,dict): 321 debugger.HandleCommand("type summary add -F CFArray.CFArray_SummaryProvider NSArray CFArrayRef CFMutableArrayRef") 322