476 lines · python
1"""2LLDB Formatters for LLVM data types.3 4Load into LLDB with 'command script import /path/to/lldbDataFormatters.py'5"""6 7from __future__ import annotations8 9import collections10import lldb11 12 13def __lldb_init_module(debugger, internal_dict):14 debugger.HandleCommand("type category define -e llvm -l c++")15 debugger.HandleCommand(16 "type synthetic add -w llvm "17 f"-l {__name__}.SmallVectorSynthProvider "18 '-x "^llvm::SmallVectorImpl<.+>$"'19 )20 debugger.HandleCommand(21 "type summary add -w llvm "22 '-e -s "size=${svar%#}" '23 '-x "^llvm::SmallVectorImpl<.+>$"'24 )25 debugger.HandleCommand(26 "type synthetic add -w llvm "27 f"-l {__name__}.SmallVectorSynthProvider "28 '-x "^llvm::SmallVector<.+,.+>$"'29 )30 debugger.HandleCommand(31 "type summary add -w llvm "32 '-e -s "size=${svar%#}" '33 '-x "^llvm::SmallVector<.+,.+>$"'34 )35 debugger.HandleCommand(36 "type synthetic add -w llvm "37 f"-l {__name__}.ArrayRefSynthProvider "38 '-x "^llvm::ArrayRef<.+>$"'39 )40 debugger.HandleCommand(41 "type summary add -w llvm "42 '-e -s "size=${svar%#}" '43 '-x "^llvm::ArrayRef<.+>$"'44 )45 debugger.HandleCommand(46 "type summary add -w llvm "47 f"-F {__name__}.SmallStringSummaryProvider "48 '-x "^llvm::SmallString<.+>$"'49 )50 debugger.HandleCommand(51 "type summary add -w llvm "52 f"-F {__name__}.StringRefSummaryProvider "53 "llvm::StringRef"54 )55 debugger.HandleCommand(56 "type summary add -w llvm "57 f"-F {__name__}.ConstStringSummaryProvider "58 "lldb_private::ConstString"59 )60 61 # The synthetic providers for PointerIntPair and PointerUnion are disabled62 # because of a few issues. One example is template arguments that are63 # non-pointer types that instead specialize PointerLikeTypeTraits.64 # debugger.HandleCommand(65 # "type synthetic add -w llvm "66 # f"-l {__name__}.PointerIntPairSynthProvider "67 # '-x "^llvm::PointerIntPair<.+>$"'68 # )69 # debugger.HandleCommand(70 # "type synthetic add -w llvm "71 # f"-l {__name__}.PointerUnionSynthProvider "72 # '-x "^llvm::PointerUnion<.+>$"'73 # )74 75 debugger.HandleCommand(76 "type summary add -w llvm "77 f"-e -F {__name__}.DenseMapSummary "78 '-x "^llvm::DenseMap<.+>$"'79 )80 debugger.HandleCommand(81 "type synthetic add -w llvm "82 f"-l {__name__}.DenseMapSynthetic "83 '-x "^llvm::DenseMap<.+>$"'84 )85 debugger.HandleCommand(86 "type synthetic add -w llvm "87 f"-l {__name__}.DenseSetSynthetic "88 '-x "^llvm::DenseSet<.+>$"'89 )90 91 debugger.HandleCommand(92 "type synthetic add -w llvm "93 f"-l {__name__}.ExpectedSynthetic "94 '-x "^llvm::Expected<.+>$"'95 )96 debugger.HandleCommand(97 "type summary add -w llvm "98 f"-F {__name__}.SmallBitVectorSummary "99 "llvm::SmallBitVector"100 )101 102 103# Pretty printer for llvm::SmallVector/llvm::SmallVectorImpl104class SmallVectorSynthProvider:105 def __init__(self, valobj, internal_dict):106 self.valobj = valobj107 self.update() # initialize this provider108 109 def num_children(self):110 return self.size.GetValueAsUnsigned(0)111 112 def get_child_index(self, name):113 try:114 return int(name.lstrip("[").rstrip("]"))115 except:116 return -1117 118 def get_child_at_index(self, index):119 # Do bounds checking.120 if index < 0:121 return None122 if index >= self.num_children():123 return None124 125 offset = index * self.type_size126 return self.begin.CreateChildAtOffset(127 "[" + str(index) + "]", offset, self.data_type128 )129 130 def update(self):131 self.begin = self.valobj.GetChildMemberWithName("BeginX")132 self.size = self.valobj.GetChildMemberWithName("Size")133 the_type = self.valobj.GetType()134 # If this is a reference type we have to dereference it to get to the135 # template parameter.136 if the_type.IsReferenceType():137 the_type = the_type.GetDereferencedType()138 139 if the_type.IsPointerType():140 the_type = the_type.GetPointeeType()141 142 self.data_type = the_type.GetTemplateArgumentType(0)143 self.type_size = self.data_type.GetByteSize()144 assert self.type_size != 0145 146 147class ArrayRefSynthProvider:148 """Provider for llvm::ArrayRef"""149 150 def __init__(self, valobj, internal_dict):151 self.valobj = valobj152 self.update() # initialize this provider153 154 def num_children(self):155 return self.length156 157 def get_child_index(self, name):158 try:159 return int(name.lstrip("[").rstrip("]"))160 except:161 return -1162 163 def get_child_at_index(self, index):164 if index < 0 or index >= self.num_children():165 return None166 offset = index * self.type_size167 return self.data.CreateChildAtOffset(168 "[" + str(index) + "]", offset, self.data_type169 )170 171 def update(self):172 self.data = self.valobj.GetChildMemberWithName("Data")173 length_obj = self.valobj.GetChildMemberWithName("Length")174 self.length = length_obj.GetValueAsUnsigned(0)175 self.data_type = self.data.GetType().GetPointeeType()176 self.type_size = self.data_type.GetByteSize()177 assert self.type_size != 0178 179 180def SmallStringSummaryProvider(valobj, internal_dict):181 # The underlying SmallVector base class is the first child.182 vector = valobj.GetChildAtIndex(0)183 num_elements = vector.GetNumChildren()184 res = '"'185 for i in range(num_elements):186 c = vector.GetChildAtIndex(i)187 if c:188 res += chr(c.GetValueAsUnsigned())189 res += '"'190 return res191 192 193def StringRefSummaryProvider(valobj, internal_dict):194 data_pointer = valobj.GetChildMemberWithName("Data")195 length = valobj.GetChildMemberWithName("Length").unsigned196 if data_pointer.unsigned == 0 or length == 0:197 return '""'198 199 data = data_pointer.deref200 # StringRef may be uninitialized with length exceeding available memory,201 # potentially causing bad_alloc exceptions. Limit the length to max string summary setting.202 limit_obj = valobj.target.debugger.GetSetting("target.max-string-summary-length")203 if limit_obj:204 length = min(length, limit_obj.GetUnsignedIntegerValue())205 # Get a char[N] type, from the underlying char type.206 array_type = data.type.GetArrayType(length)207 # Cast the char* string data to a char[N] array.208 char_array = data.Cast(array_type)209 # Use the builtin summary for its support of max-string-summary-length and210 # display of non-printable bytes.211 return char_array.summary212 213 214def ConstStringSummaryProvider(valobj, internal_dict):215 if valobj.GetNumChildren() == 1:216 return valobj.GetChildAtIndex(0).GetSummary()217 return ""218 219 220def get_expression_path(val):221 stream = lldb.SBStream()222 if not val.GetExpressionPath(stream):223 return None224 return stream.GetData()225 226 227class PointerIntPairSynthProvider:228 def __init__(self, valobj, internal_dict):229 self.valobj = valobj230 self.update()231 232 def num_children(self):233 return 2234 235 def get_child_index(self, name):236 if name == "Pointer":237 return 0238 if name == "Int":239 return 1240 return None241 242 def get_child_at_index(self, index):243 expr_path = get_expression_path(self.valobj)244 if index == 0:245 return self.valobj.CreateValueFromExpression(246 "Pointer", f"({self.pointer_ty.name}){expr_path}.getPointer()"247 )248 if index == 1:249 return self.valobj.CreateValueFromExpression(250 "Int", f"({self.int_ty.name}){expr_path}.getInt()"251 )252 return None253 254 def update(self):255 self.pointer_ty = self.valobj.GetType().GetTemplateArgumentType(0)256 self.int_ty = self.valobj.GetType().GetTemplateArgumentType(2)257 258 259def parse_template_parameters(typename):260 """261 LLDB doesn't support template parameter packs, so let's parse them manually.262 """263 result = []264 start = typename.find("<")265 end = typename.rfind(">")266 if start < 1 or end < 2 or end - start < 2:267 return result268 269 nesting_level = 0270 current_parameter_start = start + 1271 272 for i in range(start + 1, end + 1):273 c = typename[i]274 if c == "<":275 nesting_level += 1276 elif c == ">":277 nesting_level -= 1278 elif c == "," and nesting_level == 0:279 result.append(typename[current_parameter_start:i].strip())280 current_parameter_start = i + 1281 282 result.append(typename[current_parameter_start:i].strip())283 284 return result285 286 287class PointerUnionSynthProvider:288 def __init__(self, valobj, internal_dict):289 self.valobj = valobj290 self.update()291 292 def num_children(self):293 return 1294 295 def get_child_index(self, name):296 if name == "Ptr":297 return 0298 return None299 300 def get_child_at_index(self, index):301 if index != 0:302 return None303 ptr_type_name = self.template_args[self.active_type_tag]304 return self.valobj.CreateValueFromExpression(305 "Ptr", f"({ptr_type_name}){self.val_expr_path}.getPointer()"306 )307 308 def update(self):309 self.pointer_int_pair = self.valobj.GetChildMemberWithName("Val")310 self.val_expr_path = get_expression_path(311 self.valobj.GetChildMemberWithName("Val")312 )313 self.active_type_tag = self.valobj.CreateValueFromExpression(314 "", f"(int){self.val_expr_path}.getInt()"315 ).GetValueAsSigned()316 self.template_args = parse_template_parameters(self.valobj.GetType().name)317 318 319def DenseMapSummary(valobj: lldb.SBValue, _) -> str:320 raw_value = valobj.GetNonSyntheticValue()321 num_entries = raw_value.GetChildMemberWithName("NumEntries").unsigned322 num_tombstones = raw_value.GetChildMemberWithName("NumTombstones").unsigned323 324 summary = f"size={num_entries}"325 if num_tombstones == 1:326 # The heuristic to identify valid entries does not handle the case of a327 # single tombstone. The summary calls attention to this.328 summary = f"tombstones=1, {summary}"329 return summary330 331 332class DenseMapSynthetic:333 valobj: lldb.SBValue334 335 # The indexes into `Buckets` that contain valid map entries.336 child_buckets: list[int]337 338 def __init__(self, valobj: lldb.SBValue, _) -> None:339 self.valobj = valobj340 341 def num_children(self) -> int:342 return len(self.child_buckets)343 344 def get_child_at_index(self, child_index: int) -> lldb.SBValue:345 bucket_index = self.child_buckets[child_index]346 entry = self.valobj.GetValueForExpressionPath(f".Buckets[{bucket_index}]")347 348 # By default, DenseMap instances use DenseMapPair to hold key-value349 # entries. When the entry is a DenseMapPair, unwrap it to expose the350 # children as simple std::pair values.351 #352 # This entry type is customizable (a template parameter). For other353 # types, expose the entry type as is.354 if entry.type.name.startswith("llvm::detail::DenseMapPair<"):355 entry = entry.GetChildAtIndex(0)356 357 return entry.Clone(f"[{child_index}]")358 359 def update(self):360 self.child_buckets = []361 362 num_entries = self.valobj.GetChildMemberWithName("NumEntries").unsigned363 if num_entries == 0:364 return365 366 buckets = self.valobj.GetChildMemberWithName("Buckets")367 num_buckets = self.valobj.GetChildMemberWithName("NumBuckets").unsigned368 369 # Bucket entries contain one of the following:370 # 1. Valid key-value371 # 2. Empty key372 # 3. Tombstone key (a deleted entry)373 #374 # NumBuckets is always greater than NumEntries. The empty key, and375 # potentially the tombstone key, will occur multiple times. A key that376 # is repeated is either the empty key or the tombstone key.377 378 # For each key, collect a list of buckets it appears in.379 key_buckets: dict[str, list[int]] = collections.defaultdict(list)380 for index in range(num_buckets):381 bucket = buckets.GetValueForExpressionPath(f"[{index}]")382 key = bucket.GetChildAtIndex(0)383 key_buckets[str(key.data)].append(index)384 385 # Heuristic: This is not a multi-map, any repeated (non-unique) keys are386 # either the the empty key or the tombstone key. Populate child_buckets387 # with the indexes of entries containing unique keys.388 for indexes in key_buckets.values():389 if len(indexes) == 1:390 self.child_buckets.append(indexes[0])391 392 393class DenseSetSynthetic:394 valobj: lldb.SBValue395 map: lldb.SBValue396 397 def __init__(self, valobj: lldb.SBValue, _) -> None:398 self.valobj = valobj399 400 def num_children(self) -> int:401 return self.map.num_children402 403 def get_child_at_index(self, idx: int) -> lldb.SBValue:404 map_entry = self.map.child[idx]405 set_entry = map_entry.GetChildAtIndex(0)406 return set_entry.Clone(f"[{idx}]")407 408 def update(self):409 raw_map = self.valobj.GetChildMemberWithName("TheMap")410 self.map = raw_map.GetSyntheticValue()411 412 413class ExpectedSynthetic:414 # The llvm::Expected<T> value.415 expected: lldb.SBValue416 # The stored success value or error value.417 stored_value: lldb.SBValue418 419 def __init__(self, valobj: lldb.SBValue, _) -> None:420 self.expected = valobj421 422 def update(self) -> None:423 has_error = self.expected.GetChildMemberWithName("HasError").unsigned424 if not has_error:425 name = "value"426 member = "TStorage"427 else:428 name = "error"429 member = "ErrorStorage"430 # Anonymous union.431 union = self.expected.child[0]432 storage = union.GetChildMemberWithName(member)433 stored_type = storage.type.template_args[0]434 self.stored_value = storage.Cast(stored_type).Clone(name)435 436 def num_children(self) -> int:437 return 1438 439 def get_child_index(self, name: str) -> int:440 if name == self.stored_value.name:441 return 0442 # Allow dereferencing for values, not errors.443 if name == "$$dereference$$" and self.stored_value.name == "value":444 return 0445 return -1446 447 def get_child_at_index(self, idx: int) -> lldb.SBValue:448 if idx == 0:449 return self.stored_value450 return lldb.SBValue()451 452 453def SmallBitVectorSummary(valobj, _):454 underlyingValue = valobj.GetChildMemberWithName("X").unsigned455 numBaseBits = valobj.target.addr_size * 8456 smallNumRawBits = numBaseBits - 1457 smallNumSizeBits = None458 if numBaseBits == 32:459 smallNumSizeBits = 5460 elif numBaseBits == 64:461 smallNumSizeBits = 6462 else:463 smallNumSizeBits = smallNumRawBits464 smallNumDataBits = smallNumRawBits - smallNumSizeBits465 466 # If our underlying value is not small, print we can not dump large values.467 isSmallMask = 1468 if underlyingValue & isSmallMask == 0:469 return "<can not read large SmallBitVector>"470 471 smallRawBits = underlyingValue >> 1472 smallSize = smallRawBits >> smallNumDataBits473 bits = smallRawBits & ((1 << (smallSize + 1)) - 1)474 # format `bits` in binary (b), with 0 padding, of width `smallSize`, and left aligned (>)475 return f"[{bits:0>{smallSize}b}]"476