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