295 lines · python
1"""2LLDB AppKit 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"""8# summary provider for NSSet9import lldb10import ctypes11import lldb.runtime.objc.objc_runtime12import lldb.formatters.metrics13import CFBag14import lldb.formatters.Logger15 16statistics = lldb.formatters.metrics.Metrics()17statistics.add_metric("invalid_isa")18statistics.add_metric("invalid_pointer")19statistics.add_metric("unknown_class")20statistics.add_metric("code_notrun")21 22# despite the similary to synthetic children providers, these classes are not23# trying to provide anything but the port number of an NSMachPort, so they need not24# obey the interface specification for synthetic children providers25 26 27class NSCFSet_SummaryProvider:28 def adjust_for_architecture(self):29 pass30 31 def __init__(self, valobj, params):32 logger = lldb.formatters.Logger.Logger()33 self.valobj = valobj34 self.sys_params = params35 if not (self.sys_params.types_cache.NSUInteger):36 if self.sys_params.is_64_bit:37 self.sys_params.types_cache.NSUInteger = (38 self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)39 )40 else:41 self.sys_params.types_cache.NSUInteger = (42 self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)43 )44 self.update()45 46 def update(self):47 logger = lldb.formatters.Logger.Logger()48 self.adjust_for_architecture()49 50 # one pointer is the ISA51 # then we have one other internal pointer, plus52 # 4 bytes worth of flags. hence, these values53 def offset(self):54 logger = lldb.formatters.Logger.Logger()55 if self.sys_params.is_64_bit:56 return 2057 else:58 return 1259 60 def count(self):61 logger = lldb.formatters.Logger.Logger()62 vcount = self.valobj.CreateChildAtOffset(63 "count", self.offset(), self.sys_params.types_cache.NSUInteger64 )65 return vcount.GetValueAsUnsigned(0)66 67 68class NSSetUnknown_SummaryProvider:69 def adjust_for_architecture(self):70 pass71 72 def __init__(self, valobj, params):73 logger = lldb.formatters.Logger.Logger()74 self.valobj = valobj75 self.sys_params = params76 self.update()77 78 def update(self):79 logger = lldb.formatters.Logger.Logger()80 self.adjust_for_architecture()81 82 def count(self):83 logger = lldb.formatters.Logger.Logger()84 stream = lldb.SBStream()85 self.valobj.GetExpressionPath(stream)86 expr = "(int)[" + stream.GetData() + " count]"87 num_children_vo = self.valobj.CreateValueFromExpression("count", expr)88 if num_children_vo.IsValid():89 return num_children_vo.GetValueAsUnsigned(0)90 return "<variable is not NSSet>"91 92 93class NSSetI_SummaryProvider:94 def adjust_for_architecture(self):95 pass96 97 def __init__(self, valobj, params):98 logger = lldb.formatters.Logger.Logger()99 self.valobj = valobj100 self.sys_params = params101 if not (self.sys_params.types_cache.NSUInteger):102 if self.sys_params.is_64_bit:103 self.sys_params.types_cache.NSUInteger = (104 self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)105 )106 else:107 self.sys_params.types_cache.NSUInteger = (108 self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)109 )110 self.update()111 112 def update(self):113 logger = lldb.formatters.Logger.Logger()114 self.adjust_for_architecture()115 116 # we just need to skip the ISA and the count immediately follows117 def offset(self):118 logger = lldb.formatters.Logger.Logger()119 return self.sys_params.pointer_size120 121 def count(self):122 logger = lldb.formatters.Logger.Logger()123 num_children_vo = self.valobj.CreateChildAtOffset(124 "count", self.offset(), self.sys_params.types_cache.NSUInteger125 )126 value = num_children_vo.GetValueAsUnsigned(0)127 if value is not None:128 # the MSB on immutable sets seems to be taken by some other data129 # not sure if it is a bug or some weird sort of feature, but masking it out130 # gets the count right (unless, of course, someone's dictionaries grow131 # too large - but I have not tested this)132 if self.sys_params.is_64_bit:133 value = value & ~0xFF00000000000000134 else:135 value = value & ~0xFF000000136 return value137 138 139class NSSetM_SummaryProvider:140 def adjust_for_architecture(self):141 pass142 143 def __init__(self, valobj, params):144 logger = lldb.formatters.Logger.Logger()145 self.valobj = valobj146 self.sys_params = params147 if not (self.sys_params.types_cache.NSUInteger):148 if self.sys_params.is_64_bit:149 self.sys_params.types_cache.NSUInteger = (150 self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)151 )152 else:153 self.sys_params.types_cache.NSUInteger = (154 self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)155 )156 self.update()157 158 def update(self):159 logger = lldb.formatters.Logger.Logger()160 self.adjust_for_architecture()161 162 # we just need to skip the ISA and the count immediately follows163 def offset(self):164 logger = lldb.formatters.Logger.Logger()165 return self.sys_params.pointer_size166 167 def count(self):168 logger = lldb.formatters.Logger.Logger()169 num_children_vo = self.valobj.CreateChildAtOffset(170 "count", self.offset(), self.sys_params.types_cache.NSUInteger171 )172 return num_children_vo.GetValueAsUnsigned(0)173 174 175class NSCountedSet_SummaryProvider:176 def adjust_for_architecture(self):177 pass178 179 def __init__(self, valobj, params):180 logger = lldb.formatters.Logger.Logger()181 self.valobj = valobj182 self.sys_params = params183 if not (self.sys_params.types_cache.voidptr):184 self.sys_params.types_cache.voidptr = (185 self.valobj.GetType().GetBasicType(lldb.eBasicTypeVoid).GetPointerType()186 )187 self.update()188 189 def update(self):190 logger = lldb.formatters.Logger.Logger()191 self.adjust_for_architecture()192 193 # an NSCountedSet is implemented using a CFBag whose pointer just follows194 # the ISA195 def offset(self):196 logger = lldb.formatters.Logger.Logger()197 return self.sys_params.pointer_size198 199 def count(self):200 logger = lldb.formatters.Logger.Logger()201 cfbag_vo = self.valobj.CreateChildAtOffset(202 "bag_impl", self.offset(), self.sys_params.types_cache.voidptr203 )204 return CFBag.CFBagRef_SummaryProvider(cfbag_vo, self.sys_params).length()205 206 207def GetSummary_Impl(valobj):208 logger = lldb.formatters.Logger.Logger()209 global statistics210 (211 class_data,212 wrapper,213 ) = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection(214 valobj, statistics215 )216 if wrapper:217 return wrapper218 219 name_string = class_data.class_name()220 logger >> "class name is: " + str(name_string)221 222 if name_string == "__NSCFSet":223 wrapper = NSCFSet_SummaryProvider(valobj, class_data.sys_params)224 statistics.metric_hit("code_notrun", valobj)225 elif name_string == "__NSSetI":226 wrapper = NSSetI_SummaryProvider(valobj, class_data.sys_params)227 statistics.metric_hit("code_notrun", valobj)228 elif name_string == "__NSSetM":229 wrapper = NSSetM_SummaryProvider(valobj, class_data.sys_params)230 statistics.metric_hit("code_notrun", valobj)231 elif name_string == "NSCountedSet":232 wrapper = NSCountedSet_SummaryProvider(valobj, class_data.sys_params)233 statistics.metric_hit("code_notrun", valobj)234 else:235 wrapper = NSSetUnknown_SummaryProvider(valobj, class_data.sys_params)236 statistics.metric_hit(237 "unknown_class", valobj.GetName() + " seen as " + name_string238 )239 return wrapper240 241 242def NSSet_SummaryProvider(valobj, dict):243 logger = lldb.formatters.Logger.Logger()244 provider = GetSummary_Impl(valobj)245 if provider is not None:246 try:247 summary = provider.count()248 except:249 summary = None250 if summary is None:251 summary = "<variable is not NSSet>"252 if isinstance(summary, str):253 return summary254 else:255 summary = str(summary) + (" objects" if summary != 1 else " object")256 return summary257 return "Summary Unavailable"258 259 260def NSSet_SummaryProvider2(valobj, dict):261 logger = lldb.formatters.Logger.Logger()262 provider = GetSummary_Impl(valobj)263 if provider is not None:264 if isinstance(265 provider, lldb.runtime.objc.objc_runtime.SpecialSituation_Description266 ):267 return provider.message()268 try:269 summary = provider.count()270 except:271 summary = None272 logger >> "got summary " + str(summary)273 # for some reason, one needs to clear some bits for the count returned274 # to be correct when using directly CF*SetRef as compared to NS*Set275 # this only happens on 64bit, and the bit mask was derived through276 # experimentation (if counts start looking weird, then most probably277 # the mask needs to be changed)278 if summary is None:279 summary = "<variable is not CFSet>"280 if isinstance(summary, str):281 return summary282 else:283 if provider.sys_params.is_64_bit:284 summary = summary & ~0x1FFF000000000000285 summary = '@"' + str(summary) + (' values"' if summary != 1 else ' value"')286 return summary287 return "Summary Unavailable"288 289 290def __lldb_init_module(debugger, dict):291 debugger.HandleCommand("type summary add -F NSSet.NSSet_SummaryProvider NSSet")292 debugger.HandleCommand(293 "type summary add -F NSSet.NSSet_SummaryProvider2 CFSetRef CFMutableSetRef"294 )295