299 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# example summary provider for NSNumber9# the real summary is now C++ code built into LLDB10 11import lldb12import ctypes13import lldb.runtime.objc.objc_runtime14import lldb.formatters.metrics15import struct16import lldb.formatters.Logger17 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 not25# trying to provide anything but the port number of an NSNumber, so they need not26# obey the interface specification for synthetic children providers27 28 29class NSTaggedNumber_SummaryProvider:30 def adjust_for_architecture(self):31 pass32 33 def __init__(self, valobj, info_bits, data, params):34 logger = lldb.formatters.Logger.Logger()35 self.valobj = valobj36 self.sys_params = params37 self.info_bits = info_bits38 self.data = data39 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 API48 # only a bunch of these are actually used in the internal implementation49 # unfortunately, the original type information appears to be lost50 # so we try to at least recover the proper magnitude of the data51 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)).value58 )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 pass74 75 def __init__(self, valobj, params):76 logger = lldb.formatters.Logger.Logger()77 self.valobj = valobj78 self.sys_params = params79 if not (self.sys_params.types_cache.char):80 self.sys_params.types_cache.char = self.valobj.GetType().GetBasicType(81 lldb.eBasicTypeChar82 )83 if not (self.sys_params.types_cache.short):84 self.sys_params.types_cache.short = self.valobj.GetType().GetBasicType(85 lldb.eBasicTypeShort86 )87 if not (self.sys_params.types_cache.ushort):88 self.sys_params.types_cache.ushort = self.valobj.GetType().GetBasicType(89 lldb.eBasicTypeUnsignedShort90 )91 if not (self.sys_params.types_cache.int):92 self.sys_params.types_cache.int = self.valobj.GetType().GetBasicType(93 lldb.eBasicTypeInt94 )95 if not (self.sys_params.types_cache.long):96 self.sys_params.types_cache.long = self.valobj.GetType().GetBasicType(97 lldb.eBasicTypeLong98 )99 if not (self.sys_params.types_cache.ulong):100 self.sys_params.types_cache.ulong = self.valobj.GetType().GetBasicType(101 lldb.eBasicTypeUnsignedLong102 )103 if not (self.sys_params.types_cache.longlong):104 self.sys_params.types_cache.longlong = self.valobj.GetType().GetBasicType(105 lldb.eBasicTypeLongLong106 )107 if not (self.sys_params.types_cache.ulonglong):108 self.sys_params.types_cache.ulonglong = self.valobj.GetType().GetBasicType(109 lldb.eBasicTypeUnsignedLongLong110 )111 if not (self.sys_params.types_cache.float):112 self.sys_params.types_cache.float = self.valobj.GetType().GetBasicType(113 lldb.eBasicTypeFloat114 )115 if not (self.sys_params.types_cache.double):116 self.sys_params.types_cache.double = self.valobj.GetType().GetBasicType(117 lldb.eBasicTypeDouble118 )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 statistics128 # we need to skip the ISA, then the next byte tells us what to read129 # we then skip one other full pointer worth of data and then fetch the contents130 # if we are fetching an int64 value, one more pointer must be skipped131 # to get at our data132 data_type_vo = self.valobj.CreateChildAtOffset(133 "dt", self.sys_params.pointer_size, self.sys_params.types_cache.char134 )135 data_type = (data_type_vo.GetValueAsUnsigned(0) % 256) & 0x1F136 data_offset = 2 * self.sys_params.pointer_size137 if data_type == 0b00001:138 data_vo = self.valobj.CreateChildAtOffset(139 "data", data_offset, self.sys_params.types_cache.char140 )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.short148 )149 statistics.metric_hit("code_notrun", self.valobj)150 return "(short)" + str(151 ctypes.c_short(data_vo.GetValueAsUnsigned(0) % (256 * 256)).value152 )153 # IF tagged pointers are possible on 32bit+v2 runtime154 # (of which the only existing instance should be iOS)155 # then values of this type might be tagged156 elif data_type == 0b0011:157 data_vo = self.valobj.CreateChildAtOffset(158 "data", data_offset, self.sys_params.types_cache.int159 )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 ).value165 )166 # apparently, on is_64_bit architectures, these are the only values that will ever167 # be represented by a non tagged pointers168 elif data_type == 0b10001:169 data_offset = data_offset + 8 # 8 is needed even if we are on 32bit170 data_vo = self.valobj.CreateChildAtOffset(171 "data", data_offset, self.sys_params.types_cache.longlong172 )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_size178 data_vo = self.valobj.CreateChildAtOffset(179 "data", data_offset, self.sys_params.types_cache.longlong180 )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.longlong186 )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.longlong195 )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 pass210 211 def __init__(self, valobj, params):212 logger = lldb.formatters.Logger.Logger()213 self.valobj = valobj214 self.sys_params = params215 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 statistics235 (236 class_data,237 wrapper,238 ) = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection(239 valobj, statistics240 )241 if wrapper:242 return wrapper243 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 NSNumber258 # and then have to run code on it259 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_string264 )265 return wrapper266 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_Description274 ):275 return provider.message()276 try:277 summary = provider.value()278 except Exception as foo:279 print(foo)280 # except:281 summary = None282 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