Run DCE after a LoopFlatten test to reduce spurious output [nfc]
[llvm-project.git] / lldb / examples / summaries / cocoa / objc_runtime.py
blob4299e57b81f6c93f6d4e145579dfa489e90f4f2b
1 """
2 Objective-C runtime wrapper for use by LLDB Python formatters
4 Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 See https://llvm.org/LICENSE.txt for license information.
6 SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 """
8 import lldb
9 import lldb.formatters.cache
10 import lldb.formatters.attrib_fromdict
11 import functools
12 import lldb.formatters.Logger
15 class Utilities:
16 @staticmethod
17 def read_ascii(process, pointer, max_len=128):
18 logger = lldb.formatters.Logger.Logger()
19 error = lldb.SBError()
20 content = None
21 try:
22 content = process.ReadCStringFromMemory(pointer, max_len, error)
23 except:
24 pass
25 if content is None or len(content) == 0 or error.fail:
26 return None
27 return content
29 @staticmethod
30 def is_valid_pointer(pointer, pointer_size, allow_tagged=0, allow_NULL=0):
31 logger = lldb.formatters.Logger.Logger()
32 if pointer is None:
33 return 0
34 if pointer == 0:
35 return allow_NULL
36 if allow_tagged and (pointer % 2) == 1:
37 return 1
38 return (pointer % pointer_size) == 0
40 # Objective-C runtime has a rule that pointers in a class_t will only have bits 0 thru 46 set
41 # so if any pointer has bits 47 thru 63 high we know that this is not a
42 # valid isa
43 @staticmethod
44 def is_allowed_pointer(pointer):
45 logger = lldb.formatters.Logger.Logger()
46 if pointer is None:
47 return 0
48 return (pointer & 0xFFFF800000000000) == 0
50 @staticmethod
51 def read_child_of(valobj, offset, type):
52 logger = lldb.formatters.Logger.Logger()
53 if offset == 0 and type.GetByteSize() == valobj.GetByteSize():
54 return valobj.GetValueAsUnsigned()
55 child = valobj.CreateChildAtOffset("childUNK", offset, type)
56 if child is None or child.IsValid() == 0:
57 return None
58 return child.GetValueAsUnsigned()
60 @staticmethod
61 def is_valid_identifier(name):
62 logger = lldb.formatters.Logger.Logger()
63 if name is None:
64 return None
65 if len(name) == 0:
66 return None
67 # technically, the ObjC runtime does not enforce any rules about what name a class can have
68 # in practice, the commonly used byte values for a class name are the letters, digits and some
69 # symbols: $, %, -, _, .
70 # WARNING: this means that you cannot use this runtime implementation if you need to deal
71 # with class names that use anything but what is allowed here
72 ok_values = dict.fromkeys(
73 "$%_.-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
75 return all(c in ok_values for c in name)
77 @staticmethod
78 def check_is_osx_lion(target):
79 logger = lldb.formatters.Logger.Logger()
80 # assume the only thing that has a Foundation.framework is a Mac
81 # assume anything < Lion does not even exist
82 try:
83 mod = target.module["Foundation"]
84 except:
85 mod = None
86 if mod is None or mod.IsValid() == 0:
87 return None
88 ver = mod.GetVersion()
89 if ver is None or ver == []:
90 return None
91 return ver[0] < 900
93 # a utility method that factors out code common to almost all the formatters
94 # takes in an SBValue and a metrics object
95 # returns a class_data and a wrapper (or None, if the runtime alone can't
96 # decide on a wrapper)
97 @staticmethod
98 def prepare_class_detection(valobj, statistics):
99 logger = lldb.formatters.Logger.Logger()
100 class_data = ObjCRuntime(valobj)
101 if class_data.is_valid() == 0:
102 statistics.metric_hit("invalid_pointer", valobj)
103 wrapper = InvalidPointer_Description(valobj.GetValueAsUnsigned(0) == 0)
104 return class_data, wrapper
105 class_data = class_data.read_class_data()
106 if class_data.is_valid() == 0:
107 statistics.metric_hit("invalid_isa", valobj)
108 wrapper = InvalidISA_Description()
109 return class_data, wrapper
110 if class_data.is_kvo():
111 class_data = class_data.get_superclass()
112 if class_data.class_name() == "_NSZombie_OriginalClass":
113 wrapper = ThisIsZombie_Description()
114 return class_data, wrapper
115 return class_data, None
118 class RoT_Data:
119 def __init__(self, rot_pointer, params):
120 logger = lldb.formatters.Logger.Logger()
121 if Utilities.is_valid_pointer(
122 rot_pointer.GetValueAsUnsigned(), params.pointer_size, allow_tagged=0
124 self.sys_params = params
125 self.valobj = rot_pointer
126 # self.flags = Utilities.read_child_of(self.valobj,0,self.sys_params.uint32_t)
127 # self.instanceStart = Utilities.read_child_of(self.valobj,4,self.sys_params.uint32_t)
128 self.instanceSize = None # lazy fetching
129 offset = 24 if self.sys_params.is_64_bit else 16
130 # self.ivarLayoutPtr = Utilities.read_child_of(self.valobj,offset,self.sys_params.addr_ptr_type)
131 self.namePointer = Utilities.read_child_of(
132 self.valobj, offset, self.sys_params.types_cache.addr_ptr_type
134 self.valid = 1 # self.check_valid()
135 else:
136 logger >> "Marking as invalid - rot is invalid"
137 self.valid = 0
138 if self.valid:
139 self.name = Utilities.read_ascii(
140 self.valobj.GetTarget().GetProcess(), self.namePointer
142 if not (Utilities.is_valid_identifier(self.name)):
143 logger >> "Marking as invalid - name is invalid"
144 self.valid = 0
146 # perform sanity checks on the contents of this class_ro_t
147 def check_valid(self):
148 self.valid = 1
149 # misaligned pointers seem to be possible for this field
150 # if not(Utilities.is_valid_pointer(self.namePointer,self.sys_params.pointer_size,allow_tagged=0)):
151 # self.valid = 0
152 # pass
154 def __str__(self):
155 logger = lldb.formatters.Logger.Logger()
156 return (
157 "instanceSize = "
158 + hex(self.instance_size())
159 + "\n"
160 + "namePointer = "
161 + hex(self.namePointer)
162 + " --> "
163 + self.name
166 def is_valid(self):
167 return self.valid
169 def instance_size(self, align=0):
170 logger = lldb.formatters.Logger.Logger()
171 if self.is_valid() == 0:
172 return None
173 if self.instanceSize is None:
174 self.instanceSize = Utilities.read_child_of(
175 self.valobj, 8, self.sys_params.types_cache.uint32_t
177 if align:
178 unalign = self.instance_size(0)
179 if self.sys_params.is_64_bit:
180 return ((unalign + 7) & ~7) % 0x100000000
181 else:
182 return ((unalign + 3) & ~3) % 0x100000000
183 else:
184 return self.instanceSize
187 class RwT_Data:
188 def __init__(self, rwt_pointer, params):
189 logger = lldb.formatters.Logger.Logger()
190 if Utilities.is_valid_pointer(
191 rwt_pointer.GetValueAsUnsigned(), params.pointer_size, allow_tagged=0
193 self.sys_params = params
194 self.valobj = rwt_pointer
195 # self.flags = Utilities.read_child_of(self.valobj,0,self.sys_params.uint32_t)
196 # self.version = Utilities.read_child_of(self.valobj,4,self.sys_params.uint32_t)
197 self.roPointer = Utilities.read_child_of(
198 self.valobj, 8, self.sys_params.types_cache.addr_ptr_type
200 self.check_valid()
201 else:
202 logger >> "Marking as invalid - rwt is invald"
203 self.valid = 0
204 if self.valid:
205 self.rot = self.valobj.CreateValueFromData(
206 "rot",
207 lldb.SBData.CreateDataFromUInt64Array(
208 self.sys_params.endianness,
209 self.sys_params.pointer_size,
210 [self.roPointer],
212 self.sys_params.types_cache.addr_ptr_type,
214 # self.rot = self.valobj.CreateValueFromAddress("rot",self.roPointer,self.sys_params.types_cache.addr_ptr_type).AddressOf()
215 self.data = RoT_Data(self.rot, self.sys_params)
217 # perform sanity checks on the contents of this class_rw_t
218 def check_valid(self):
219 logger = lldb.formatters.Logger.Logger()
220 self.valid = 1
221 if not (
222 Utilities.is_valid_pointer(
223 self.roPointer, self.sys_params.pointer_size, allow_tagged=0
226 logger >> "Marking as invalid - ropointer is invalid"
227 self.valid = 0
229 def __str__(self):
230 logger = lldb.formatters.Logger.Logger()
231 return "roPointer = " + hex(self.roPointer)
233 def is_valid(self):
234 logger = lldb.formatters.Logger.Logger()
235 if self.valid:
236 return self.data.is_valid()
237 return 0
240 class Class_Data_V2:
241 def __init__(self, isa_pointer, params):
242 logger = lldb.formatters.Logger.Logger()
243 if (isa_pointer is not None) and (
244 Utilities.is_valid_pointer(
245 isa_pointer.GetValueAsUnsigned(), params.pointer_size, allow_tagged=0
248 self.sys_params = params
249 self.valobj = isa_pointer
250 self.check_valid()
251 else:
252 logger >> "Marking as invalid - isa is invalid or None"
253 self.valid = 0
254 if self.valid:
255 self.rwt = self.valobj.CreateValueFromData(
256 "rwt",
257 lldb.SBData.CreateDataFromUInt64Array(
258 self.sys_params.endianness,
259 self.sys_params.pointer_size,
260 [self.dataPointer],
262 self.sys_params.types_cache.addr_ptr_type,
264 # self.rwt = self.valobj.CreateValueFromAddress("rwt",self.dataPointer,self.sys_params.types_cache.addr_ptr_type).AddressOf()
265 self.data = RwT_Data(self.rwt, self.sys_params)
267 # perform sanity checks on the contents of this class_t
268 # this call tries to minimize the amount of data fetched- as soon as we have "proven"
269 # that we have an invalid object, we stop reading
270 def check_valid(self):
271 logger = lldb.formatters.Logger.Logger()
272 self.valid = 1
274 self.isaPointer = Utilities.read_child_of(
275 self.valobj, 0, self.sys_params.types_cache.addr_ptr_type
277 if not (
278 Utilities.is_valid_pointer(
279 self.isaPointer, self.sys_params.pointer_size, allow_tagged=0
282 logger >> "Marking as invalid - isaPointer is invalid"
283 self.valid = 0
284 return
285 if not (Utilities.is_allowed_pointer(self.isaPointer)):
286 logger >> "Marking as invalid - isaPointer is not allowed"
287 self.valid = 0
288 return
290 self.cachePointer = Utilities.read_child_of(
291 self.valobj,
292 2 * self.sys_params.pointer_size,
293 self.sys_params.types_cache.addr_ptr_type,
295 if not (
296 Utilities.is_valid_pointer(
297 self.cachePointer, self.sys_params.pointer_size, allow_tagged=0
300 logger >> "Marking as invalid - cachePointer is invalid"
301 self.valid = 0
302 return
303 if not (Utilities.is_allowed_pointer(self.cachePointer)):
304 logger >> "Marking as invalid - cachePointer is not allowed"
305 self.valid = 0
306 return
307 self.dataPointer = Utilities.read_child_of(
308 self.valobj,
309 4 * self.sys_params.pointer_size,
310 self.sys_params.types_cache.addr_ptr_type,
312 if not (
313 Utilities.is_valid_pointer(
314 self.dataPointer, self.sys_params.pointer_size, allow_tagged=0
317 logger >> "Marking as invalid - dataPointer is invalid"
318 self.valid = 0
319 return
320 if not (Utilities.is_allowed_pointer(self.dataPointer)):
321 logger >> "Marking as invalid - dataPointer is not allowed"
322 self.valid = 0
323 return
325 self.superclassIsaPointer = Utilities.read_child_of(
326 self.valobj,
327 1 * self.sys_params.pointer_size,
328 self.sys_params.types_cache.addr_ptr_type,
330 if not (
331 Utilities.is_valid_pointer(
332 self.superclassIsaPointer,
333 self.sys_params.pointer_size,
334 allow_tagged=0,
335 allow_NULL=1,
338 logger >> "Marking as invalid - superclassIsa is invalid"
339 self.valid = 0
340 return
341 if not (Utilities.is_allowed_pointer(self.superclassIsaPointer)):
342 logger >> "Marking as invalid - superclassIsa is not allowed"
343 self.valid = 0
344 return
346 # in general, KVO is implemented by transparently subclassing
347 # however, there could be exceptions where a class does something else
348 # internally to implement the feature - this method will have no clue that a class
349 # has been KVO'ed unless the standard implementation technique is used
350 def is_kvo(self):
351 logger = lldb.formatters.Logger.Logger()
352 if self.is_valid():
353 if self.class_name().startswith("NSKVONotifying_"):
354 return 1
355 return 0
357 # some CF classes have a valid ObjC isa in their CFRuntimeBase
358 # but instead of being class-specific this isa points to a match-'em-all class
359 # which is __NSCFType (the versions without __ also exists and we are matching to it
360 # just to be on the safe side)
361 def is_cftype(self):
362 logger = lldb.formatters.Logger.Logger()
363 if self.is_valid():
364 return self.class_name() == "__NSCFType" or self.class_name() == "NSCFType"
366 def get_superclass(self):
367 logger = lldb.formatters.Logger.Logger()
368 if self.is_valid():
369 parent_isa_pointer = self.valobj.CreateChildAtOffset(
370 "parent_isa",
371 self.sys_params.pointer_size,
372 self.sys_params.addr_ptr_type,
374 return Class_Data_V2(parent_isa_pointer, self.sys_params)
375 else:
376 return None
378 def class_name(self):
379 logger = lldb.formatters.Logger.Logger()
380 if self.is_valid():
381 return self.data.data.name
382 else:
383 return None
385 def is_valid(self):
386 logger = lldb.formatters.Logger.Logger()
387 if self.valid:
388 return self.data.is_valid()
389 return 0
391 def __str__(self):
392 logger = lldb.formatters.Logger.Logger()
393 return (
394 "isaPointer = "
395 + hex(self.isaPointer)
396 + "\n"
397 + "superclassIsaPointer = "
398 + hex(self.superclassIsaPointer)
399 + "\n"
400 + "cachePointer = "
401 + hex(self.cachePointer)
402 + "\n"
403 + "data = "
404 + hex(self.dataPointer)
407 def is_tagged(self):
408 return 0
410 def instance_size(self, align=0):
411 logger = lldb.formatters.Logger.Logger()
412 if self.is_valid() == 0:
413 return None
414 return self.rwt.rot.instance_size(align)
417 # runtime v1 is much less intricate than v2 and stores relevant
418 # information directly in the class_t object
421 class Class_Data_V1:
422 def __init__(self, isa_pointer, params):
423 logger = lldb.formatters.Logger.Logger()
424 if (isa_pointer is not None) and (
425 Utilities.is_valid_pointer(
426 isa_pointer.GetValueAsUnsigned(), params.pointer_size, allow_tagged=0
429 self.valid = 1
430 self.sys_params = params
431 self.valobj = isa_pointer
432 self.check_valid()
433 else:
434 logger >> "Marking as invalid - isaPointer is invalid or None"
435 self.valid = 0
436 if self.valid:
437 self.name = Utilities.read_ascii(
438 self.valobj.GetTarget().GetProcess(), self.namePointer
440 if not (Utilities.is_valid_identifier(self.name)):
441 logger >> "Marking as invalid - name is not valid"
442 self.valid = 0
444 # perform sanity checks on the contents of this class_t
445 def check_valid(self):
446 logger = lldb.formatters.Logger.Logger()
447 self.valid = 1
449 self.isaPointer = Utilities.read_child_of(
450 self.valobj, 0, self.sys_params.types_cache.addr_ptr_type
452 if not (
453 Utilities.is_valid_pointer(
454 self.isaPointer, self.sys_params.pointer_size, allow_tagged=0
457 logger >> "Marking as invalid - isaPointer is invalid"
458 self.valid = 0
459 return
461 self.superclassIsaPointer = Utilities.read_child_of(
462 self.valobj,
463 1 * self.sys_params.pointer_size,
464 self.sys_params.types_cache.addr_ptr_type,
466 if not (
467 Utilities.is_valid_pointer(
468 self.superclassIsaPointer,
469 self.sys_params.pointer_size,
470 allow_tagged=0,
471 allow_NULL=1,
474 logger >> "Marking as invalid - superclassIsa is invalid"
475 self.valid = 0
476 return
478 self.namePointer = Utilities.read_child_of(
479 self.valobj,
480 2 * self.sys_params.pointer_size,
481 self.sys_params.types_cache.addr_ptr_type,
483 # if not(Utilities.is_valid_pointer(self.namePointer,self.sys_params.pointer_size,allow_tagged=0,allow_NULL=0)):
484 # self.valid = 0
485 # return
487 # in general, KVO is implemented by transparently subclassing
488 # however, there could be exceptions where a class does something else
489 # internally to implement the feature - this method will have no clue that a class
490 # has been KVO'ed unless the standard implementation technique is used
491 def is_kvo(self):
492 logger = lldb.formatters.Logger.Logger()
493 if self.is_valid():
494 if self.class_name().startswith("NSKVONotifying_"):
495 return 1
496 return 0
498 # some CF classes have a valid ObjC isa in their CFRuntimeBase
499 # but instead of being class-specific this isa points to a match-'em-all class
500 # which is __NSCFType (the versions without __ also exists and we are matching to it
501 # just to be on the safe side)
502 def is_cftype(self):
503 logger = lldb.formatters.Logger.Logger()
504 if self.is_valid():
505 return self.class_name() == "__NSCFType" or self.class_name() == "NSCFType"
507 def get_superclass(self):
508 logger = lldb.formatters.Logger.Logger()
509 if self.is_valid():
510 parent_isa_pointer = self.valobj.CreateChildAtOffset(
511 "parent_isa",
512 self.sys_params.pointer_size,
513 self.sys_params.addr_ptr_type,
515 return Class_Data_V1(parent_isa_pointer, self.sys_params)
516 else:
517 return None
519 def class_name(self):
520 logger = lldb.formatters.Logger.Logger()
521 if self.is_valid():
522 return self.name
523 else:
524 return None
526 def is_valid(self):
527 return self.valid
529 def __str__(self):
530 logger = lldb.formatters.Logger.Logger()
531 return (
532 "isaPointer = "
533 + hex(self.isaPointer)
534 + "\n"
535 + "superclassIsaPointer = "
536 + hex(self.superclassIsaPointer)
537 + "\n"
538 + "namePointer = "
539 + hex(self.namePointer)
540 + " --> "
541 + self.name
542 + "instanceSize = "
543 + hex(self.instanceSize())
544 + "\n"
547 def is_tagged(self):
548 return 0
550 def instance_size(self, align=0):
551 logger = lldb.formatters.Logger.Logger()
552 if self.is_valid() == 0:
553 return None
554 if self.instanceSize is None:
555 self.instanceSize = Utilities.read_child_of(
556 self.valobj,
557 5 * self.sys_params.pointer_size,
558 self.sys_params.types_cache.addr_ptr_type,
560 if align:
561 unalign = self.instance_size(0)
562 if self.sys_params.is_64_bit:
563 return ((unalign + 7) & ~7) % 0x100000000
564 else:
565 return ((unalign + 3) & ~3) % 0x100000000
566 else:
567 return self.instanceSize
570 # these are the only tagged pointers values for current versions
571 # of OSX - they might change in future OS releases, and no-one is
572 # advised to rely on these values, or any of the bitmasking formulas
573 # in TaggedClass_Data. doing otherwise is at your own risk
574 TaggedClass_Values_Lion = {
575 1: "NSNumber",
576 5: "NSManagedObject",
577 6: "NSDate",
578 7: "NSDateTS",
580 TaggedClass_Values_NMOS = {
581 0: "NSAtom",
582 3: "NSNumber",
583 4: "NSDateTS",
584 5: "NSManagedObject",
585 6: "NSDate",
589 class TaggedClass_Data:
590 def __init__(self, pointer, params):
591 logger = lldb.formatters.Logger.Logger()
592 global TaggedClass_Values_Lion, TaggedClass_Values_NMOS
593 self.valid = 1
594 self.name = None
595 self.sys_params = params
596 self.valobj = pointer
597 self.val = (pointer & ~0x0000000000000000FF) >> 8
598 self.class_bits = (pointer & 0xE) >> 1
599 self.i_bits = (pointer & 0xF0) >> 4
601 if self.sys_params.is_lion:
602 if self.class_bits in TaggedClass_Values_Lion:
603 self.name = TaggedClass_Values_Lion[self.class_bits]
604 else:
605 logger >> "Marking as invalid - not a good tagged pointer for Lion"
606 self.valid = 0
607 else:
608 if self.class_bits in TaggedClass_Values_NMOS:
609 self.name = TaggedClass_Values_NMOS[self.class_bits]
610 else:
611 logger >> "Marking as invalid - not a good tagged pointer for NMOS"
612 self.valid = 0
614 def is_valid(self):
615 return self.valid
617 def class_name(self):
618 logger = lldb.formatters.Logger.Logger()
619 if self.is_valid():
620 return self.name
621 else:
622 return 0
624 def value(self):
625 return self.val if self.is_valid() else None
627 def info_bits(self):
628 return self.i_bits if self.is_valid() else None
630 def is_kvo(self):
631 return 0
633 def is_cftype(self):
634 return 0
636 # we would need to go around looking for the superclass or ask the runtime
637 # for now, we seem not to require support for this operation so we will merrily
638 # pretend to be at a root point in the hierarchy
639 def get_superclass(self):
640 return None
642 # anything that is handled here is tagged
643 def is_tagged(self):
644 return 1
646 # it seems reasonable to say that a tagged pointer is the size of a pointer
647 def instance_size(self, align=0):
648 logger = lldb.formatters.Logger.Logger()
649 if self.is_valid() == 0:
650 return None
651 return self.sys_params.pointer_size
654 class InvalidClass_Data:
655 def __init__(self):
656 pass
658 def is_valid(self):
659 return 0
662 class Version:
663 def __init__(self, major, minor, release, build_string):
664 self._major = major
665 self._minor = minor
666 self._release = release
667 self._build_string = build_string
669 def get_major(self):
670 return self._major
672 def get_minor(self):
673 return self._minor
675 def get_release(self):
676 return self._release
678 def get_build_string(self):
679 return self._build_string
681 major = property(get_major, None)
682 minor = property(get_minor, None)
683 release = property(get_release, None)
684 build_string = property(get_build_string, None)
686 def __lt__(self, other):
687 if self.major < other.major:
688 return 1
689 if self.minor < other.minor:
690 return 1
691 if self.release < other.release:
692 return 1
693 # build strings are not compared since they are heavily platform-dependent and might not always
694 # be available
695 return 0
697 def __eq__(self, other):
698 return (
699 (self.major == other.major)
700 and (self.minor == other.minor)
701 and (self.release == other.release)
702 and (self.build_string == other.build_string)
705 # Python 2.6 doesn't have functools.total_ordering, so we have to implement
706 # other comparators
707 def __gt__(self, other):
708 return other < self
710 def __le__(self, other):
711 return not other < self
713 def __ge__(self, other):
714 return not self < other
717 runtime_version = lldb.formatters.cache.Cache()
718 os_version = lldb.formatters.cache.Cache()
719 types_caches = lldb.formatters.cache.Cache()
720 isa_caches = lldb.formatters.cache.Cache()
723 class SystemParameters:
724 def __init__(self, valobj):
725 logger = lldb.formatters.Logger.Logger()
726 self.adjust_for_architecture(valobj)
727 self.adjust_for_process(valobj)
729 def adjust_for_process(self, valobj):
730 logger = lldb.formatters.Logger.Logger()
731 global runtime_version
732 global os_version
733 global types_caches
734 global isa_caches
736 process = valobj.GetTarget().GetProcess()
737 # using the unique ID for added guarantees (see svn revision 172628 for
738 # further details)
739 self.pid = process.GetUniqueID()
741 if runtime_version.look_for_key(self.pid):
742 self.runtime_version = runtime_version.get_value(self.pid)
743 else:
744 self.runtime_version = ObjCRuntime.runtime_version(process)
745 runtime_version.add_item(self.pid, self.runtime_version)
747 if os_version.look_for_key(self.pid):
748 self.is_lion = os_version.get_value(self.pid)
749 else:
750 self.is_lion = Utilities.check_is_osx_lion(valobj.GetTarget())
751 os_version.add_item(self.pid, self.is_lion)
753 if types_caches.look_for_key(self.pid):
754 self.types_cache = types_caches.get_value(self.pid)
755 else:
756 self.types_cache = lldb.formatters.attrib_fromdict.AttributesDictionary(
757 allow_reset=0
759 self.types_cache.addr_type = valobj.GetType().GetBasicType(
760 lldb.eBasicTypeUnsignedLong
762 self.types_cache.addr_ptr_type = self.types_cache.addr_type.GetPointerType()
763 self.types_cache.uint32_t = valobj.GetType().GetBasicType(
764 lldb.eBasicTypeUnsignedInt
766 types_caches.add_item(self.pid, self.types_cache)
768 if isa_caches.look_for_key(self.pid):
769 self.isa_cache = isa_caches.get_value(self.pid)
770 else:
771 self.isa_cache = lldb.formatters.cache.Cache()
772 isa_caches.add_item(self.pid, self.isa_cache)
774 def adjust_for_architecture(self, valobj):
775 process = valobj.GetTarget().GetProcess()
776 self.pointer_size = process.GetAddressByteSize()
777 self.is_64_bit = self.pointer_size == 8
778 self.endianness = process.GetByteOrder()
779 self.is_little = self.endianness == lldb.eByteOrderLittle
780 self.cfruntime_size = 16 if self.is_64_bit else 8
782 # a simple helper function that makes it more explicit that one is calculating
783 # an offset that is made up of X pointers and Y bytes of additional data
784 # taking into account pointer size - if you know there is going to be some padding
785 # you can pass that in and it will be taken into account (since padding may be different between
786 # 32 and 64 bit versions, you can pass padding value for both, the right
787 # one will be used)
788 def calculate_offset(self, num_pointers=0, bytes_count=0, padding32=0, padding64=0):
789 value = bytes_count + num_pointers * self.pointer_size
790 return value + padding64 if self.is_64_bit else value + padding32
793 class ObjCRuntime:
794 # the ObjC runtime has no explicit "version" field that we can use
795 # instead, we discriminate v1 from v2 by looking for the presence
796 # of a well-known section only present in v1
797 @staticmethod
798 def runtime_version(process):
799 logger = lldb.formatters.Logger.Logger()
800 if process.IsValid() == 0:
801 logger >> "No process - bailing out"
802 return None
803 target = process.GetTarget()
804 num_modules = target.GetNumModules()
805 module_objc = None
806 for idx in range(num_modules):
807 module = target.GetModuleAtIndex(idx)
808 if module.GetFileSpec().GetFilename() == "libobjc.A.dylib":
809 module_objc = module
810 break
811 if module_objc is None or module_objc.IsValid() == 0:
812 logger >> "no libobjc - bailing out"
813 return None
814 num_sections = module.GetNumSections()
815 section_objc = None
816 for idx in range(num_sections):
817 section = module.GetSectionAtIndex(idx)
818 if section.GetName() == "__OBJC":
819 section_objc = section
820 break
821 if section_objc is not None and section_objc.IsValid():
822 logger >> "found __OBJC: v1"
823 return 1
824 logger >> "no __OBJC: v2"
825 return 2
827 @staticmethod
828 def runtime_from_isa(isa):
829 logger = lldb.formatters.Logger.Logger()
830 runtime = ObjCRuntime(isa)
831 runtime.isa = isa
832 return runtime
834 def __init__(self, valobj):
835 logger = lldb.formatters.Logger.Logger()
836 self.valobj = valobj
837 self.adjust_for_architecture()
838 self.sys_params = SystemParameters(self.valobj)
839 self.unsigned_value = self.valobj.GetValueAsUnsigned()
840 self.isa_value = None
842 def adjust_for_architecture(self):
843 pass
845 # an ObjC pointer can either be tagged or must be aligned
846 def is_tagged(self):
847 logger = lldb.formatters.Logger.Logger()
848 if self.valobj is None:
849 return 0
850 return Utilities.is_valid_pointer(
851 self.unsigned_value, self.sys_params.pointer_size, allow_tagged=1
852 ) and not (
853 Utilities.is_valid_pointer(
854 self.unsigned_value, self.sys_params.pointer_size, allow_tagged=0
858 def is_valid(self):
859 logger = lldb.formatters.Logger.Logger()
860 if self.valobj is None:
861 return 0
862 if self.valobj.IsInScope() == 0:
863 return 0
864 return Utilities.is_valid_pointer(
865 self.unsigned_value, self.sys_params.pointer_size, allow_tagged=1
868 def is_nil(self):
869 return self.unsigned_value == 0
871 def read_isa(self):
872 logger = lldb.formatters.Logger.Logger()
873 if self.isa_value is not None:
874 logger >> "using cached isa"
875 return self.isa_value
876 self.isa_pointer = self.valobj.CreateChildAtOffset(
877 "cfisa", 0, self.sys_params.types_cache.addr_ptr_type
879 if self.isa_pointer is None or self.isa_pointer.IsValid() == 0:
880 logger >> "invalid isa - bailing out"
881 return None
882 self.isa_value = self.isa_pointer.GetValueAsUnsigned(1)
883 if self.isa_value == 1:
884 logger >> "invalid isa value - bailing out"
885 return None
886 return Ellipsis
888 def read_class_data(self):
889 logger = lldb.formatters.Logger.Logger()
890 global isa_cache
891 if self.is_tagged():
892 # tagged pointers only exist in ObjC v2
893 if self.sys_params.runtime_version == 2:
894 logger >> "on v2 and tagged - maybe"
895 # not every odd-valued pointer is actually tagged. most are just plain wrong
896 # we could try and predetect this before even creating a TaggedClass_Data object
897 # but unless performance requires it, this seems a cleaner way
898 # to tackle the task
899 tentative_tagged = TaggedClass_Data(
900 self.unsigned_value, self.sys_params
902 if tentative_tagged.is_valid():
903 logger >> "truly tagged"
904 return tentative_tagged
905 else:
906 logger >> "not tagged - error"
907 return InvalidClass_Data()
908 else:
909 logger >> "on v1 and tagged - error"
910 return InvalidClass_Data()
911 if self.is_valid() == 0 or self.read_isa() is None:
912 return InvalidClass_Data()
913 data = self.sys_params.isa_cache.get_value(self.isa_value, default=None)
914 if data is not None:
915 return data
916 if self.sys_params.runtime_version == 2:
917 data = Class_Data_V2(self.isa_pointer, self.sys_params)
918 else:
919 data = Class_Data_V1(self.isa_pointer, self.sys_params)
920 if data is None:
921 return InvalidClass_Data()
922 if data.is_valid():
923 self.sys_params.isa_cache.add_item(self.isa_value, data, ok_to_replace=1)
924 return data
927 # these classes below can be used by the data formatters to provide a
928 # consistent message that describes a given runtime-generated situation
931 class SpecialSituation_Description:
932 def message(self):
933 return ""
936 class InvalidPointer_Description(SpecialSituation_Description):
937 def __init__(self, nil):
938 self.is_nil = nil
940 def message(self):
941 if self.is_nil:
942 return '@"<nil>"'
943 else:
944 return "<invalid pointer>"
947 class InvalidISA_Description(SpecialSituation_Description):
948 def __init__(self):
949 pass
951 def message(self):
952 return "<not an Objective-C object>"
955 class ThisIsZombie_Description(SpecialSituation_Description):
956 def message(self):
957 return "<freed object>"