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 NSDate 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 struct 15import time 16import datetime 17import CFString 18import lldb.formatters.Logger 19 20statistics = lldb.formatters.metrics.Metrics() 21statistics.add_metric("invalid_isa") 22statistics.add_metric("invalid_pointer") 23statistics.add_metric("unknown_class") 24statistics.add_metric("code_notrun") 25 26# Python promises to start counting time at midnight on Jan 1st on the epoch year 27# hence, all we need to know is the epoch year 28python_epoch = time.gmtime(0).tm_year 29 30osx_epoch = datetime.date(2001, 1, 1).timetuple() 31 32 33def mkgmtime(t): 34 logger = lldb.formatters.Logger.Logger() 35 return time.mktime(t) - time.timezone 36 37 38osx_epoch = mkgmtime(osx_epoch) 39 40 41def osx_to_python_time(osx): 42 logger = lldb.formatters.Logger.Logger() 43 if python_epoch <= 2001: 44 return osx + osx_epoch 45 else: 46 return osx - osx_epoch 47 48 49# represent a struct_time as a string in the format used by Xcode 50 51 52def xcode_format_time(X): 53 logger = lldb.formatters.Logger.Logger() 54 return time.strftime("%Y-%m-%d %H:%M:%S %Z", X) 55 56 57# represent a count-since-epoch as a string in the format used by Xcode 58 59 60def xcode_format_count(X): 61 logger = lldb.formatters.Logger.Logger() 62 return xcode_format_time(time.localtime(X)) 63 64 65# despite the similary to synthetic children providers, these classes are not 66# trying to provide anything but the summary for NSDate, so they need not 67# obey the interface specification for synthetic children providers 68 69 70class NSTaggedDate_SummaryProvider: 71 def adjust_for_architecture(self): 72 pass 73 74 def __init__(self, valobj, info_bits, data, params): 75 logger = lldb.formatters.Logger.Logger() 76 self.valobj = valobj 77 self.sys_params = params 78 self.update() 79 # NSDate is not using its info_bits for info like NSNumber is 80 # so we need to regroup info_bits and data 81 self.data = (data << 8) | (info_bits << 4) 82 83 def update(self): 84 logger = lldb.formatters.Logger.Logger() 85 self.adjust_for_architecture() 86 87 def value(self): 88 logger = lldb.formatters.Logger.Logger() 89 # the value of the date-time object is wrapped into the pointer value 90 # unfortunately, it is made as a time-delta after Jan 1 2001 midnight GMT 91 # while all Python knows about is the "epoch", which is a platform-dependent 92 # year (1970 of *nix) whose Jan 1 at midnight is taken as reference 93 value_double = struct.unpack("d", struct.pack("Q", self.data))[0] 94 if value_double == -63114076800.0: 95 return "0001-12-30 00:00:00 +0000" 96 return xcode_format_count(osx_to_python_time(value_double)) 97 98 99class NSUntaggedDate_SummaryProvider: 100 def adjust_for_architecture(self): 101 pass 102 103 def __init__(self, valobj, params): 104 logger = lldb.formatters.Logger.Logger() 105 self.valobj = valobj 106 self.sys_params = params 107 if not (self.sys_params.types_cache.double): 108 self.sys_params.types_cache.double = self.valobj.GetType().GetBasicType( 109 lldb.eBasicTypeDouble 110 ) 111 self.update() 112 113 def update(self): 114 logger = lldb.formatters.Logger.Logger() 115 self.adjust_for_architecture() 116 117 def offset(self): 118 logger = lldb.formatters.Logger.Logger() 119 return self.sys_params.pointer_size 120 121 def value(self): 122 logger = lldb.formatters.Logger.Logger() 123 value = self.valobj.CreateChildAtOffset( 124 "value", self.offset(), self.sys_params.types_cache.double 125 ) 126 value_double = struct.unpack("d", struct.pack("Q", value.GetData().uint64[0]))[ 127 0 128 ] 129 if value_double == -63114076800.0: 130 return "0001-12-30 00:00:00 +0000" 131 return xcode_format_count(osx_to_python_time(value_double)) 132 133 134class NSCalendarDate_SummaryProvider: 135 def adjust_for_architecture(self): 136 pass 137 138 def __init__(self, valobj, params): 139 logger = lldb.formatters.Logger.Logger() 140 self.valobj = valobj 141 self.sys_params = params 142 if not (self.sys_params.types_cache.double): 143 self.sys_params.types_cache.double = self.valobj.GetType().GetBasicType( 144 lldb.eBasicTypeDouble 145 ) 146 self.update() 147 148 def update(self): 149 logger = lldb.formatters.Logger.Logger() 150 self.adjust_for_architecture() 151 152 def offset(self): 153 logger = lldb.formatters.Logger.Logger() 154 return 2 * self.sys_params.pointer_size 155 156 def value(self): 157 logger = lldb.formatters.Logger.Logger() 158 value = self.valobj.CreateChildAtOffset( 159 "value", self.offset(), self.sys_params.types_cache.double 160 ) 161 value_double = struct.unpack("d", struct.pack("Q", value.GetData().uint64[0]))[ 162 0 163 ] 164 return xcode_format_count(osx_to_python_time(value_double)) 165 166 167class NSTimeZoneClass_SummaryProvider: 168 def adjust_for_architecture(self): 169 pass 170 171 def __init__(self, valobj, params): 172 logger = lldb.formatters.Logger.Logger() 173 self.valobj = valobj 174 self.sys_params = params 175 if not (self.sys_params.types_cache.voidptr): 176 self.sys_params.types_cache.voidptr = ( 177 self.valobj.GetType().GetBasicType(lldb.eBasicTypeVoid).GetPointerType() 178 ) 179 self.update() 180 181 def update(self): 182 logger = lldb.formatters.Logger.Logger() 183 self.adjust_for_architecture() 184 185 def offset(self): 186 logger = lldb.formatters.Logger.Logger() 187 return self.sys_params.pointer_size 188 189 def timezone(self): 190 logger = lldb.formatters.Logger.Logger() 191 tz_string = self.valobj.CreateChildAtOffset( 192 "tz_name", self.offset(), self.sys_params.types_cache.voidptr 193 ) 194 return CFString.CFString_SummaryProvider(tz_string, None) 195 196 197class NSUnknownDate_SummaryProvider: 198 def adjust_for_architecture(self): 199 pass 200 201 def __init__(self, valobj): 202 logger = lldb.formatters.Logger.Logger() 203 self.valobj = valobj 204 self.update() 205 206 def update(self): 207 logger = lldb.formatters.Logger.Logger() 208 self.adjust_for_architecture() 209 210 def value(self): 211 logger = lldb.formatters.Logger.Logger() 212 stream = lldb.SBStream() 213 self.valobj.GetExpressionPath(stream) 214 expr = "(NSString*)[" + stream.GetData() + " description]" 215 num_children_vo = self.valobj.CreateValueFromExpression("str", expr) 216 if num_children_vo.IsValid(): 217 return num_children_vo.GetSummary() 218 return "<variable is not NSDate>" 219 220 221def GetSummary_Impl(valobj): 222 logger = lldb.formatters.Logger.Logger() 223 global statistics 224 ( 225 class_data, 226 wrapper, 227 ) = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection( 228 valobj, statistics 229 ) 230 if wrapper: 231 return wrapper 232 233 name_string = class_data.class_name() 234 logger >> "class name is: " + str(name_string) 235 236 if ( 237 name_string == "NSDate" 238 or name_string == "__NSDate" 239 or name_string == "__NSTaggedDate" 240 ): 241 if class_data.is_tagged(): 242 wrapper = NSTaggedDate_SummaryProvider( 243 valobj, 244 class_data.info_bits(), 245 class_data.value(), 246 class_data.sys_params, 247 ) 248 statistics.metric_hit("code_notrun", valobj) 249 else: 250 wrapper = NSUntaggedDate_SummaryProvider(valobj, class_data.sys_params) 251 statistics.metric_hit("code_notrun", valobj) 252 elif name_string == "NSCalendarDate": 253 wrapper = NSCalendarDate_SummaryProvider(valobj, class_data.sys_params) 254 statistics.metric_hit("code_notrun", valobj) 255 elif name_string == "__NSTimeZone": 256 wrapper = NSTimeZoneClass_SummaryProvider(valobj, class_data.sys_params) 257 statistics.metric_hit("code_notrun", valobj) 258 else: 259 wrapper = NSUnknownDate_SummaryProvider(valobj) 260 statistics.metric_hit( 261 "unknown_class", valobj.GetName() + " seen as " + name_string 262 ) 263 return wrapper 264 265 266def NSDate_SummaryProvider(valobj, dict): 267 logger = lldb.formatters.Logger.Logger() 268 provider = GetSummary_Impl(valobj) 269 if provider is not None: 270 if isinstance( 271 provider, lldb.runtime.objc.objc_runtime.SpecialSituation_Description 272 ): 273 return provider.message() 274 try: 275 summary = provider.value() 276 except: 277 summary = None 278 if summary is None: 279 summary = "<variable is not NSDate>" 280 return str(summary) 281 return "Summary Unavailable" 282 283 284def NSTimeZone_SummaryProvider(valobj, dict): 285 logger = lldb.formatters.Logger.Logger() 286 provider = GetSummary_Impl(valobj) 287 if provider is not None: 288 if isinstance( 289 provider, lldb.runtime.objc.objc_runtime.SpecialSituation_Description 290 ): 291 return provider.message() 292 try: 293 summary = provider.timezone() 294 except: 295 summary = None 296 logger >> "got summary " + str(summary) 297 if summary is None: 298 summary = "<variable is not NSTimeZone>" 299 return str(summary) 300 return "Summary Unavailable" 301 302 303def CFAbsoluteTime_SummaryProvider(valobj, dict): 304 logger = lldb.formatters.Logger.Logger() 305 try: 306 value_double = struct.unpack("d", struct.pack("Q", valobj.GetData().uint64[0]))[ 307 0 308 ] 309 return xcode_format_count(osx_to_python_time(value_double)) 310 except: 311 return "Summary Unavailable" 312 313 314def __lldb_init_module(debugger, dict): 315 debugger.HandleCommand("type summary add -F NSDate.NSDate_SummaryProvider NSDate") 316 debugger.HandleCommand( 317 "type summary add -F NSDate.CFAbsoluteTime_SummaryProvider CFAbsoluteTime" 318 ) 319 debugger.HandleCommand( 320 "type summary add -F NSDate.NSTimeZone_SummaryProvider NSTimeZone CFTimeZoneRef" 321 ) 322