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 NSArray 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# much less functional than the other two cases below 28# just runs code to get to the count and then returns 29# no children 30 31 32class NSArrayKVC_SynthProvider: 33 34 def adjust_for_architecture(self): 35 pass 36 37 def __init__(self, valobj, dict, params): 38 logger = lldb.formatters.Logger.Logger() 39 self.valobj = valobj 40 self.update() 41 42 def update(self): 43 logger = lldb.formatters.Logger.Logger() 44 self.adjust_for_architecture() 45 46 def num_children(self): 47 logger = lldb.formatters.Logger.Logger() 48 stream = lldb.SBStream() 49 self.valobj.GetExpressionPath(stream) 50 num_children_vo = self.valobj.CreateValueFromExpression( 51 "count", "(int)[" + stream.GetData() + " count]") 52 if num_children_vo.IsValid(): 53 return num_children_vo.GetValueAsUnsigned(0) 54 return "<variable is not NSArray>" 55 56# much less functional than the other two cases below 57# just runs code to get to the count and then returns 58# no children 59 60 61class NSArrayCF_SynthProvider: 62 63 def adjust_for_architecture(self): 64 pass 65 66 def __init__(self, valobj, dict, params): 67 logger = lldb.formatters.Logger.Logger() 68 self.valobj = valobj 69 self.sys_params = params 70 if not (self.sys_params.types_cache.ulong): 71 self.sys_params.types_cache.ulong = self.valobj.GetType( 72 ).GetBasicType(lldb.eBasicTypeUnsignedLong) 73 self.update() 74 75 def update(self): 76 logger = lldb.formatters.Logger.Logger() 77 self.adjust_for_architecture() 78 79 def num_children(self): 80 logger = lldb.formatters.Logger.Logger() 81 num_children_vo = self.valobj.CreateChildAtOffset( 82 "count", self.sys_params.cfruntime_size, self.sys_params.types_cache.ulong) 83 return num_children_vo.GetValueAsUnsigned(0) 84 85 86class NSArrayI_SynthProvider: 87 88 def adjust_for_architecture(self): 89 pass 90 91 def __init__(self, valobj, dict, params): 92 logger = lldb.formatters.Logger.Logger() 93 self.valobj = valobj 94 self.sys_params = params 95 if not(self.sys_params.types_cache.long): 96 self.sys_params.types_cache.long = self.valobj.GetType( 97 ).GetBasicType(lldb.eBasicTypeLong) 98 self.update() 99 100 def update(self): 101 logger = lldb.formatters.Logger.Logger() 102 self.adjust_for_architecture() 103 104 # skip the isa pointer and get at the size 105 def num_children(self): 106 logger = lldb.formatters.Logger.Logger() 107 count = self.valobj.CreateChildAtOffset( 108 "count", 109 self.sys_params.pointer_size, 110 self.sys_params.types_cache.long) 111 return count.GetValueAsUnsigned(0) 112 113 114class NSArrayM_SynthProvider: 115 116 def adjust_for_architecture(self): 117 pass 118 119 def __init__(self, valobj, dict, params): 120 logger = lldb.formatters.Logger.Logger() 121 self.valobj = valobj 122 self.sys_params = params 123 if not(self.sys_params.types_cache.long): 124 self.sys_params.types_cache.long = self.valobj.GetType( 125 ).GetBasicType(lldb.eBasicTypeLong) 126 self.update() 127 128 def update(self): 129 logger = lldb.formatters.Logger.Logger() 130 self.adjust_for_architecture() 131 132 # skip the isa pointer and get at the size 133 def num_children(self): 134 logger = lldb.formatters.Logger.Logger() 135 count = self.valobj.CreateChildAtOffset( 136 "count", 137 self.sys_params.pointer_size, 138 self.sys_params.types_cache.long) 139 return count.GetValueAsUnsigned(0) 140 141# this is the actual synth provider, but is just a wrapper that checks 142# whether valobj is an instance of __NSArrayI or __NSArrayM and sets up an 143# appropriate backend layer to do the computations 144 145 146class NSArray_SynthProvider: 147 148 def adjust_for_architecture(self): 149 pass 150 151 def __init__(self, valobj, dict): 152 logger = lldb.formatters.Logger.Logger() 153 self.valobj = valobj 154 self.adjust_for_architecture() 155 self.error = False 156 self.wrapper = self.make_wrapper() 157 self.invalid = (self.wrapper is None) 158 159 def num_children(self): 160 logger = lldb.formatters.Logger.Logger() 161 if self.wrapper is None: 162 return 0 163 return self.wrapper.num_children() 164 165 def update(self): 166 logger = lldb.formatters.Logger.Logger() 167 if self.wrapper is None: 168 return 169 self.wrapper.update() 170 171 # this code acts as our defense against NULL and uninitialized 172 # NSArray pointers, which makes it much longer than it would be otherwise 173 def make_wrapper(self): 174 logger = lldb.formatters.Logger.Logger() 175 if self.valobj.GetValueAsUnsigned() == 0: 176 self.error = True 177 return lldb.runtime.objc.objc_runtime.InvalidPointer_Description( 178 True) 179 else: 180 global statistics 181 class_data, wrapper = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection( 182 self.valobj, statistics) 183 if wrapper: 184 self.error = True 185 return wrapper 186 187 name_string = class_data.class_name() 188 189 logger >> "Class name is " + str(name_string) 190 191 if name_string == '__NSArrayI': 192 wrapper = NSArrayI_SynthProvider( 193 self.valobj, dict, class_data.sys_params) 194 statistics.metric_hit('code_notrun', self.valobj.GetName()) 195 elif name_string == '__NSArrayM': 196 wrapper = NSArrayM_SynthProvider( 197 self.valobj, dict, class_data.sys_params) 198 statistics.metric_hit('code_notrun', self.valobj.GetName()) 199 elif name_string == '__NSCFArray': 200 wrapper = NSArrayCF_SynthProvider( 201 self.valobj, dict, class_data.sys_params) 202 statistics.metric_hit('code_notrun', self.valobj.GetName()) 203 else: 204 wrapper = NSArrayKVC_SynthProvider( 205 self.valobj, dict, class_data.sys_params) 206 statistics.metric_hit( 207 'unknown_class', str( 208 self.valobj.GetName()) + " seen as " + name_string) 209 return wrapper 210 211 212def CFArray_SummaryProvider(valobj, dict): 213 logger = lldb.formatters.Logger.Logger() 214 provider = NSArray_SynthProvider(valobj, dict) 215 if not provider.invalid: 216 if provider.error: 217 return provider.wrapper.message() 218 try: 219 summary = int(provider.num_children()) 220 except: 221 summary = None 222 logger >> "provider gave me " + str(summary) 223 if summary is None: 224 summary = '<variable is not NSArray>' 225 elif isinstance(summary, basestring): 226 pass 227 else: 228 # we format it like it were a CFString to make it look the same as 229 # the summary from Xcode 230 summary = '@"' + str(summary) + \ 231 (" objects" if summary != 1 else " object") + '"' 232 return summary 233 return 'Summary Unavailable' 234 235 236def __lldb_init_module(debugger, dict): 237 debugger.HandleCommand( 238 "type summary add -F CFArray.CFArray_SummaryProvider NSArray CFArrayRef CFMutableArrayRef") 239