blob: c8864269322621c9ae24432c5c7effa44229f343 [file] [log] [blame]
Enrico Granataeb4a4792012-02-23 23:10:27 +00001# a wrapper for the Objective-C runtime for use by LLDB
2import lldb
3import cache
4
5class Utilities:
6 @staticmethod
7 def read_ascii(process, pointer,max_len=128,when_over_return_none=True):
8 error = lldb.SBError()
9 pystr = ''
10 count = 0
11 # there is not any length byte to tell us how much to read
12 # however, there are most probably ways to optimize this
13 # in order to avoid doing the read byte-by-byte (caching is
14 # still occurring, but we could just fetch a larger chunk
15 # of memory instead of going one byte at a time)
16 while True:
17 content = process.ReadMemory(pointer, 1, error)
18 new_bytes = bytearray(content)
19 if new_bytes == None or len(new_bytes) == 0:
20 break
21 b0 = new_bytes[0]
22 pointer = pointer + 1
23 if b0 == 0:
24 break
25 count = count + 1
26 if count > max_len:
27 if when_over_return_none:
28 return None
29 else:
30 return pystr
31 pystr = pystr + chr(b0)
32 return pystr
33
34 @staticmethod
35 def is_valid_pointer(pointer, pointer_size, allow_tagged):
36 if pointer == None:
37 return False
38 if pointer == 0:
39 return False
40 if allow_tagged:
41 if (pointer % 2) == 1:
42 return True
43 return ((pointer % pointer_size) == 0)
44
45 # Objective-C runtime has a rule that pointers in a class_t will only have bits 0 thru 46 set
46 # so if any pointer has bits 47 thru 63 high we know that this is not a valid isa
47 @staticmethod
48 def is_allowed_pointer(pointer):
49 if pointer == None:
50 return False
51 mask = 0xFFFF800000000000
52 if (pointer & mask) != 0:
53 return False
54 return True
55
56 @staticmethod
57 def read_child_of(valobj,offset,type):
58 child = valobj.CreateChildAtOffset("childUNK",offset,type)
59 if child == None or child.IsValid() == False:
60 return None;
61 return child.GetValueAsUnsigned()
62
63 @staticmethod
64 def is_valid_identifier(name):
65 if name is None:
66 return None
67 if len(name) == 0:
68 return None
69 return True
70 # the rules below make perfect sense for compile-time class names, but the runtime is free
71 # to create classes with arbitrary names to implement functionality (e.g -poseAsClass)
72 # hence, we cannot really outlaw anything appearing in an identifier
73 #ok_first = dict.fromkeys("$ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_")
74 #ok_others = dict.fromkeys("$ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_1234567890")
75 #if name[0] not in ok_first:
76 # return False
77 #return all(c in ok_others for c in name[1:])
78
79class RoT_Data:
80 def __init__(self,rot_pointer,params):
81 if (Utilities.is_valid_pointer(rot_pointer.GetValueAsUnsigned(),params.pointer_size, allow_tagged=False)):
82 self.sys_params = params
83 self.valobj = rot_pointer
84 self.flags = Utilities.read_child_of(self.valobj,0,self.sys_params.uint32_t)
85 self.instanceStart = Utilities.read_child_of(self.valobj,4,self.sys_params.uint32_t)
86 self.instanceSize = Utilities.read_child_of(self.valobj,8,self.sys_params.uint32_t)
87 if self.sys_params.lp64:
88 self.reserved = Utilities.read_child_of(self.valobj,12,self.sys_params.uint32_t)
89 offset = 16
90 else:
91 self.reserved = 0
92 offset = 12
93 self.ivarLayoutPtr = Utilities.read_child_of(self.valobj,offset,self.sys_params.addr_ptr_type)
94 offset = offset + self.sys_params.pointer_size
95 self.namePointer = Utilities.read_child_of(self.valobj,offset,self.sys_params.addr_ptr_type)
96 self.check_valid()
97 else:
98 self.valid = False
99 if self.valid:
100 self.name = Utilities.read_ascii(self.valobj.GetTarget().GetProcess(),self.namePointer)
101 if not(Utilities.is_valid_identifier(self.name)):
102 self.valid = False
103
104 # perform sanity checks on the contents of this class_rw_t
105 def check_valid(self):
106 self.valid = True
107 # misaligned pointers seem to be possible for this field
108 #if not(Utilities.is_valid_pointer(self.namePointer,self.sys_params.pointer_size,allow_tagged=False)):
109 # self.valid = False
110 # pass
111
112 def __str__(self):
113 return 'flags = ' + str(self.flags) + "\n" + \
114 "instanceStart = " + hex(self.instanceStart) + "\n" + \
115 "instanceSize = " + hex(self.instanceSize) + "\n" + \
116 "reserved = " + hex(self.reserved) + "\n" + \
117 "ivarLayoutPtr = " + hex(self.ivarLayoutPtr) + "\n" + \
118 "namePointer = " + hex(self.namePointer) + " --> " + self.name
119
120 def is_valid(self):
121 return self.valid
122
123
124class RwT_Data:
125 def __init__(self,rwt_pointer,params):
126 if (Utilities.is_valid_pointer(rwt_pointer.GetValueAsUnsigned(),params.pointer_size, allow_tagged=False)):
127 self.sys_params = params
128 self.valobj = rwt_pointer
129 self.flags = Utilities.read_child_of(self.valobj,0,self.sys_params.uint32_t)
130 self.version = Utilities.read_child_of(self.valobj,4,self.sys_params.uint32_t)
131 self.roPointer = Utilities.read_child_of(self.valobj,8,self.sys_params.addr_ptr_type)
132 self.check_valid()
133 else:
134 self.valid = False
135 if self.valid:
136 self.rot = self.valobj.CreateValueFromAddress("rot",self.roPointer,self.sys_params.addr_ptr_type).AddressOf()
137 self.data = RoT_Data(self.rot,self.sys_params)
138
139 # perform sanity checks on the contents of this class_rw_t
140 def check_valid(self):
141 self.valid = True
142 if not(Utilities.is_valid_pointer(self.roPointer,self.sys_params.pointer_size,allow_tagged=False)):
143 self.valid = False
144
145 def __str__(self):
146 return 'flags = ' + str(self.flags) + "\n" + \
147 "version = " + hex(self.version) + "\n" + \
148 "roPointer = " + hex(self.roPointer)
149
150 def is_valid(self):
151 if self.valid:
152 return self.data.is_valid()
153 return False
154
155class Class_Data_V2:
156 def __init__(self,isa_pointer,params):
157 if (isa_pointer != None) and (Utilities.is_valid_pointer(isa_pointer.GetValueAsUnsigned(),params.pointer_size, allow_tagged=False)):
158 self.sys_params = params
159 self.valobj = isa_pointer
160 self.isaPointer = Utilities.read_child_of(self.valobj,0,self.sys_params.addr_ptr_type)
161 self.superclassIsaPointer = Utilities.read_child_of(self.valobj,1*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
162 self.cachePointer = Utilities.read_child_of(self.valobj,2*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
163 self.vtablePointer = Utilities.read_child_of(self.valobj,3*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
164 self.dataPointer = Utilities.read_child_of(self.valobj,4*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
165 self.check_valid()
166 else:
167 self.valid = False
168 if self.valid:
169 self.rwt = self.valobj.CreateValueFromAddress("rwt",self.dataPointer,self.sys_params.addr_ptr_type).AddressOf()
170 self.data = RwT_Data(self.rwt,self.sys_params)
171
172 # perform sanity checks on the contents of this class_t
173 def check_valid(self):
174 self.valid = True
175 if not(Utilities.is_valid_pointer(self.isaPointer,self.sys_params.pointer_size,allow_tagged=False)):
176 self.valid = False
177 return
178 if not(Utilities.is_valid_pointer(self.superclassIsaPointer,self.sys_params.pointer_size,allow_tagged=False)):
179 # NULL is a valid value for superclass (it means we have reached NSObject)
180 if self.superclassIsaPointer != 0:
181 self.valid = False
182 return
183 if not(Utilities.is_valid_pointer(self.cachePointer,self.sys_params.pointer_size,allow_tagged=False)):
184 self.valid = False
185 return
186 if not(Utilities.is_valid_pointer(self.vtablePointer,self.sys_params.pointer_size,allow_tagged=False)):
187 self.valid = False
188 return
189 if not(Utilities.is_valid_pointer(self.dataPointer,self.sys_params.pointer_size,allow_tagged=False)):
190 self.valid = False
191 return
192 if not(Utilities.is_allowed_pointer(self.isaPointer)):
193 self.valid = False
194 return
195 if not(Utilities.is_allowed_pointer(self.superclassIsaPointer)):
196 # NULL is a valid value for superclass (it means we have reached NSObject)
197 if self.superclassIsaPointer != 0:
198 self.valid = False
199 return
200 if not(Utilities.is_allowed_pointer(self.cachePointer)):
201 self.valid = False
202 return
203 if not(Utilities.is_allowed_pointer(self.vtablePointer)):
204 self.valid = False
205 return
206 if not(Utilities.is_allowed_pointer(self.dataPointer)):
207 self.valid = False
208 return
209
210 def is_kvo(self):
211 if self.is_valid():
212 if self.data.data.name.startswith("NSKVONotify"):
213 return True
214 else:
215 return None
216
217 def get_superclass(self):
218 if self.is_valid():
219 parent_isa_pointer = self.valobj.CreateChildAtOffset("parent_isa",
220 self.sys_params.pointer_size,
221 self.sys_params.addr_ptr_type)
222 return Class_Data_V2(parent_isa_pointer,self.sys_params)
223 else:
224 return None
225
226 def class_name(self):
227 if self.is_valid():
228 return self.data.data.name
229 else:
230 return None
231
232 def is_valid(self):
233 if self.valid:
234 return self.data.is_valid()
235 return False
236
237 def __str__(self):
238 return 'isaPointer = ' + hex(self.isaPointer) + "\n" + \
239 "superclassIsaPointer = " + hex(self.superclassIsaPointer) + "\n" + \
240 "cachePointer = " + hex(self.cachePointer) + "\n" + \
241 "vtablePointer = " + hex(self.vtablePointer) + "\n" + \
242 "data = " + hex(self.dataPointer)
243
244 def is_tagged(self):
245 return False
246
Enrico Granatab8cbe9c2012-02-23 23:26:48 +0000247 def instance_size(self,align=False):
248 if self.is_valid() == False:
249 return None
250 if align:
251 unalign = self.instance_size(False)
252 if self.sys_params.lp64:
253 return ((unalign + 7) & ~7) % 0x100000000
254 else:
255 return ((unalign + 3) & ~3) % 0x100000000
256 else:
257 return self.rwt.rot.instanceSize
258
Enrico Granataeb4a4792012-02-23 23:10:27 +0000259# runtime v1 is much less intricate than v2 and stores relevant information directly in the class_t object
Enrico Granataeb4a4792012-02-23 23:10:27 +0000260class Class_Data_V1:
261 def __init__(self,isa_pointer,params):
262 if (isa_pointer != None) and (Utilities.is_valid_pointer(isa_pointer.GetValueAsUnsigned(),params.pointer_size, allow_tagged=False)):
263 self.sys_params = params
264 self.valobj = isa_pointer
265 self.isaPointer = Utilities.read_child_of(self.valobj,0,self.sys_params.addr_ptr_type)
266 self.superclassIsaPointer = Utilities.read_child_of(self.valobj,1*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
267 self.namePointer = Utilities.read_child_of(self.valobj,2*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
Enrico Granatab8cbe9c2012-02-23 23:26:48 +0000268 self.version = Utilities.read_child_of(self.valobj,3*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
269 self.info = Utilities.read_child_of(self.valobj,4*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
270 self.instanceSize = Utilities.read_child_of(self.valobj,5*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
271 # since we do not introspect ivars, methods, ... these four pointers need not be named in a meaningful way
272 # moreover, we do not care about their values, just that they are correctly aligned
273 self.ptr1 = Utilities.read_child_of(self.valobj,6*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
274 self.ptr2 = Utilities.read_child_of(self.valobj,7*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
275 self.ptr3 = Utilities.read_child_of(self.valobj,8*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
276 self.ptr4 = Utilities.read_child_of(self.valobj,9*self.sys_params.pointer_size,self.sys_params.addr_ptr_type)
Enrico Granataeb4a4792012-02-23 23:10:27 +0000277 self.check_valid()
278 else:
279 self.valid = False
280 if self.valid:
281 self.name = Utilities.read_ascii(self.valobj.GetTarget().GetProcess(),self.namePointer)
282 if not(Utilities.is_valid_identifier(self.name)):
283 self.valid = False
284
285 # perform sanity checks on the contents of this class_t
286 def check_valid(self):
287 self.valid = True
288 if not(Utilities.is_valid_pointer(self.isaPointer,self.sys_params.pointer_size,allow_tagged=False)):
289 self.valid = False
290 return
291 if not(Utilities.is_valid_pointer(self.superclassIsaPointer,self.sys_params.pointer_size,allow_tagged=False)):
292 # NULL is a valid value for superclass (it means we have reached NSObject)
293 if self.superclassIsaPointer != 0:
294 self.valid = False
295 return
296 #if not(Utilities.is_valid_pointer(self.namePointer,self.sys_params.pointer_size,allow_tagged=False)):
297 # self.valid = False
298 # return
299
300 def is_kvo(self):
301 if self.is_valid():
302 if self.name.startswith("NSKVONotify"):
303 return True
304 else:
305 return None
306
307 def get_superclass(self):
308 if self.is_valid():
309 parent_isa_pointer = self.valobj.CreateChildAtOffset("parent_isa",
310 self.sys_params.pointer_size,
311 self.sys_params.addr_ptr_type)
312 return Class_Data_V1(parent_isa_pointer,self.sys_params)
313 else:
314 return None
315
316 def class_name(self):
317 if self.is_valid():
318 return self.name
319 else:
320 return None
321
322 def is_valid(self):
323 return self.valid
324
325 def __str__(self):
326 return 'isaPointer = ' + hex(self.isaPointer) + "\n" + \
327 "superclassIsaPointer = " + hex(self.superclassIsaPointer) + "\n" + \
Enrico Granatab8cbe9c2012-02-23 23:26:48 +0000328 "namePointer = " + hex(self.namePointer) + " --> " + self.name + \
329 "version = " + hex(self.version) + "\n" + \
330 "info = " + hex(self.info) + "\n" + \
331 "instanceSize = " + hex(self.instanceSize) + "\n"
Enrico Granataeb4a4792012-02-23 23:10:27 +0000332
333 def is_tagged(self):
334 return False
335
Enrico Granatab8cbe9c2012-02-23 23:26:48 +0000336 def instance_size(self,align=False):
337 if self.is_valid() == False:
338 return None
339 if align:
340 unalign = self.instance_size(False)
341 if self.sys_params.lp64:
342 return ((unalign + 7) & ~7) % 0x100000000
343 else:
344 return ((unalign + 3) & ~3) % 0x100000000
345 else:
346 return self.instanceSize
347
Enrico Granataeb4a4792012-02-23 23:10:27 +0000348class TaggedClass_Data:
349 def __init__(self,pointer,params):
350 self.valid = True
351 self.name = None
352 self.sys_params = params
353 self.valobj = pointer
354 self.val = (pointer & ~0x0000000000000000FF) >> 8
355 self.class_bits = (pointer & 0xE) >> 1
356 self.i_bits = (pointer & 0xF0) >> 4
357 # ignoring the LSB, NSNumber gets marked
358 # as 3 on Zin and as 1 on Lion - I can either make
359 # a difference or accept false positives
360 # ToT LLDB + some knowledge of framework versioning
361 # would let me do the right thing - for now I just
362 # act dumb and accept false positives
363 if self.class_bits == 0 or \
364 self.class_bits == 5 or \
365 self.class_bits == 7 or \
366 self.class_bits == 9:
367 self.valid = False
368 return
369 elif self.class_bits == 3 or self.class_bits == 1:
370 self.name = 'NSNumber'
371 elif self.class_bits == 11:
372 self.name = 'NSManagedObject'
373 elif self.class_bits == 13:
374 self.name = 'NSDate'
375 elif self.class_bits == 15:
376 self.name = 'NSDateTS' # not sure what this is
377 else:
378 self.valid = False
379
380 def is_valid(self):
381 return self.valid
382
383 def class_name(self):
384 if self.is_valid():
385 return self.name
386 else:
387 return None
388
389 def value(self):
390 return self.val if self.is_valid() else None
391
392 def info_bits(self):
393 return self.i_bits if self.is_valid() else None
394
395 def is_kvo(self):
396 return False
397
398 # we would need to go around looking for the superclass or ask the runtime
399 # for now, we seem not to require support for this operation so we will merrily
400 # pretend to be at a root point in the hierarchy
401 def get_superclass(self):
402 return None
403
404 # anything that is handled here is tagged
405 def is_tagged(self):
406 return True
407
Enrico Granatab8cbe9c2012-02-23 23:26:48 +0000408 # it seems reasonable to say that a tagged pointer is the size of a pointer
409 def instance_size(self,align=False):
410 if self.is_valid() == False:
411 return None
412 return 8 if self.sys_params.lp64 else 4
413
414
Enrico Granataeb4a4792012-02-23 23:10:27 +0000415class InvalidClass_Data:
416 def __init__(self):
417 pass
418 def is_valid(self):
419 return False
420
421runtime_version = cache.Cache()
422
423class SystemParameters:
424 def __init__(self,valobj):
425 self.adjust_for_architecture(valobj)
426
427 def adjust_for_architecture(self,valobj):
428 self.process = valobj.GetTarget().GetProcess()
429 self.lp64 = (self.process.GetAddressByteSize() == 8)
430 self.is_little = (self.process.GetByteOrder() == lldb.eByteOrderLittle)
431 self.pointer_size = self.process.GetAddressByteSize()
432 self.addr_type = valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)
433 self.addr_ptr_type = self.addr_type.GetPointerType()
434 self.uint32_t = valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)
435 global runtime_version
436 pid = self.process.GetProcessID()
437 if runtime_version.look_for_key(pid) == None:
438 self.runtime_version = runtime_version.get_value(pid)
439 else:
440 self.runtime_version = ObjCRuntime.runtime_version(self.process)
441 runtime_version.add_item(pid,self.runtime_version)
442
443isa_cache = cache.Cache()
444
445class ObjCRuntime:
446
447 # the ObjC runtime has no explicit "version" field that we can use
448 # instead, we discriminate v1 from v2 by looking for the presence
449 # of a well-known section only present in v1
450 @staticmethod
451 def runtime_version(process):
452 if process.IsValid() == False:
453 return None
454 target = process.GetTarget()
455 num_modules = target.GetNumModules()
456 module_objc = None
457 for idx in range(num_modules):
458 module = target.GetModuleAtIndex(idx)
459 if module.GetFileSpec().GetFilename() == 'libobjc.A.dylib':
460 module_objc = module
461 break
462 if module_objc == None or module_objc.IsValid() == False:
463 return None
464 num_sections = module.GetNumSections()
465 section_objc = None
466 for idx in range(num_sections):
467 section = module.GetSectionAtIndex(idx)
468 if section.GetName() == '__OBJC':
469 section_objc = section
470 break
471 if section_objc != None and section_objc.IsValid():
472 return 1
473 return 2
474
475 def __init__(self,valobj):
476 self.valobj = valobj
477 self.adjust_for_architecture()
478 self.sys_params = SystemParameters(self.valobj)
479
480 def adjust_for_architecture(self):
481 self.lp64 = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8)
482 self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle)
483 self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
484 self.addr_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)
485 self.addr_ptr_type = self.addr_type.GetPointerType()
486
487# an ObjC pointer can either be tagged or must be aligned
488 def is_tagged(self):
489 if self.valobj is None:
490 return False
491 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)))
492
493 def is_valid(self):
494 if self.valobj is None:
495 return False
496 if self.valobj.IsInScope() == False:
497 return False
498 return Utilities.is_valid_pointer(self.valobj.GetValueAsUnsigned(),self.pointer_size, allow_tagged=True)
499
500 def read_isa(self):
501 isa_pointer = self.valobj.CreateChildAtOffset("cfisa",
502 0,
503 self.addr_ptr_type)
504 if isa_pointer == None or isa_pointer.IsValid() == False:
505 return None;
506 if isa_pointer.GetValueAsUnsigned(1) == 1:
507 return None;
508 return isa_pointer
509
510 def read_class_data(self):
511 global isa_cache
512 if self.is_tagged():
513 # tagged pointers only exist in ObjC v2
514 if self.sys_params.runtime_version == 2:
515 # not every odd-valued pointer is actually tagged. most are just plain wrong
516 # we could try and predetect this before even creating a TaggedClass_Data object
517 # but unless performance requires it, this seems a cleaner way to tackle the task
518 tentative_tagged = TaggedClass_Data(self.valobj.GetValueAsUnsigned(0),self.sys_params)
519 return tentative_tagged if tentative_tagged.is_valid() else InvalidClass_Data()
520 else:
521 return InvalidClass_Data()
522 if self.is_valid() == False:
523 return InvalidClass_Data()
524 isa = self.read_isa()
525 if isa == None:
526 return InvalidClass_Data()
527 isa_value = isa.GetValueAsUnsigned(1)
528 if isa_value == 1:
529 return InvalidClass_Data()
530 data = isa_cache.get_value(isa_value,default=None)
531 if data != None:
532 return data
533 if self.sys_params.runtime_version == 2:
534 data = Class_Data_V2(isa,self.sys_params)
535 else:
536 data = Class_Data_V1(isa,self.sys_params)
537 if data == None:
538 return InvalidClass_Data()
539 if data.is_valid():
540 isa_cache.add_item(isa_value,data,ok_to_replace=True)
541 return data
Enrico Granatab8cbe9c2012-02-23 23:26:48 +0000542