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 NSNumber 9# the real summary is now C++ code built into LLDB 10 11import lldb 12import ctypes 13import lldb.runtime.objc.objc_runtime 14import lldb.formatters.metrics 15import struct 16import lldb.formatters.Logger 17 18statistics = lldb.formatters.metrics.Metrics() 19statistics.add_metric("invalid_isa") 20statistics.add_metric("invalid_pointer") 21statistics.add_metric("unknown_class") 22statistics.add_metric("code_notrun") 23 24# despite the similary to synthetic children providers, these classes are not 25# trying to provide anything but the port number of an NSNumber, so they need not 26# obey the interface specification for synthetic children providers 27 28 29class NSTaggedNumber_SummaryProvider: 30 def adjust_for_architecture(self): 31 pass 32 33 def __init__(self, valobj, info_bits, data, params): 34 logger = lldb.formatters.Logger.Logger() 35 self.valobj = valobj 36 self.sys_params = params 37 self.info_bits = info_bits 38 self.data = data 39 self.update() 40 41 def update(self): 42 logger = lldb.formatters.Logger.Logger() 43 self.adjust_for_architecture() 44 45 def value(self): 46 logger = lldb.formatters.Logger.Logger() 47 # in spite of the plenty of types made available by the public NSNumber API 48 # only a bunch of these are actually used in the internal implementation 49 # unfortunately, the original type information appears to be lost 50 # so we try to at least recover the proper magnitude of the data 51 if self.info_bits == 0: 52 return "(char)" + str(ord(ctypes.c_char(chr(self.data % 256)).value)) 53 if self.info_bits == 4: 54 return "(short)" + str(ctypes.c_short(self.data % (256 * 256)).value) 55 if self.info_bits == 8: 56 return "(int)" + str( 57 ctypes.c_int(self.data % (256 * 256 * 256 * 256)).value 58 ) 59 if self.info_bits == 12: 60 return "(long)" + str(ctypes.c_long(self.data).value) 61 else: 62 return ( 63 "unexpected value:(info=" 64 + str(self.info_bits) 65 + ", value = " 66 + str(self.data) 67 + ")" 68 ) 69 70 71class NSUntaggedNumber_SummaryProvider: 72 def adjust_for_architecture(self): 73 pass 74 75 def __init__(self, valobj, params): 76 logger = lldb.formatters.Logger.Logger() 77 self.valobj = valobj 78 self.sys_params = params 79 if not (self.sys_params.types_cache.char): 80 self.sys_params.types_cache.char = self.valobj.GetType().GetBasicType( 81 lldb.eBasicTypeChar 82 ) 83 if not (self.sys_params.types_cache.short): 84 self.sys_params.types_cache.short = self.valobj.GetType().GetBasicType( 85 lldb.eBasicTypeShort 86 ) 87 if not (self.sys_params.types_cache.ushort): 88 self.sys_params.types_cache.ushort = self.valobj.GetType().GetBasicType( 89 lldb.eBasicTypeUnsignedShort 90 ) 91 if not (self.sys_params.types_cache.int): 92 self.sys_params.types_cache.int = self.valobj.GetType().GetBasicType( 93 lldb.eBasicTypeInt 94 ) 95 if not (self.sys_params.types_cache.long): 96 self.sys_params.types_cache.long = self.valobj.GetType().GetBasicType( 97 lldb.eBasicTypeLong 98 ) 99 if not (self.sys_params.types_cache.ulong): 100 self.sys_params.types_cache.ulong = self.valobj.GetType().GetBasicType( 101 lldb.eBasicTypeUnsignedLong 102 ) 103 if not (self.sys_params.types_cache.longlong): 104 self.sys_params.types_cache.longlong = self.valobj.GetType().GetBasicType( 105 lldb.eBasicTypeLongLong 106 ) 107 if not (self.sys_params.types_cache.ulonglong): 108 self.sys_params.types_cache.ulonglong = self.valobj.GetType().GetBasicType( 109 lldb.eBasicTypeUnsignedLongLong 110 ) 111 if not (self.sys_params.types_cache.float): 112 self.sys_params.types_cache.float = self.valobj.GetType().GetBasicType( 113 lldb.eBasicTypeFloat 114 ) 115 if not (self.sys_params.types_cache.double): 116 self.sys_params.types_cache.double = self.valobj.GetType().GetBasicType( 117 lldb.eBasicTypeDouble 118 ) 119 self.update() 120 121 def update(self): 122 logger = lldb.formatters.Logger.Logger() 123 self.adjust_for_architecture() 124 125 def value(self): 126 logger = lldb.formatters.Logger.Logger() 127 global statistics 128 # we need to skip the ISA, then the next byte tells us what to read 129 # we then skip one other full pointer worth of data and then fetch the contents 130 # if we are fetching an int64 value, one more pointer must be skipped 131 # to get at our data 132 data_type_vo = self.valobj.CreateChildAtOffset( 133 "dt", self.sys_params.pointer_size, self.sys_params.types_cache.char 134 ) 135 data_type = (data_type_vo.GetValueAsUnsigned(0) % 256) & 0x1F 136 data_offset = 2 * self.sys_params.pointer_size 137 if data_type == 0b00001: 138 data_vo = self.valobj.CreateChildAtOffset( 139 "data", data_offset, self.sys_params.types_cache.char 140 ) 141 statistics.metric_hit("code_notrun", self.valobj) 142 return "(char)" + str( 143 ord(ctypes.c_char(chr(data_vo.GetValueAsUnsigned(0))).value) 144 ) 145 elif data_type == 0b0010: 146 data_vo = self.valobj.CreateChildAtOffset( 147 "data", data_offset, self.sys_params.types_cache.short 148 ) 149 statistics.metric_hit("code_notrun", self.valobj) 150 return "(short)" + str( 151 ctypes.c_short(data_vo.GetValueAsUnsigned(0) % (256 * 256)).value 152 ) 153 # IF tagged pointers are possible on 32bit+v2 runtime 154 # (of which the only existing instance should be iOS) 155 # then values of this type might be tagged 156 elif data_type == 0b0011: 157 data_vo = self.valobj.CreateChildAtOffset( 158 "data", data_offset, self.sys_params.types_cache.int 159 ) 160 statistics.metric_hit("code_notrun", self.valobj) 161 return "(int)" + str( 162 ctypes.c_int( 163 data_vo.GetValueAsUnsigned(0) % (256 * 256 * 256 * 256) 164 ).value 165 ) 166 # apparently, on is_64_bit architectures, these are the only values that will ever 167 # be represented by a non tagged pointers 168 elif data_type == 0b10001: 169 data_offset = data_offset + 8 # 8 is needed even if we are on 32bit 170 data_vo = self.valobj.CreateChildAtOffset( 171 "data", data_offset, self.sys_params.types_cache.longlong 172 ) 173 statistics.metric_hit("code_notrun", self.valobj) 174 return "(long)" + str(ctypes.c_long(data_vo.GetValueAsUnsigned(0)).value) 175 elif data_type == 0b0100: 176 if self.sys_params.is_64_bit: 177 data_offset = data_offset + self.sys_params.pointer_size 178 data_vo = self.valobj.CreateChildAtOffset( 179 "data", data_offset, self.sys_params.types_cache.longlong 180 ) 181 statistics.metric_hit("code_notrun", self.valobj) 182 return "(long)" + str(ctypes.c_long(data_vo.GetValueAsUnsigned(0)).value) 183 elif data_type == 0b0101: 184 data_vo = self.valobj.CreateChildAtOffset( 185 "data", data_offset, self.sys_params.types_cache.longlong 186 ) 187 data_plain = int(str(data_vo.GetValueAsUnsigned(0) & 0x00000000FFFFFFFF)) 188 packed = struct.pack("I", data_plain) 189 data_float = struct.unpack("f", packed)[0] 190 statistics.metric_hit("code_notrun", self.valobj) 191 return "(float)" + str(data_float) 192 elif data_type == 0b0110: 193 data_vo = self.valobj.CreateChildAtOffset( 194 "data", data_offset, self.sys_params.types_cache.longlong 195 ) 196 data_plain = data_vo.GetValueAsUnsigned(0) 197 data_double = struct.unpack("d", struct.pack("Q", data_plain))[0] 198 statistics.metric_hit("code_notrun", self.valobj) 199 return "(double)" + str(data_double) 200 statistics.metric_hit( 201 "unknown_class", 202 str(valobj.GetName()) + " had unknown data_type " + str(data_type), 203 ) 204 return "unexpected: dt = " + str(data_type) 205 206 207class NSUnknownNumber_SummaryProvider: 208 def adjust_for_architecture(self): 209 pass 210 211 def __init__(self, valobj, params): 212 logger = lldb.formatters.Logger.Logger() 213 self.valobj = valobj 214 self.sys_params = params 215 self.update() 216 217 def update(self): 218 logger = lldb.formatters.Logger.Logger() 219 self.adjust_for_architecture() 220 221 def value(self): 222 logger = lldb.formatters.Logger.Logger() 223 stream = lldb.SBStream() 224 self.valobj.GetExpressionPath(stream) 225 expr = "(NSString*)[" + stream.GetData() + " stringValue]" 226 num_children_vo = self.valobj.CreateValueFromExpression("str", expr) 227 if num_children_vo.IsValid(): 228 return num_children_vo.GetSummary() 229 return "<variable is not NSNumber>" 230 231 232def GetSummary_Impl(valobj): 233 logger = lldb.formatters.Logger.Logger() 234 global statistics 235 ( 236 class_data, 237 wrapper, 238 ) = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection( 239 valobj, statistics 240 ) 241 if wrapper: 242 return wrapper 243 244 name_string = class_data.class_name() 245 logger >> "class name is: " + str(name_string) 246 247 if name_string == "NSNumber" or name_string == "__NSCFNumber": 248 if class_data.is_tagged(): 249 wrapper = NSTaggedNumber_SummaryProvider( 250 valobj, 251 class_data.info_bits(), 252 class_data.value(), 253 class_data.sys_params, 254 ) 255 statistics.metric_hit("code_notrun", valobj) 256 else: 257 # the wrapper might be unable to decipher what is into the NSNumber 258 # and then have to run code on it 259 wrapper = NSUntaggedNumber_SummaryProvider(valobj, class_data.sys_params) 260 else: 261 wrapper = NSUnknownNumber_SummaryProvider(valobj, class_data.sys_params) 262 statistics.metric_hit( 263 "unknown_class", valobj.GetName() + " seen as " + name_string 264 ) 265 return wrapper 266 267 268def NSNumber_SummaryProvider(valobj, dict): 269 logger = lldb.formatters.Logger.Logger() 270 provider = GetSummary_Impl(valobj) 271 if provider is not None: 272 if isinstance( 273 provider, lldb.runtime.objc.objc_runtime.SpecialSituation_Description 274 ): 275 return provider.message() 276 try: 277 summary = provider.value() 278 except Exception as foo: 279 print(foo) 280 # except: 281 summary = None 282 logger >> "got summary " + str(summary) 283 if summary is None: 284 summary = "<variable is not NSNumber>" 285 return str(summary) 286 return "Summary Unavailable" 287 288 289def __lldb_init_module(debugger, dict): 290 debugger.HandleCommand( 291 "type summary add -F NSNumber.NSNumber_SummaryProvider NSNumber" 292 ) 293 debugger.HandleCommand( 294 "type summary add -F NSNumber.NSNumber_SummaryProvider __NSCFBoolean" 295 ) 296 debugger.HandleCommand( 297 "type summary add -F NSNumber.NSNumber_SummaryProvider __NSCFNumber" 298 ) 299