373 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 synthetic children and summary provider for CFString (and related NSString class)9# the real code is part of the LLDB core10import lldb11import lldb.runtime.objc.objc_runtime12import lldb.formatters.Logger13 14 15def CFString_SummaryProvider(valobj, dict):16 logger = lldb.formatters.Logger.Logger()17 provider = CFStringSynthProvider(valobj, dict)18 if not provider.invalid:19 try:20 summary = provider.get_child_at_index(provider.get_child_index("content"))21 if isinstance(summary, lldb.SBValue):22 summary = summary.GetSummary()23 else:24 summary = '"' + summary + '"'25 except:26 summary = None27 if summary is None:28 summary = "<variable is not NSString>"29 return "@" + summary30 return ""31 32 33def CFAttributedString_SummaryProvider(valobj, dict):34 logger = lldb.formatters.Logger.Logger()35 offset = valobj.GetTarget().GetProcess().GetAddressByteSize()36 pointee = valobj.GetValueAsUnsigned(0)37 summary = "<variable is not NSAttributedString>"38 if pointee is not None and pointee != 0:39 pointee = pointee + offset40 child_ptr = valobj.CreateValueFromAddress(41 "string_ptr", pointee, valobj.GetType()42 )43 child = child_ptr.CreateValueFromAddress(44 "string_data", child_ptr.GetValueAsUnsigned(), valobj.GetType()45 ).AddressOf()46 provider = CFStringSynthProvider(child, dict)47 if not provider.invalid:48 try:49 summary = provider.get_child_at_index(50 provider.get_child_index("content")51 ).GetSummary()52 except:53 summary = "<variable is not NSAttributedString>"54 if summary is None:55 summary = "<variable is not NSAttributedString>"56 return "@" + summary57 58 59def __lldb_init_module(debugger, dict):60 debugger.HandleCommand(61 "type summary add -F CFString.CFString_SummaryProvider NSString CFStringRef CFMutableStringRef"62 )63 debugger.HandleCommand(64 "type summary add -F CFString.CFAttributedString_SummaryProvider NSAttributedString"65 )66 67 68class CFStringSynthProvider:69 def __init__(self, valobj, dict):70 logger = lldb.formatters.Logger.Logger()71 self.valobj = valobj72 self.update()73 74 # children other than "content" are for debugging only and must not be75 # used in production code76 def num_children(self):77 logger = lldb.formatters.Logger.Logger()78 if self.invalid:79 return 080 return 681 82 def read_unicode(self, pointer, max_len=2048):83 logger = lldb.formatters.Logger.Logger()84 process = self.valobj.GetTarget().GetProcess()85 error = lldb.SBError()86 pystr = ""87 # cannot do the read at once because the length value has88 # a weird encoding. better play it safe here89 while max_len > 0:90 content = process.ReadMemory(pointer, 2, error)91 new_bytes = bytearray(content)92 b0 = new_bytes[0]93 b1 = new_bytes[1]94 pointer = pointer + 295 if b0 == 0 and b1 == 0:96 break97 # rearrange bytes depending on endianness98 # (do we really need this or is Cocoa going to99 # use Windows-compatible little-endian even100 # if the target is big endian?)101 if self.is_little:102 value = b1 * 256 + b0103 else:104 value = b0 * 256 + b1105 pystr = pystr + chr(value)106 # read max_len unicode values, not max_len bytes107 max_len = max_len - 1108 return pystr109 110 # handle the special case strings111 # only use the custom code for the tested LP64 case112 def handle_special(self):113 logger = lldb.formatters.Logger.Logger()114 if not self.is_64_bit:115 # for 32bit targets, use safe ObjC code116 return self.handle_unicode_string_safe()117 offset = 12118 pointer = self.valobj.GetValueAsUnsigned(0) + offset119 pystr = self.read_unicode(pointer)120 return self.valobj.CreateValueFromExpression(121 "content", '(char*)"' + pystr.encode("utf-8") + '"'122 )123 124 # last resort call, use ObjC code to read; the final aim is to125 # be able to strip this call away entirely and only do the read126 # ourselves127 def handle_unicode_string_safe(self):128 return self.valobj.CreateValueFromExpression(129 "content", '(char*)"' + self.valobj.GetObjectDescription() + '"'130 )131 132 def handle_unicode_string(self):133 logger = lldb.formatters.Logger.Logger()134 # step 1: find offset135 if self.inline:136 pointer = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base()137 if not self.explicit:138 # untested, use the safe code path139 return self.handle_unicode_string_safe()140 else:141 # a full pointer is skipped here before getting to the live142 # data143 pointer = pointer + self.pointer_size144 else:145 pointer = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base()146 # read 8 bytes here and make an address out of them147 try:148 char_type = (149 self.valobj.GetType()150 .GetBasicType(lldb.eBasicTypeChar)151 .GetPointerType()152 )153 vopointer = self.valobj.CreateValueFromAddress(154 "dummy", pointer, char_type155 )156 pointer = vopointer.GetValueAsUnsigned(0)157 except:158 return self.valobj.CreateValueFromExpression(159 "content", '(char*)"@"invalid NSString""'160 )161 # step 2: read Unicode data at pointer162 pystr = self.read_unicode(pointer)163 # step 3: return it164 return pystr.encode("utf-8")165 166 def handle_inline_explicit(self):167 logger = lldb.formatters.Logger.Logger()168 offset = 3 * self.pointer_size169 offset = offset + self.valobj.GetValueAsUnsigned(0)170 return self.valobj.CreateValueFromExpression(171 "content", "(char*)(" + str(offset) + ")"172 )173 174 def handle_mutable_string(self):175 logger = lldb.formatters.Logger.Logger()176 offset = 2 * self.pointer_size177 data = self.valobj.CreateChildAtOffset(178 "content",179 offset,180 self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar).GetPointerType(),181 )182 data_value = data.GetValueAsUnsigned(0)183 if self.explicit and self.unicode:184 return self.read_unicode(data_value).encode("utf-8")185 else:186 data_value = data_value + 1187 return self.valobj.CreateValueFromExpression(188 "content", "(char*)(" + str(data_value) + ")"189 )190 191 def handle_UTF8_inline(self):192 logger = lldb.formatters.Logger.Logger()193 offset = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base()194 if not self.explicit:195 offset = offset + 1196 return self.valobj.CreateValueFromAddress(197 "content", offset, self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar)198 ).AddressOf()199 200 def handle_UTF8_not_inline(self):201 logger = lldb.formatters.Logger.Logger()202 offset = self.size_of_cfruntime_base()203 return self.valobj.CreateChildAtOffset(204 "content",205 offset,206 self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar).GetPointerType(),207 )208 209 def get_child_at_index(self, index):210 logger = lldb.formatters.Logger.Logger()211 logger >> "Querying for child [" + str(index) + "]"212 if index == 0:213 return self.valobj.CreateValueFromExpression(214 "mutable", str(int(self.mutable))215 )216 if index == 1:217 return self.valobj.CreateValueFromExpression(218 "inline", str(int(self.inline))219 )220 if index == 2:221 return self.valobj.CreateValueFromExpression(222 "explicit", str(int(self.explicit))223 )224 if index == 3:225 return self.valobj.CreateValueFromExpression(226 "unicode", str(int(self.unicode))227 )228 if index == 4:229 return self.valobj.CreateValueFromExpression(230 "special", str(int(self.special))231 )232 if index == 5:233 # we are handling the several possible combinations of flags.234 # for each known combination we have a function that knows how to235 # go fetch the data from memory instead of running code. if a string is not236 # correctly displayed, one should start by finding a combination of flags that237 # makes it different from these known cases, and provide a new reader function238 # if this is not possible, a new flag might have to be made up (like the "special" flag239 # below, which is not a real flag in CFString), or alternatively one might need to use240 # the ObjC runtime helper to detect the new class and deal with it accordingly241 # print 'mutable = ' + str(self.mutable)242 # print 'inline = ' + str(self.inline)243 # print 'explicit = ' + str(self.explicit)244 # print 'unicode = ' + str(self.unicode)245 # print 'special = ' + str(self.special)246 if self.mutable:247 return self.handle_mutable_string()248 elif (249 self.inline250 and self.explicit251 and not self.unicode252 and not self.special253 and not self.mutable254 ):255 return self.handle_inline_explicit()256 elif self.unicode:257 return self.handle_unicode_string()258 elif self.special:259 return self.handle_special()260 elif self.inline:261 return self.handle_UTF8_inline()262 else:263 return self.handle_UTF8_not_inline()264 265 def get_child_index(self, name):266 logger = lldb.formatters.Logger.Logger()267 logger >> "Querying for child ['" + str(name) + "']"268 if name == "content":269 return self.num_children() - 1270 if name == "mutable":271 return 0272 if name == "inline":273 return 1274 if name == "explicit":275 return 2276 if name == "unicode":277 return 3278 if name == "special":279 return 4280 281 # CFRuntimeBase is defined as having an additional282 # 4 bytes (padding?) on LP64 architectures283 # to get its size we add up sizeof(pointer)+4284 # and then add 4 more bytes if we are on a 64bit system285 def size_of_cfruntime_base(self):286 logger = lldb.formatters.Logger.Logger()287 return self.pointer_size + 4 + (4 if self.is_64_bit else 0)288 289 # the info bits are part of the CFRuntimeBase structure290 # to get at them we have to skip a uintptr_t and then get291 # at the least-significant byte of a 4 byte array. If we are292 # on big-endian this means going to byte 3, if we are on293 # little endian (OSX & iOS), this means reading byte 0294 def offset_of_info_bits(self):295 logger = lldb.formatters.Logger.Logger()296 offset = self.pointer_size297 if not self.is_little:298 offset = offset + 3299 return offset300 301 def read_info_bits(self):302 logger = lldb.formatters.Logger.Logger()303 cfinfo = self.valobj.CreateChildAtOffset(304 "cfinfo",305 self.offset_of_info_bits(),306 self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar),307 )308 cfinfo.SetFormat(11)309 info = cfinfo.GetValue()310 if info is not None:311 self.invalid = False312 return int(info, 0)313 else:314 self.invalid = True315 return None316 317 # calculating internal flag bits of the CFString object318 # this stuff is defined and discussed in CFString.c319 def is_mutable(self):320 logger = lldb.formatters.Logger.Logger()321 return (self.info_bits & 1) == 1322 323 def is_inline(self):324 logger = lldb.formatters.Logger.Logger()325 return (self.info_bits & 0x60) == 0326 327 # this flag's name is ambiguous, it turns out328 # we must skip a length byte to get at the data329 # when this flag is False330 def has_explicit_length(self):331 logger = lldb.formatters.Logger.Logger()332 return (self.info_bits & (1 | 4)) != 4333 334 # probably a subclass of NSString. obtained this from [str pathExtension]335 # here info_bits = 0 and Unicode data at the start of the padding word336 # in the long run using the isa value might be safer as a way to identify this337 # instead of reading the info_bits338 def is_special_case(self):339 logger = lldb.formatters.Logger.Logger()340 return self.info_bits == 0341 342 def is_unicode(self):343 logger = lldb.formatters.Logger.Logger()344 return (self.info_bits & 0x10) == 0x10345 346 # preparing ourselves to read into memory347 # by adjusting architecture-specific info348 def adjust_for_architecture(self):349 logger = lldb.formatters.Logger.Logger()350 self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()351 self.is_64_bit = self.pointer_size == 8352 self.is_little = (353 self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle354 )355 356 # reading info bits out of the CFString and computing357 # useful values to get at the real data358 def compute_flags(self):359 logger = lldb.formatters.Logger.Logger()360 self.info_bits = self.read_info_bits()361 if self.info_bits is None:362 return363 self.mutable = self.is_mutable()364 self.inline = self.is_inline()365 self.explicit = self.has_explicit_length()366 self.unicode = self.is_unicode()367 self.special = self.is_special_case()368 369 def update(self):370 logger = lldb.formatters.Logger.Logger()371 self.adjust_for_architecture()372 self.compute_flags()373