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 16try: 17 basestring 18except NameError 19 basestring = str 20 21statistics = lldb.formatters.metrics.Metrics() 22statistics.add_metric('invalid_isa') 23statistics.add_metric('invalid_pointer') 24statistics.add_metric('unknown_class') 25statistics.add_metric('code_notrun') 26 27# despite the similary to synthetic children providers, these classes are not 28# trying to provide anything but the count for an NSDictionary, so they need not 29# obey the interface specification for synthetic children providers 30 31 32class NSCFDictionary_SummaryProvider: 33 34 def adjust_for_architecture(self): 35 pass 36 37 def __init__(self, valobj, params): 38 logger = lldb.formatters.Logger.Logger() 39 self.valobj = valobj 40 self.sys_params = params 41 if not(self.sys_params.types_cache.NSUInteger): 42 if self.sys_params.is_64_bit: 43 self.sys_params.types_cache.NSUInteger = self.valobj.GetType( 44 ).GetBasicType(lldb.eBasicTypeUnsignedLong) 45 else: 46 self.sys_params.types_cache.NSUInteger = self.valobj.GetType( 47 ).GetBasicType(lldb.eBasicTypeUnsignedInt) 48 self.update() 49 50 def update(self): 51 logger = lldb.formatters.Logger.Logger() 52 self.adjust_for_architecture() 53 54 # empirically determined on both 32 and 64bit desktop Mac OS X 55 # probably boils down to 2 pointers and 4 bytes of data, but 56 # the description of __CFDictionary is not readily available so most 57 # of this is guesswork, plain and simple 58 def offset(self): 59 logger = lldb.formatters.Logger.Logger() 60 if self.sys_params.is_64_bit: 61 return 20 62 else: 63 return 12 64 65 def num_children(self): 66 logger = lldb.formatters.Logger.Logger() 67 num_children_vo = self.valobj.CreateChildAtOffset( 68 "count", self.offset(), self.sys_params.types_cache.NSUInteger) 69 return num_children_vo.GetValueAsUnsigned(0) 70 71 72class NSDictionaryI_SummaryProvider: 73 74 def adjust_for_architecture(self): 75 pass 76 77 def __init__(self, valobj, params): 78 logger = lldb.formatters.Logger.Logger() 79 self.valobj = valobj 80 self.sys_params = params 81 if not(self.sys_params.types_cache.NSUInteger): 82 if self.sys_params.is_64_bit: 83 self.sys_params.types_cache.NSUInteger = self.valobj.GetType( 84 ).GetBasicType(lldb.eBasicTypeUnsignedLong) 85 else: 86 self.sys_params.types_cache.NSUInteger = self.valobj.GetType( 87 ).GetBasicType(lldb.eBasicTypeUnsignedInt) 88 self.update() 89 90 def update(self): 91 logger = lldb.formatters.Logger.Logger() 92 self.adjust_for_architecture() 93 94 # we just need to skip the ISA and the count immediately follows 95 def offset(self): 96 logger = lldb.formatters.Logger.Logger() 97 return self.sys_params.pointer_size 98 99 def num_children(self): 100 logger = lldb.formatters.Logger.Logger() 101 num_children_vo = self.valobj.CreateChildAtOffset( 102 "count", self.offset(), self.sys_params.types_cache.NSUInteger) 103 value = num_children_vo.GetValueAsUnsigned(0) 104 if value is not None: 105 # the MS6bits on immutable dictionaries seem to be taken by the LSB of capacity 106 # not sure if it is a bug or some weird sort of feature, but masking that out 107 # gets the count right 108 if self.sys_params.is_64_bit: 109 value = value & ~0xFC00000000000000 110 else: 111 value = value & ~0xFC000000 112 return value 113 114 115class NSDictionaryM_SummaryProvider: 116 117 def adjust_for_architecture(self): 118 pass 119 120 def __init__(self, valobj, params): 121 logger = lldb.formatters.Logger.Logger() 122 self.valobj = valobj 123 self.sys_params = params 124 if not(self.sys_params.types_cache.NSUInteger): 125 if self.sys_params.is_64_bit: 126 self.sys_params.types_cache.NSUInteger = self.valobj.GetType( 127 ).GetBasicType(lldb.eBasicTypeUnsignedLong) 128 else: 129 self.sys_params.types_cache.NSUInteger = self.valobj.GetType( 130 ).GetBasicType(lldb.eBasicTypeUnsignedInt) 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 value = num_children_vo.GetValueAsUnsigned(0) 146 if value is not None: 147 # the MS6bits on immutable dictionaries seem to be taken by the LSB of capacity 148 # not sure if it is a bug or some weird sort of feature, but masking that out 149 # gets the count right 150 if self.sys_params.is_64_bit: 151 value = value & ~0xFC00000000000000 152 else: 153 value = value & ~0xFC000000 154 return value 155 156 157class NSDictionaryUnknown_SummaryProvider: 158 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 if num_children_vo.IsValid(): 179 return num_children_vo.GetValueAsUnsigned(0) 180 return '<variable is not NSDictionary>' 181 182 183def GetSummary_Impl(valobj): 184 logger = lldb.formatters.Logger.Logger() 185 global statistics 186 class_data, wrapper = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection( 187 valobj, statistics) 188 if wrapper: 189 return wrapper 190 191 name_string = class_data.class_name() 192 193 logger >> "class name is: " + str(name_string) 194 195 if name_string == '__NSCFDictionary': 196 wrapper = NSCFDictionary_SummaryProvider(valobj, class_data.sys_params) 197 statistics.metric_hit('code_notrun', valobj) 198 elif name_string == '__NSDictionaryI': 199 wrapper = NSDictionaryI_SummaryProvider(valobj, class_data.sys_params) 200 statistics.metric_hit('code_notrun', valobj) 201 elif name_string == '__NSDictionaryM': 202 wrapper = NSDictionaryM_SummaryProvider(valobj, class_data.sys_params) 203 statistics.metric_hit('code_notrun', valobj) 204 else: 205 wrapper = NSDictionaryUnknown_SummaryProvider( 206 valobj, class_data.sys_params) 207 statistics.metric_hit( 208 'unknown_class', 209 valobj.GetName() + 210 " seen as " + 211 name_string) 212 return wrapper 213 214 215def CFDictionary_SummaryProvider(valobj, dict): 216 logger = lldb.formatters.Logger.Logger() 217 provider = GetSummary_Impl(valobj) 218 if provider is not None: 219 if isinstance( 220 provider, 221 lldb.runtime.objc.objc_runtime.SpecialSituation_Description): 222 return provider.message() 223 try: 224 summary = provider.num_children() 225 except: 226 summary = None 227 logger >> "got summary " + str(summary) 228 if summary is None: 229 return '<variable is not NSDictionary>' 230 if isinstance(summary, basestring): 231 return summary 232 return str(summary) + (" key/value pairs" if summary != 233 1 else " key/value pair") 234 return 'Summary Unavailable' 235 236 237def CFDictionary_SummaryProvider2(valobj, dict): 238 logger = lldb.formatters.Logger.Logger() 239 provider = GetSummary_Impl(valobj) 240 if provider is not None: 241 if isinstance( 242 provider, 243 lldb.runtime.objc.objc_runtime.SpecialSituation_Description): 244 return provider.message() 245 try: 246 summary = provider.num_children() 247 except: 248 summary = None 249 logger >> "got summary " + str(summary) 250 if summary is None: 251 summary = '<variable is not CFDictionary>' 252 if isinstance(summary, basestring): 253 return summary 254 else: 255 # needed on OSX Mountain Lion 256 if provider.sys_params.is_64_bit: 257 summary = summary & ~0x0f1f000000000000 258 summary = '@"' + str(summary) + \ 259 (' entries"' if summary != 1 else ' entry"') 260 return summary 261 return 'Summary Unavailable' 262 263 264def __lldb_init_module(debugger, dict): 265 debugger.HandleCommand( 266 "type summary add -F CFDictionary.CFDictionary_SummaryProvider NSDictionary") 267 debugger.HandleCommand( 268 "type summary add -F CFDictionary.CFDictionary_SummaryProvider2 CFDictionaryRef CFMutableDictionaryRef") 269