1""" 2LLDB AppKit formatters 3 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-exception 7""" 8# example summary provider for NSDictionary 9# the real summary is now C++ code built into LLDB 10import lldb 11import ctypes 12import lldb.runtime.objc.objc_runtime 13import lldb.formatters.metrics 14import lldb.formatters.Logger 15 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 not 23# trying to provide anything but the count for an NSDictionary, so they need not 24# obey the interface specification for synthetic children providers 25 26 27class NSCFDictionary_SummaryProvider: 28 def adjust_for_architecture(self): 29 pass 30 31 def __init__(self, valobj, params): 32 logger = lldb.formatters.Logger.Logger() 33 self.valobj = valobj 34 self.sys_params = params 35 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 # empirically determined on both 32 and 64bit desktop Mac OS X 51 # probably boils down to 2 pointers and 4 bytes of data, but 52 # the description of __CFDictionary is not readily available so most 53 # of this is guesswork, plain and simple 54 def offset(self): 55 logger = lldb.formatters.Logger.Logger() 56 if self.sys_params.is_64_bit: 57 return 20 58 else: 59 return 12 60 61 def num_children(self): 62 logger = lldb.formatters.Logger.Logger() 63 num_children_vo = self.valobj.CreateChildAtOffset( 64 "count", self.offset(), self.sys_params.types_cache.NSUInteger 65 ) 66 return num_children_vo.GetValueAsUnsigned(0) 67 68 69class NSDictionaryI_SummaryProvider: 70 def adjust_for_architecture(self): 71 pass 72 73 def __init__(self, valobj, params): 74 logger = lldb.formatters.Logger.Logger() 75 self.valobj = valobj 76 self.sys_params = params 77 if not (self.sys_params.types_cache.NSUInteger): 78 if self.sys_params.is_64_bit: 79 self.sys_params.types_cache.NSUInteger = ( 80 self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) 81 ) 82 else: 83 self.sys_params.types_cache.NSUInteger = ( 84 self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt) 85 ) 86 self.update() 87 88 def update(self): 89 logger = lldb.formatters.Logger.Logger() 90 self.adjust_for_architecture() 91 92 # we just need to skip the ISA and the count immediately follows 93 def offset(self): 94 logger = lldb.formatters.Logger.Logger() 95 return self.sys_params.pointer_size 96 97 def num_children(self): 98 logger = lldb.formatters.Logger.Logger() 99 num_children_vo = self.valobj.CreateChildAtOffset( 100 "count", self.offset(), self.sys_params.types_cache.NSUInteger 101 ) 102 value = num_children_vo.GetValueAsUnsigned(0) 103 if value is not None: 104 # the MS6bits on immutable dictionaries seem to be taken by the LSB of capacity 105 # not sure if it is a bug or some weird sort of feature, but masking that out 106 # gets the count right 107 if self.sys_params.is_64_bit: 108 value = value & ~0xFC00000000000000 109 else: 110 value = value & ~0xFC000000 111 return value 112 113 114class NSDictionaryM_SummaryProvider: 115 def adjust_for_architecture(self): 116 pass 117 118 def __init__(self, valobj, params): 119 logger = lldb.formatters.Logger.Logger() 120 self.valobj = valobj 121 self.sys_params = params 122 if not (self.sys_params.types_cache.NSUInteger): 123 if self.sys_params.is_64_bit: 124 self.sys_params.types_cache.NSUInteger = ( 125 self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong) 126 ) 127 else: 128 self.sys_params.types_cache.NSUInteger = ( 129 self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt) 130 ) 131 self.update() 132 133 def update(self): 134 logger = lldb.formatters.Logger.Logger() 135 self.adjust_for_architecture() 136 137 # we just need to skip the ISA and the count immediately follows 138 def offset(self): 139 return self.sys_params.pointer_size 140 141 def num_children(self): 142 logger = lldb.formatters.Logger.Logger() 143 num_children_vo = self.valobj.CreateChildAtOffset( 144 "count", self.offset(), self.sys_params.types_cache.NSUInteger 145 ) 146 value = num_children_vo.GetValueAsUnsigned(0) 147 if value is not None: 148 # the MS6bits on immutable dictionaries seem to be taken by the LSB of capacity 149 # not sure if it is a bug or some weird sort of feature, but masking that out 150 # gets the count right 151 if self.sys_params.is_64_bit: 152 value = value & ~0xFC00000000000000 153 else: 154 value = value & ~0xFC000000 155 return value 156 157 158class NSDictionaryUnknown_SummaryProvider: 159 def adjust_for_architecture(self): 160 pass 161 162 def __init__(self, valobj, params): 163 logger = lldb.formatters.Logger.Logger() 164 self.valobj = valobj 165 self.sys_params = params 166 self.update() 167 168 def update(self): 169 logger = lldb.formatters.Logger.Logger() 170 self.adjust_for_architecture() 171 172 def num_children(self): 173 logger = lldb.formatters.Logger.Logger() 174 stream = lldb.SBStream() 175 self.valobj.GetExpressionPath(stream) 176 num_children_vo = self.valobj.CreateValueFromExpression( 177 "count", "(int)[" + stream.GetData() + " count]" 178 ) 179 if num_children_vo.IsValid(): 180 return num_children_vo.GetValueAsUnsigned(0) 181 return "<variable is not NSDictionary>" 182 183 184def GetSummary_Impl(valobj): 185 logger = lldb.formatters.Logger.Logger() 186 global statistics 187 ( 188 class_data, 189 wrapper, 190 ) = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection( 191 valobj, statistics 192 ) 193 if wrapper: 194 return wrapper 195 196 name_string = class_data.class_name() 197 198 logger >> "class name is: " + str(name_string) 199 200 if name_string == "__NSCFDictionary": 201 wrapper = NSCFDictionary_SummaryProvider(valobj, class_data.sys_params) 202 statistics.metric_hit("code_notrun", valobj) 203 elif name_string == "__NSDictionaryI": 204 wrapper = NSDictionaryI_SummaryProvider(valobj, class_data.sys_params) 205 statistics.metric_hit("code_notrun", valobj) 206 elif name_string == "__NSDictionaryM": 207 wrapper = NSDictionaryM_SummaryProvider(valobj, class_data.sys_params) 208 statistics.metric_hit("code_notrun", valobj) 209 else: 210 wrapper = NSDictionaryUnknown_SummaryProvider(valobj, class_data.sys_params) 211 statistics.metric_hit( 212 "unknown_class", valobj.GetName() + " seen as " + name_string 213 ) 214 return wrapper 215 216 217def CFDictionary_SummaryProvider(valobj, dict): 218 logger = lldb.formatters.Logger.Logger() 219 provider = GetSummary_Impl(valobj) 220 if provider is not None: 221 if isinstance( 222 provider, lldb.runtime.objc.objc_runtime.SpecialSituation_Description 223 ): 224 return provider.message() 225 try: 226 summary = provider.num_children() 227 except: 228 summary = None 229 logger >> "got summary " + str(summary) 230 if summary is None: 231 return "<variable is not NSDictionary>" 232 if isinstance(summary, str): 233 return summary 234 return str(summary) + ( 235 " key/value pairs" if summary != 1 else " key/value pair" 236 ) 237 return "Summary Unavailable" 238 239 240def CFDictionary_SummaryProvider2(valobj, dict): 241 logger = lldb.formatters.Logger.Logger() 242 provider = GetSummary_Impl(valobj) 243 if provider is not None: 244 if isinstance( 245 provider, lldb.runtime.objc.objc_runtime.SpecialSituation_Description 246 ): 247 return provider.message() 248 try: 249 summary = provider.num_children() 250 except: 251 summary = None 252 logger >> "got summary " + str(summary) 253 if summary is None: 254 summary = "<variable is not CFDictionary>" 255 if isinstance(summary, str): 256 return summary 257 else: 258 # needed on OSX Mountain Lion 259 if provider.sys_params.is_64_bit: 260 summary = summary & ~0x0F1F000000000000 261 summary = '@"' + str(summary) + (' entries"' if summary != 1 else ' entry"') 262 return summary 263 return "Summary Unavailable" 264 265 266def __lldb_init_module(debugger, dict): 267 debugger.HandleCommand( 268 "type summary add -F CFDictionary.CFDictionary_SummaryProvider NSDictionary" 269 ) 270 debugger.HandleCommand( 271 "type summary add -F CFDictionary.CFDictionary_SummaryProvider2 CFDictionaryRef CFMutableDictionaryRef" 272 ) 273