brintos

brintos / llvm-project-archived public Read only

0
0
Text · 33.4 KiB · 4299e57 Raw
958 lines · python
1"""2Objective-C runtime wrapper for use by LLDB Python formatters3 4Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.5See https://llvm.org/LICENSE.txt for license information.6SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception7"""8import lldb9import lldb.formatters.cache10import lldb.formatters.attrib_fromdict11import functools12import lldb.formatters.Logger13 14 15class Utilities:16    @staticmethod17    def read_ascii(process, pointer, max_len=128):18        logger = lldb.formatters.Logger.Logger()19        error = lldb.SBError()20        content = None21        try:22            content = process.ReadCStringFromMemory(pointer, max_len, error)23        except:24            pass25        if content is None or len(content) == 0 or error.fail:26            return None27        return content28 29    @staticmethod30    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 034        if pointer == 0:35            return allow_NULL36        if allow_tagged and (pointer % 2) == 1:37            return 138        return (pointer % pointer_size) == 039 40    # Objective-C runtime has a rule that pointers in a class_t will only have bits 0 thru 46 set41    # so if any pointer has bits 47 thru 63 high we know that this is not a42    # valid isa43    @staticmethod44    def is_allowed_pointer(pointer):45        logger = lldb.formatters.Logger.Logger()46        if pointer is None:47            return 048        return (pointer & 0xFFFF800000000000) == 049 50    @staticmethod51    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 None58        return child.GetValueAsUnsigned()59 60    @staticmethod61    def is_valid_identifier(name):62        logger = lldb.formatters.Logger.Logger()63        if name is None:64            return None65        if len(name) == 0:66            return None67        # technically, the ObjC runtime does not enforce any rules about what name a class can have68        # in practice, the commonly used byte values for a class name are the letters, digits and some69        # symbols: $, %, -, _, .70        # WARNING: this means that you cannot use this runtime implementation if you need to deal71        # with class names that use anything but what is allowed here72        ok_values = dict.fromkeys(73            "$%_.-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"74        )75        return all(c in ok_values for c in name)76 77    @staticmethod78    def check_is_osx_lion(target):79        logger = lldb.formatters.Logger.Logger()80        # assume the only thing that has a Foundation.framework is a Mac81        # assume anything < Lion does not even exist82        try:83            mod = target.module["Foundation"]84        except:85            mod = None86        if mod is None or mod.IsValid() == 0:87            return None88        ver = mod.GetVersion()89        if ver is None or ver == []:90            return None91        return ver[0] < 90092 93    # a utility method that factors out code common to almost all the formatters94    # takes in an SBValue and a metrics object95    # returns a class_data and a wrapper (or None, if the runtime alone can't96    # decide on a wrapper)97    @staticmethod98    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, wrapper105        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, wrapper110        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, wrapper115        return class_data, None116 117 118class 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=0123        ):124            self.sys_params = params125            self.valobj = rot_pointer126            # 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 fetching129            offset = 24 if self.sys_params.is_64_bit else 16130            # 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_type133            )134            self.valid = 1  # self.check_valid()135        else:136            logger >> "Marking as invalid - rot is invalid"137            self.valid = 0138        if self.valid:139            self.name = Utilities.read_ascii(140                self.valobj.GetTarget().GetProcess(), self.namePointer141            )142            if not (Utilities.is_valid_identifier(self.name)):143                logger >> "Marking as invalid - name is invalid"144                self.valid = 0145 146    # perform sanity checks on the contents of this class_ro_t147    def check_valid(self):148        self.valid = 1149        # misaligned pointers seem to be possible for this field150        # if not(Utilities.is_valid_pointer(self.namePointer,self.sys_params.pointer_size,allow_tagged=0)):151        # 	self.valid = 0152        # 	pass153 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.name164        )165 166    def is_valid(self):167        return self.valid168 169    def instance_size(self, align=0):170        logger = lldb.formatters.Logger.Logger()171        if self.is_valid() == 0:172            return None173        if self.instanceSize is None:174            self.instanceSize = Utilities.read_child_of(175                self.valobj, 8, self.sys_params.types_cache.uint32_t176            )177        if align:178            unalign = self.instance_size(0)179            if self.sys_params.is_64_bit:180                return ((unalign + 7) & ~7) % 0x100000000181            else:182                return ((unalign + 3) & ~3) % 0x100000000183        else:184            return self.instanceSize185 186 187class 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=0192        ):193            self.sys_params = params194            self.valobj = rwt_pointer195            # 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_type199            )200            self.check_valid()201        else:202            logger >> "Marking as invalid - rwt is invald"203            self.valid = 0204        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],211                ),212                self.sys_params.types_cache.addr_ptr_type,213            )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)216 217    # perform sanity checks on the contents of this class_rw_t218    def check_valid(self):219        logger = lldb.formatters.Logger.Logger()220        self.valid = 1221        if not (222            Utilities.is_valid_pointer(223                self.roPointer, self.sys_params.pointer_size, allow_tagged=0224            )225        ):226            logger >> "Marking as invalid - ropointer is invalid"227            self.valid = 0228 229    def __str__(self):230        logger = lldb.formatters.Logger.Logger()231        return "roPointer = " + hex(self.roPointer)232 233    def is_valid(self):234        logger = lldb.formatters.Logger.Logger()235        if self.valid:236            return self.data.is_valid()237        return 0238 239 240class 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=0246            )247        ):248            self.sys_params = params249            self.valobj = isa_pointer250            self.check_valid()251        else:252            logger >> "Marking as invalid - isa is invalid or None"253            self.valid = 0254        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],261                ),262                self.sys_params.types_cache.addr_ptr_type,263            )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)266 267    # perform sanity checks on the contents of this class_t268    # 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 reading270    def check_valid(self):271        logger = lldb.formatters.Logger.Logger()272        self.valid = 1273 274        self.isaPointer = Utilities.read_child_of(275            self.valobj, 0, self.sys_params.types_cache.addr_ptr_type276        )277        if not (278            Utilities.is_valid_pointer(279                self.isaPointer, self.sys_params.pointer_size, allow_tagged=0280            )281        ):282            logger >> "Marking as invalid - isaPointer is invalid"283            self.valid = 0284            return285        if not (Utilities.is_allowed_pointer(self.isaPointer)):286            logger >> "Marking as invalid - isaPointer is not allowed"287            self.valid = 0288            return289 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,294        )295        if not (296            Utilities.is_valid_pointer(297                self.cachePointer, self.sys_params.pointer_size, allow_tagged=0298            )299        ):300            logger >> "Marking as invalid - cachePointer is invalid"301            self.valid = 0302            return303        if not (Utilities.is_allowed_pointer(self.cachePointer)):304            logger >> "Marking as invalid - cachePointer is not allowed"305            self.valid = 0306            return307        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,311        )312        if not (313            Utilities.is_valid_pointer(314                self.dataPointer, self.sys_params.pointer_size, allow_tagged=0315            )316        ):317            logger >> "Marking as invalid - dataPointer is invalid"318            self.valid = 0319            return320        if not (Utilities.is_allowed_pointer(self.dataPointer)):321            logger >> "Marking as invalid - dataPointer is not allowed"322            self.valid = 0323            return324 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,329        )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,336            )337        ):338            logger >> "Marking as invalid - superclassIsa is invalid"339            self.valid = 0340            return341        if not (Utilities.is_allowed_pointer(self.superclassIsaPointer)):342            logger >> "Marking as invalid - superclassIsa is not allowed"343            self.valid = 0344            return345 346    # in general, KVO is implemented by transparently subclassing347    # however, there could be exceptions where a class does something else348    # internally to implement the feature - this method will have no clue that a class349    # has been KVO'ed unless the standard implementation technique is used350    def is_kvo(self):351        logger = lldb.formatters.Logger.Logger()352        if self.is_valid():353            if self.class_name().startswith("NSKVONotifying_"):354                return 1355        return 0356 357    # some CF classes have a valid ObjC isa in their CFRuntimeBase358    # but instead of being class-specific this isa points to a match-'em-all class359    # which is __NSCFType (the versions without __ also exists and we are matching to it360    #                      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"365 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,373            )374            return Class_Data_V2(parent_isa_pointer, self.sys_params)375        else:376            return None377 378    def class_name(self):379        logger = lldb.formatters.Logger.Logger()380        if self.is_valid():381            return self.data.data.name382        else:383            return None384 385    def is_valid(self):386        logger = lldb.formatters.Logger.Logger()387        if self.valid:388            return self.data.is_valid()389        return 0390 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)405        )406 407    def is_tagged(self):408        return 0409 410    def instance_size(self, align=0):411        logger = lldb.formatters.Logger.Logger()412        if self.is_valid() == 0:413            return None414        return self.rwt.rot.instance_size(align)415 416 417# runtime v1 is much less intricate than v2 and stores relevant418# information directly in the class_t object419 420 421class 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=0427            )428        ):429            self.valid = 1430            self.sys_params = params431            self.valobj = isa_pointer432            self.check_valid()433        else:434            logger >> "Marking as invalid - isaPointer is invalid or None"435            self.valid = 0436        if self.valid:437            self.name = Utilities.read_ascii(438                self.valobj.GetTarget().GetProcess(), self.namePointer439            )440            if not (Utilities.is_valid_identifier(self.name)):441                logger >> "Marking as invalid - name is not valid"442                self.valid = 0443 444    # perform sanity checks on the contents of this class_t445    def check_valid(self):446        logger = lldb.formatters.Logger.Logger()447        self.valid = 1448 449        self.isaPointer = Utilities.read_child_of(450            self.valobj, 0, self.sys_params.types_cache.addr_ptr_type451        )452        if not (453            Utilities.is_valid_pointer(454                self.isaPointer, self.sys_params.pointer_size, allow_tagged=0455            )456        ):457            logger >> "Marking as invalid - isaPointer is invalid"458            self.valid = 0459            return460 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,465        )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,472            )473        ):474            logger >> "Marking as invalid - superclassIsa is invalid"475            self.valid = 0476            return477 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,482        )483        # if not(Utilities.is_valid_pointer(self.namePointer,self.sys_params.pointer_size,allow_tagged=0,allow_NULL=0)):484        # 	self.valid = 0485        # 	return486 487    # in general, KVO is implemented by transparently subclassing488    # however, there could be exceptions where a class does something else489    # internally to implement the feature - this method will have no clue that a class490    # has been KVO'ed unless the standard implementation technique is used491    def is_kvo(self):492        logger = lldb.formatters.Logger.Logger()493        if self.is_valid():494            if self.class_name().startswith("NSKVONotifying_"):495                return 1496        return 0497 498    # some CF classes have a valid ObjC isa in their CFRuntimeBase499    # but instead of being class-specific this isa points to a match-'em-all class500    # which is __NSCFType (the versions without __ also exists and we are matching to it501    #                      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"506 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,514            )515            return Class_Data_V1(parent_isa_pointer, self.sys_params)516        else:517            return None518 519    def class_name(self):520        logger = lldb.formatters.Logger.Logger()521        if self.is_valid():522            return self.name523        else:524            return None525 526    def is_valid(self):527        return self.valid528 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.name542            + "instanceSize = "543            + hex(self.instanceSize())544            + "\n"545        )546 547    def is_tagged(self):548        return 0549 550    def instance_size(self, align=0):551        logger = lldb.formatters.Logger.Logger()552        if self.is_valid() == 0:553            return None554        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,559            )560        if align:561            unalign = self.instance_size(0)562            if self.sys_params.is_64_bit:563                return ((unalign + 7) & ~7) % 0x100000000564            else:565                return ((unalign + 3) & ~3) % 0x100000000566        else:567            return self.instanceSize568 569 570# these are the only tagged pointers values for current versions571# of OSX - they might change in future OS releases, and no-one is572# advised to rely on these values, or any of the bitmasking formulas573# in TaggedClass_Data. doing otherwise is at your own risk574TaggedClass_Values_Lion = {575    1: "NSNumber",576    5: "NSManagedObject",577    6: "NSDate",578    7: "NSDateTS",579}580TaggedClass_Values_NMOS = {581    0: "NSAtom",582    3: "NSNumber",583    4: "NSDateTS",584    5: "NSManagedObject",585    6: "NSDate",586}587 588 589class TaggedClass_Data:590    def __init__(self, pointer, params):591        logger = lldb.formatters.Logger.Logger()592        global TaggedClass_Values_Lion, TaggedClass_Values_NMOS593        self.valid = 1594        self.name = None595        self.sys_params = params596        self.valobj = pointer597        self.val = (pointer & ~0x0000000000000000FF) >> 8598        self.class_bits = (pointer & 0xE) >> 1599        self.i_bits = (pointer & 0xF0) >> 4600 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 = 0607        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 = 0613 614    def is_valid(self):615        return self.valid616 617    def class_name(self):618        logger = lldb.formatters.Logger.Logger()619        if self.is_valid():620            return self.name621        else:622            return 0623 624    def value(self):625        return self.val if self.is_valid() else None626 627    def info_bits(self):628        return self.i_bits if self.is_valid() else None629 630    def is_kvo(self):631        return 0632 633    def is_cftype(self):634        return 0635 636    # we would need to go around looking for the superclass or ask the runtime637    # for now, we seem not to require support for this operation so we will merrily638    # pretend to be at a root point in the hierarchy639    def get_superclass(self):640        return None641 642    # anything that is handled here is tagged643    def is_tagged(self):644        return 1645 646    # it seems reasonable to say that a tagged pointer is the size of a pointer647    def instance_size(self, align=0):648        logger = lldb.formatters.Logger.Logger()649        if self.is_valid() == 0:650            return None651        return self.sys_params.pointer_size652 653 654class InvalidClass_Data:655    def __init__(self):656        pass657 658    def is_valid(self):659        return 0660 661 662class Version:663    def __init__(self, major, minor, release, build_string):664        self._major = major665        self._minor = minor666        self._release = release667        self._build_string = build_string668 669    def get_major(self):670        return self._major671 672    def get_minor(self):673        return self._minor674 675    def get_release(self):676        return self._release677 678    def get_build_string(self):679        return self._build_string680 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)685 686    def __lt__(self, other):687        if self.major < other.major:688            return 1689        if self.minor < other.minor:690            return 1691        if self.release < other.release:692            return 1693        # build strings are not compared since they are heavily platform-dependent and might not always694        # be available695        return 0696 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)703        )704 705    # Python 2.6 doesn't have functools.total_ordering, so we have to implement706    # other comparators707    def __gt__(self, other):708        return other < self709 710    def __le__(self, other):711        return not other < self712 713    def __ge__(self, other):714        return not self < other715 716 717runtime_version = lldb.formatters.cache.Cache()718os_version = lldb.formatters.cache.Cache()719types_caches = lldb.formatters.cache.Cache()720isa_caches = lldb.formatters.cache.Cache()721 722 723class SystemParameters:724    def __init__(self, valobj):725        logger = lldb.formatters.Logger.Logger()726        self.adjust_for_architecture(valobj)727        self.adjust_for_process(valobj)728 729    def adjust_for_process(self, valobj):730        logger = lldb.formatters.Logger.Logger()731        global runtime_version732        global os_version733        global types_caches734        global isa_caches735 736        process = valobj.GetTarget().GetProcess()737        # using the unique ID for added guarantees (see svn revision 172628 for738        # further details)739        self.pid = process.GetUniqueID()740 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)746 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)752 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=0758            )759            self.types_cache.addr_type = valobj.GetType().GetBasicType(760                lldb.eBasicTypeUnsignedLong761            )762            self.types_cache.addr_ptr_type = self.types_cache.addr_type.GetPointerType()763            self.types_cache.uint32_t = valobj.GetType().GetBasicType(764                lldb.eBasicTypeUnsignedInt765            )766            types_caches.add_item(self.pid, self.types_cache)767 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)773 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 == 8778        self.endianness = process.GetByteOrder()779        self.is_little = self.endianness == lldb.eByteOrderLittle780        self.cfruntime_size = 16 if self.is_64_bit else 8781 782    # a simple helper function that makes it more explicit that one is calculating783    # an offset that is made up of X pointers and Y bytes of additional data784    # taking into account pointer size - if you know there is going to be some padding785    # you can pass that in and it will be taken into account (since padding may be different between786    # 32 and 64 bit versions, you can pass padding value for both, the right787    # 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_size790        return value + padding64 if self.is_64_bit else value + padding32791 792 793class ObjCRuntime:794    # the ObjC runtime has no explicit "version" field that we can use795    # instead, we discriminate v1 from v2 by looking for the presence796    # of a well-known section only present in v1797    @staticmethod798    def runtime_version(process):799        logger = lldb.formatters.Logger.Logger()800        if process.IsValid() == 0:801            logger >> "No process - bailing out"802            return None803        target = process.GetTarget()804        num_modules = target.GetNumModules()805        module_objc = None806        for idx in range(num_modules):807            module = target.GetModuleAtIndex(idx)808            if module.GetFileSpec().GetFilename() == "libobjc.A.dylib":809                module_objc = module810                break811        if module_objc is None or module_objc.IsValid() == 0:812            logger >> "no libobjc - bailing out"813            return None814        num_sections = module.GetNumSections()815        section_objc = None816        for idx in range(num_sections):817            section = module.GetSectionAtIndex(idx)818            if section.GetName() == "__OBJC":819                section_objc = section820                break821        if section_objc is not None and section_objc.IsValid():822            logger >> "found __OBJC: v1"823            return 1824        logger >> "no __OBJC: v2"825        return 2826 827    @staticmethod828    def runtime_from_isa(isa):829        logger = lldb.formatters.Logger.Logger()830        runtime = ObjCRuntime(isa)831        runtime.isa = isa832        return runtime833 834    def __init__(self, valobj):835        logger = lldb.formatters.Logger.Logger()836        self.valobj = valobj837        self.adjust_for_architecture()838        self.sys_params = SystemParameters(self.valobj)839        self.unsigned_value = self.valobj.GetValueAsUnsigned()840        self.isa_value = None841 842    def adjust_for_architecture(self):843        pass844 845    # an ObjC pointer can either be tagged or must be aligned846    def is_tagged(self):847        logger = lldb.formatters.Logger.Logger()848        if self.valobj is None:849            return 0850        return Utilities.is_valid_pointer(851            self.unsigned_value, self.sys_params.pointer_size, allow_tagged=1852        ) and not (853            Utilities.is_valid_pointer(854                self.unsigned_value, self.sys_params.pointer_size, allow_tagged=0855            )856        )857 858    def is_valid(self):859        logger = lldb.formatters.Logger.Logger()860        if self.valobj is None:861            return 0862        if self.valobj.IsInScope() == 0:863            return 0864        return Utilities.is_valid_pointer(865            self.unsigned_value, self.sys_params.pointer_size, allow_tagged=1866        )867 868    def is_nil(self):869        return self.unsigned_value == 0870 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_value876        self.isa_pointer = self.valobj.CreateChildAtOffset(877            "cfisa", 0, self.sys_params.types_cache.addr_ptr_type878        )879        if self.isa_pointer is None or self.isa_pointer.IsValid() == 0:880            logger >> "invalid isa - bailing out"881            return None882        self.isa_value = self.isa_pointer.GetValueAsUnsigned(1)883        if self.isa_value == 1:884            logger >> "invalid isa value - bailing out"885            return None886        return Ellipsis887 888    def read_class_data(self):889        logger = lldb.formatters.Logger.Logger()890        global isa_cache891        if self.is_tagged():892            # tagged pointers only exist in ObjC v2893            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 wrong896                # we could try and predetect this before even creating a TaggedClass_Data object897                # but unless performance requires it, this seems a cleaner way898                # to tackle the task899                tentative_tagged = TaggedClass_Data(900                    self.unsigned_value, self.sys_params901                )902                if tentative_tagged.is_valid():903                    logger >> "truly tagged"904                    return tentative_tagged905                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 data916        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 data925 926 927# these classes below can be used by the data formatters to provide a928# consistent message that describes a given runtime-generated situation929 930 931class SpecialSituation_Description:932    def message(self):933        return ""934 935 936class InvalidPointer_Description(SpecialSituation_Description):937    def __init__(self, nil):938        self.is_nil = nil939 940    def message(self):941        if self.is_nil:942            return '@"<nil>"'943        else:944            return "<invalid pointer>"945 946 947class InvalidISA_Description(SpecialSituation_Description):948    def __init__(self):949        pass950 951    def message(self):952        return "<not an Objective-C object>"953 954 955class ThisIsZombie_Description(SpecialSituation_Description):956    def message(self):957        return "<freed object>"958