Enrico Granata | 3f1052b | 2012-03-13 21:52:00 +0000 | [diff] [blame] | 1 | """ |
| 2 | LLDB AppKit formatters |
| 3 | |
Chandler Carruth | 2946cd7 | 2019-01-19 08:50:56 +0000 | [diff] [blame] | 4 | Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 5 | See https://llvm.org/LICENSE.txt for license information. |
| 6 | SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
Enrico Granata | 3f1052b | 2012-03-13 21:52:00 +0000 | [diff] [blame] | 7 | """ |
Enrico Granata | 7a20435 | 2012-09-04 19:18:17 +0000 | [diff] [blame] | 8 | # example synthetic children and summary provider for CFString (and related NSString class) |
| 9 | # the real code is part of the LLDB core |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 10 | import lldb |
Enrico Granata | 28399ad | 2012-04-25 01:39:27 +0000 | [diff] [blame] | 11 | import lldb.runtime.objc.objc_runtime |
| 12 | import lldb.formatters.Logger |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 13 | |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 14 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 15 | def 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( |
| 21 | provider.get_child_index("content")) |
| 22 | if isinstance(summary, lldb.SBValue): |
| 23 | summary = summary.GetSummary() |
| 24 | else: |
| 25 | summary = '"' + summary + '"' |
| 26 | except: |
| 27 | summary = None |
| 28 | if summary is None: |
| 29 | summary = '<variable is not NSString>' |
| 30 | return '@' + summary |
| 31 | return '' |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 32 | |
| 33 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 34 | def CFAttributedString_SummaryProvider(valobj, dict): |
| 35 | logger = lldb.formatters.Logger.Logger() |
| 36 | offset = valobj.GetTarget().GetProcess().GetAddressByteSize() |
| 37 | pointee = valobj.GetValueAsUnsigned(0) |
| 38 | summary = '<variable is not NSAttributedString>' |
| 39 | if pointee is not None and pointee != 0: |
| 40 | pointee = pointee + offset |
| 41 | child_ptr = valobj.CreateValueFromAddress( |
| 42 | "string_ptr", pointee, valobj.GetType()) |
| 43 | child = child_ptr.CreateValueFromAddress( |
| 44 | "string_data", |
| 45 | child_ptr.GetValueAsUnsigned(), |
| 46 | valobj.GetType()).AddressOf() |
| 47 | provider = CFStringSynthProvider(child, dict) |
| 48 | if not provider.invalid: |
| 49 | try: |
| 50 | summary = provider.get_child_at_index( |
| 51 | provider.get_child_index("content")).GetSummary() |
| 52 | except: |
| 53 | summary = '<variable is not NSAttributedString>' |
| 54 | if summary is None: |
| 55 | summary = '<variable is not NSAttributedString>' |
| 56 | return '@' + summary |
| 57 | |
| 58 | |
| 59 | def __lldb_init_module(debugger, dict): |
| 60 | debugger.HandleCommand( |
| 61 | "type summary add -F CFString.CFString_SummaryProvider NSString CFStringRef CFMutableStringRef") |
| 62 | debugger.HandleCommand( |
| 63 | "type summary add -F CFString.CFAttributedString_SummaryProvider NSAttributedString") |
| 64 | |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 65 | |
| 66 | class CFStringSynthProvider: |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 67 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 68 | def __init__(self, valobj, dict): |
| 69 | logger = lldb.formatters.Logger.Logger() |
| 70 | self.valobj = valobj |
| 71 | self.update() |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 72 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 73 | # children other than "content" are for debugging only and must not be |
| 74 | # used in production code |
| 75 | def num_children(self): |
| 76 | logger = lldb.formatters.Logger.Logger() |
| 77 | if self.invalid: |
| 78 | return 0 |
| 79 | return 6 |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 80 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 81 | def read_unicode(self, pointer, max_len=2048): |
| 82 | logger = lldb.formatters.Logger.Logger() |
| 83 | process = self.valobj.GetTarget().GetProcess() |
| 84 | error = lldb.SBError() |
| 85 | pystr = u'' |
| 86 | # cannot do the read at once because the length value has |
| 87 | # a weird encoding. better play it safe here |
| 88 | while max_len > 0: |
| 89 | content = process.ReadMemory(pointer, 2, error) |
| 90 | new_bytes = bytearray(content) |
| 91 | b0 = new_bytes[0] |
| 92 | b1 = new_bytes[1] |
| 93 | pointer = pointer + 2 |
| 94 | if b0 == 0 and b1 == 0: |
| 95 | break |
| 96 | # rearrange bytes depending on endianness |
| 97 | # (do we really need this or is Cocoa going to |
| 98 | # use Windows-compatible little-endian even |
| 99 | # if the target is big endian?) |
| 100 | if self.is_little: |
| 101 | value = b1 * 256 + b0 |
| 102 | else: |
| 103 | value = b0 * 256 + b1 |
| 104 | pystr = pystr + unichr(value) |
| 105 | # read max_len unicode values, not max_len bytes |
| 106 | max_len = max_len - 1 |
| 107 | return pystr |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 108 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 109 | # handle the special case strings |
| 110 | # only use the custom code for the tested LP64 case |
| 111 | def handle_special(self): |
| 112 | logger = lldb.formatters.Logger.Logger() |
| 113 | if not self.is_64_bit: |
| 114 | # for 32bit targets, use safe ObjC code |
| 115 | return self.handle_unicode_string_safe() |
| 116 | offset = 12 |
| 117 | pointer = self.valobj.GetValueAsUnsigned(0) + offset |
| 118 | pystr = self.read_unicode(pointer) |
| 119 | return self.valobj.CreateValueFromExpression( |
| 120 | "content", "(char*)\"" + pystr.encode('utf-8') + "\"") |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 121 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 122 | # last resort call, use ObjC code to read; the final aim is to |
| 123 | # be able to strip this call away entirely and only do the read |
| 124 | # ourselves |
| 125 | def handle_unicode_string_safe(self): |
| 126 | return self.valobj.CreateValueFromExpression( |
| 127 | "content", "(char*)\"" + self.valobj.GetObjectDescription() + "\"") |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 128 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 129 | def handle_unicode_string(self): |
| 130 | logger = lldb.formatters.Logger.Logger() |
| 131 | # step 1: find offset |
| 132 | if self.inline: |
| 133 | pointer = self.valobj.GetValueAsUnsigned( |
| 134 | 0) + self.size_of_cfruntime_base() |
| 135 | if not self.explicit: |
| 136 | # untested, use the safe code path |
| 137 | return self.handle_unicode_string_safe() |
| 138 | else: |
| 139 | # a full pointer is skipped here before getting to the live |
| 140 | # data |
| 141 | pointer = pointer + self.pointer_size |
| 142 | else: |
| 143 | pointer = self.valobj.GetValueAsUnsigned( |
| 144 | 0) + self.size_of_cfruntime_base() |
| 145 | # read 8 bytes here and make an address out of them |
| 146 | try: |
| 147 | char_type = self.valobj.GetType().GetBasicType( |
| 148 | lldb.eBasicTypeChar).GetPointerType() |
| 149 | vopointer = self.valobj.CreateValueFromAddress( |
| 150 | "dummy", pointer, char_type) |
| 151 | pointer = vopointer.GetValueAsUnsigned(0) |
| 152 | except: |
| 153 | return self.valobj.CreateValueFromExpression( |
| 154 | "content", '(char*)"@\"invalid NSString\""') |
| 155 | # step 2: read Unicode data at pointer |
| 156 | pystr = self.read_unicode(pointer) |
| 157 | # step 3: return it |
| 158 | return pystr.encode('utf-8') |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 159 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 160 | def handle_inline_explicit(self): |
| 161 | logger = lldb.formatters.Logger.Logger() |
| 162 | offset = 3 * self.pointer_size |
| 163 | offset = offset + self.valobj.GetValueAsUnsigned(0) |
| 164 | return self.valobj.CreateValueFromExpression( |
| 165 | "content", "(char*)(" + str(offset) + ")") |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 166 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 167 | def handle_mutable_string(self): |
| 168 | logger = lldb.formatters.Logger.Logger() |
| 169 | offset = 2 * self.pointer_size |
| 170 | data = self.valobj.CreateChildAtOffset( |
| 171 | "content", offset, self.valobj.GetType().GetBasicType( |
| 172 | lldb.eBasicTypeChar).GetPointerType()) |
| 173 | data_value = data.GetValueAsUnsigned(0) |
| 174 | if self.explicit and self.unicode: |
| 175 | return self.read_unicode(data_value).encode('utf-8') |
| 176 | else: |
| 177 | data_value = data_value + 1 |
| 178 | return self.valobj.CreateValueFromExpression( |
| 179 | "content", "(char*)(" + str(data_value) + ")") |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 180 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 181 | def handle_UTF8_inline(self): |
| 182 | logger = lldb.formatters.Logger.Logger() |
| 183 | offset = self.valobj.GetValueAsUnsigned( |
| 184 | 0) + self.size_of_cfruntime_base() |
| 185 | if not self.explicit: |
| 186 | offset = offset + 1 |
| 187 | return self.valobj.CreateValueFromAddress( |
| 188 | "content", offset, self.valobj.GetType().GetBasicType( |
| 189 | lldb.eBasicTypeChar)).AddressOf() |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 190 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 191 | def handle_UTF8_not_inline(self): |
| 192 | logger = lldb.formatters.Logger.Logger() |
| 193 | offset = self.size_of_cfruntime_base() |
| 194 | return self.valobj.CreateChildAtOffset( |
| 195 | "content", offset, self.valobj.GetType().GetBasicType( |
| 196 | lldb.eBasicTypeChar).GetPointerType()) |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 197 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 198 | def get_child_at_index(self, index): |
| 199 | logger = lldb.formatters.Logger.Logger() |
| 200 | logger >> "Querying for child [" + str(index) + "]" |
| 201 | if index == 0: |
| 202 | return self.valobj.CreateValueFromExpression( |
| 203 | "mutable", str(int(self.mutable))) |
| 204 | if index == 1: |
| 205 | return self.valobj.CreateValueFromExpression("inline", |
| 206 | str(int(self.inline))) |
| 207 | if index == 2: |
| 208 | return self.valobj.CreateValueFromExpression( |
| 209 | "explicit", str(int(self.explicit))) |
| 210 | if index == 3: |
| 211 | return self.valobj.CreateValueFromExpression( |
| 212 | "unicode", str(int(self.unicode))) |
| 213 | if index == 4: |
| 214 | return self.valobj.CreateValueFromExpression( |
| 215 | "special", str(int(self.special))) |
| 216 | if index == 5: |
| 217 | # we are handling the several possible combinations of flags. |
| 218 | # for each known combination we have a function that knows how to |
| 219 | # go fetch the data from memory instead of running code. if a string is not |
| 220 | # correctly displayed, one should start by finding a combination of flags that |
| 221 | # makes it different from these known cases, and provide a new reader function |
| 222 | # if this is not possible, a new flag might have to be made up (like the "special" flag |
| 223 | # below, which is not a real flag in CFString), or alternatively one might need to use |
| 224 | # the ObjC runtime helper to detect the new class and deal with it accordingly |
| 225 | # print 'mutable = ' + str(self.mutable) |
| 226 | # print 'inline = ' + str(self.inline) |
| 227 | # print 'explicit = ' + str(self.explicit) |
| 228 | # print 'unicode = ' + str(self.unicode) |
| 229 | # print 'special = ' + str(self.special) |
| 230 | if self.mutable: |
| 231 | return self.handle_mutable_string() |
| 232 | elif self.inline and self.explicit and \ |
| 233 | self.unicode == False and self.special == False and \ |
| 234 | self.mutable == False: |
| 235 | return self.handle_inline_explicit() |
| 236 | elif self.unicode: |
| 237 | return self.handle_unicode_string() |
| 238 | elif self.special: |
| 239 | return self.handle_special() |
| 240 | elif self.inline: |
| 241 | return self.handle_UTF8_inline() |
| 242 | else: |
| 243 | return self.handle_UTF8_not_inline() |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 244 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 245 | def get_child_index(self, name): |
| 246 | logger = lldb.formatters.Logger.Logger() |
| 247 | logger >> "Querying for child ['" + str(name) + "']" |
| 248 | if name == "content": |
| 249 | return self.num_children() - 1 |
| 250 | if name == "mutable": |
| 251 | return 0 |
| 252 | if name == "inline": |
| 253 | return 1 |
| 254 | if name == "explicit": |
| 255 | return 2 |
| 256 | if name == "unicode": |
| 257 | return 3 |
| 258 | if name == "special": |
| 259 | return 4 |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 260 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 261 | # CFRuntimeBase is defined as having an additional |
| 262 | # 4 bytes (padding?) on LP64 architectures |
| 263 | # to get its size we add up sizeof(pointer)+4 |
| 264 | # and then add 4 more bytes if we are on a 64bit system |
| 265 | def size_of_cfruntime_base(self): |
| 266 | logger = lldb.formatters.Logger.Logger() |
| 267 | return self.pointer_size + 4 + (4 if self.is_64_bit else 0) |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 268 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 269 | # the info bits are part of the CFRuntimeBase structure |
| 270 | # to get at them we have to skip a uintptr_t and then get |
| 271 | # at the least-significant byte of a 4 byte array. If we are |
| 272 | # on big-endian this means going to byte 3, if we are on |
| 273 | # little endian (OSX & iOS), this means reading byte 0 |
| 274 | def offset_of_info_bits(self): |
| 275 | logger = lldb.formatters.Logger.Logger() |
| 276 | offset = self.pointer_size |
| 277 | if not self.is_little: |
| 278 | offset = offset + 3 |
| 279 | return offset |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 280 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 281 | def read_info_bits(self): |
| 282 | logger = lldb.formatters.Logger.Logger() |
| 283 | cfinfo = self.valobj.CreateChildAtOffset( |
| 284 | "cfinfo", |
| 285 | self.offset_of_info_bits(), |
| 286 | self.valobj.GetType().GetBasicType( |
| 287 | lldb.eBasicTypeChar)) |
| 288 | cfinfo.SetFormat(11) |
| 289 | info = cfinfo.GetValue() |
| 290 | if info is not None: |
| 291 | self.invalid = False |
| 292 | return int(info, 0) |
| 293 | else: |
| 294 | self.invalid = True |
| 295 | return None |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 296 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 297 | # calculating internal flag bits of the CFString object |
| 298 | # this stuff is defined and discussed in CFString.c |
| 299 | def is_mutable(self): |
| 300 | logger = lldb.formatters.Logger.Logger() |
| 301 | return (self.info_bits & 1) == 1 |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 302 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 303 | def is_inline(self): |
| 304 | logger = lldb.formatters.Logger.Logger() |
| 305 | return (self.info_bits & 0x60) == 0 |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 306 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 307 | # this flag's name is ambiguous, it turns out |
| 308 | # we must skip a length byte to get at the data |
| 309 | # when this flag is False |
| 310 | def has_explicit_length(self): |
| 311 | logger = lldb.formatters.Logger.Logger() |
| 312 | return (self.info_bits & (1 | 4)) != 4 |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 313 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 314 | # probably a subclass of NSString. obtained this from [str pathExtension] |
| 315 | # here info_bits = 0 and Unicode data at the start of the padding word |
| 316 | # in the long run using the isa value might be safer as a way to identify this |
| 317 | # instead of reading the info_bits |
| 318 | def is_special_case(self): |
| 319 | logger = lldb.formatters.Logger.Logger() |
| 320 | return self.info_bits == 0 |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 321 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 322 | def is_unicode(self): |
| 323 | logger = lldb.formatters.Logger.Logger() |
| 324 | return (self.info_bits & 0x10) == 0x10 |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 325 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 326 | # preparing ourselves to read into memory |
| 327 | # by adjusting architecture-specific info |
| 328 | def adjust_for_architecture(self): |
| 329 | logger = lldb.formatters.Logger.Logger() |
| 330 | self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize() |
| 331 | self.is_64_bit = self.pointer_size == 8 |
| 332 | self.is_little = self.valobj.GetTarget().GetProcess( |
| 333 | ).GetByteOrder() == lldb.eByteOrderLittle |
Enrico Granata | eb4a479 | 2012-02-23 23:10:27 +0000 | [diff] [blame] | 334 | |
Kate Stone | b9c1b51 | 2016-09-06 20:57:50 +0000 | [diff] [blame] | 335 | # reading info bits out of the CFString and computing |
| 336 | # useful values to get at the real data |
| 337 | def compute_flags(self): |
| 338 | logger = lldb.formatters.Logger.Logger() |
| 339 | self.info_bits = self.read_info_bits() |
| 340 | if self.info_bits is None: |
| 341 | return |
| 342 | self.mutable = self.is_mutable() |
| 343 | self.inline = self.is_inline() |
| 344 | self.explicit = self.has_explicit_length() |
| 345 | self.unicode = self.is_unicode() |
| 346 | self.special = self.is_special_case() |
| 347 | |
| 348 | def update(self): |
| 349 | logger = lldb.formatters.Logger.Logger() |
| 350 | self.adjust_for_architecture() |
| 351 | self.compute_flags() |