| """ | |
| LLDB Formatters for LLVM data types. | |
| Load into LLDB with 'command script import /path/to/lldbDataFormatters.py' | |
| """ | |
| from __future__ import annotations | |
| from typing import Literal, Optional | |
| import lldb | |
| def __lldb_init_module(debugger: lldb.SBDebugger, internal_dict) -> None: | |
| debugger.HandleCommand("type category define -e llvm -l c++") | |
| debugger.HandleCommand( | |
| "type synthetic add -w llvm " | |
| f"-l {__name__}.SmallVectorSynthProvider " | |
| '-x "^llvm::SmallVectorImpl<.+>$"' | |
| ) | |
| debugger.HandleCommand( | |
| "type summary add -w llvm " | |
| '-e -s "size=${svar%#}" ' | |
| '-x "^llvm::SmallVectorImpl<.+>$"' | |
| ) | |
| debugger.HandleCommand( | |
| "type synthetic add -w llvm " | |
| f"-l {__name__}.SmallVectorSynthProvider " | |
| '-x "^llvm::SmallVector<.+,.+>$"' | |
| ) | |
| debugger.HandleCommand( | |
| "type summary add -w llvm " | |
| '-e -s "size=${svar%#}" ' | |
| '-x "^llvm::SmallVector<.+,.+>$"' | |
| ) | |
| debugger.HandleCommand( | |
| "type synthetic add -w llvm " | |
| f"-l {__name__}.ArrayRefSynthProvider " | |
| '-x "^llvm::ArrayRef<.+>$"' | |
| ) | |
| debugger.HandleCommand( | |
| "type summary add -w llvm " | |
| '-e -s "size=${svar%#}" ' | |
| '-x "^llvm::ArrayRef<.+>$"' | |
| ) | |
| debugger.HandleCommand( | |
| "type summary add -w llvm " | |
| f"-F {__name__}.SmallStringSummaryProvider " | |
| '-x "^llvm::SmallString<.+>$"' | |
| ) | |
| debugger.HandleCommand( | |
| "type summary add -w llvm " | |
| f"-F {__name__}.StringRefSummaryProvider " | |
| "llvm::StringRef" | |
| ) | |
| debugger.HandleCommand( | |
| "type summary add -w llvm " | |
| f"-F {__name__}.ConstStringSummaryProvider " | |
| "lldb_private::ConstString" | |
| ) | |
| debugger.HandleCommand( | |
| "type synthetic add -w llvm " | |
| f"-l {__name__}.PointerIntPairSynthProvider " | |
| '-x "^llvm::PointerIntPair<.+>$"' | |
| ) | |
| debugger.HandleCommand( | |
| "type synthetic add -w llvm " | |
| f"-l {__name__}.PointerUnionSynthProvider " | |
| '-x "^llvm::PointerUnion<.+>$"' | |
| ) | |
| debugger.HandleCommand( | |
| "type summary add -w llvm " | |
| f"-e -F {__name__}.DenseMapSummary " | |
| '-x "^llvm::DenseMap<.+>$"' | |
| ) | |
| debugger.HandleCommand( | |
| "type synthetic add -w llvm " | |
| f"-l {__name__}.DenseMapSynthetic " | |
| '-x "^llvm::DenseMap<.+>$"' | |
| ) | |
| debugger.HandleCommand( | |
| "type synthetic add -w llvm " | |
| f"-l {__name__}.DenseSetSynthetic " | |
| '-x "^llvm::DenseSet<.+>$"' | |
| ) | |
| debugger.HandleCommand( | |
| "type synthetic add -w llvm " | |
| f"-l {__name__}.ExpectedSynthetic " | |
| '-x "^llvm::Expected<.+>$"' | |
| ) | |
| debugger.HandleCommand( | |
| "type summary add -w llvm " | |
| f"-F {__name__}.SmallBitVectorSummary " | |
| "llvm::SmallBitVector" | |
| ) | |
| # Pretty printer for llvm::SmallVector/llvm::SmallVectorImpl | |
| class SmallVectorSynthProvider: | |
| valobj: lldb.SBValue | |
| begin: lldb.SBValue | |
| size: lldb.SBValue | |
| data_type: lldb.SBType | |
| type_size: int | |
| def __init__(self, valobj, internal_dict) -> None: | |
| self.valobj = valobj | |
| self.update() # initialize this provider | |
| def num_children(self) -> int: | |
| return self.size.GetValueAsUnsigned(0) | |
| def get_child_index(self, name): | |
| try: | |
| return int(name.lstrip("[").rstrip("]")) | |
| except: | |
| return -1 | |
| def get_child_at_index(self, index) -> Optional[lldb.SBValue]: | |
| # Do bounds checking. | |
| if index < 0: | |
| return None | |
| if index >= self.num_children(): | |
| return None | |
| offset = index * self.type_size | |
| return self.begin.CreateChildAtOffset( | |
| "[" + str(index) + "]", offset, self.data_type | |
| ) | |
| def update(self): | |
| self.begin = self.valobj.GetChildMemberWithName("BeginX") | |
| self.size = self.valobj.GetChildMemberWithName("Size") | |
| the_type = self.valobj.GetType() | |
| # If this is a reference type we have to dereference it to get to the | |
| # template parameter. | |
| if the_type.IsReferenceType(): | |
| the_type = the_type.GetDereferencedType() | |
| if the_type.IsPointerType(): | |
| the_type = the_type.GetPointeeType() | |
| self.data_type = the_type.GetTemplateArgumentType(0) | |
| self.type_size = self.data_type.GetByteSize() | |
| assert self.type_size != 0 | |
| class ArrayRefSynthProvider: | |
| """Provider for llvm::ArrayRef""" | |
| valobj: lldb.SBValue | |
| data: lldb.SBValue | |
| length: int | |
| data_type: lldb.SBType | |
| type_size: int | |
| def __init__(self, valobj: lldb.SBValue, internal_dict) -> None: | |
| self.valobj = valobj | |
| self.update() # initialize this provider | |
| def num_children(self) -> int: | |
| return self.length | |
| def get_child_index(self, name): | |
| try: | |
| return int(name.lstrip("[").rstrip("]")) | |
| except: | |
| return -1 | |
| def get_child_at_index(self, index) -> Optional[lldb.SBValue]: | |
| if index < 0 or index >= self.num_children(): | |
| return None | |
| offset = index * self.type_size | |
| return self.data.CreateChildAtOffset( | |
| "[" + str(index) + "]", offset, self.data_type | |
| ) | |
| def update(self): | |
| self.data = self.valobj.GetChildMemberWithName("Data") | |
| length_obj = self.valobj.GetChildMemberWithName("Length") | |
| self.length = length_obj.GetValueAsUnsigned(0) | |
| self.data_type = self.data.GetType().GetPointeeType() | |
| self.type_size = self.data_type.GetByteSize() | |
| assert self.type_size != 0 | |
| def SmallStringSummaryProvider(valobj: lldb.SBValue, internal_dict) -> str: | |
| # The underlying SmallVector base class is the first child. | |
| vector = valobj.GetChildAtIndex(0) | |
| num_elements = vector.GetNumChildren() | |
| res = '"' | |
| for i in range(num_elements): | |
| c = vector.GetChildAtIndex(i) | |
| if c: | |
| res += chr(c.GetValueAsUnsigned()) | |
| res += '"' | |
| return res | |
| def StringRefSummaryProvider(valobj: lldb.SBValue, internal_dict) -> str: | |
| data_pointer = valobj.GetChildMemberWithName("Data") | |
| length = valobj.GetChildMemberWithName("Length").unsigned | |
| if data_pointer.unsigned == 0 or length == 0: | |
| return '""' | |
| data = data_pointer.deref | |
| # StringRef may be uninitialized with length exceeding available memory, | |
| # potentially causing bad_alloc exceptions. Limit the length to max string summary setting. | |
| limit_obj = valobj.target.debugger.GetSetting("target.max-string-summary-length") | |
| if limit_obj: | |
| length = min(length, limit_obj.GetUnsignedIntegerValue()) | |
| # Get a char[N] type, from the underlying char type. | |
| array_type = data.type.GetArrayType(length) | |
| # Cast the char* string data to a char[N] array. | |
| char_array = data.Cast(array_type) | |
| # Use the builtin summary for its support of max-string-summary-length and | |
| # display of non-printable bytes. | |
| return char_array.summary | |
| def ConstStringSummaryProvider(valobj: lldb.SBValue, internal_dict) -> str: | |
| if valobj.GetNumChildren() == 1: | |
| return valobj.GetChildAtIndex(0).GetSummary() | |
| return "" | |
| class PointerIntPairSynthProvider: | |
| valobj: lldb.SBValue | |
| byteorder: Literal["big", "little"] | |
| ptr_size: int | |
| value: lldb.SBValue | |
| pointer_valobj: Optional[lldb.SBValue] | |
| int_valobj: Optional[lldb.SBValue] | |
| def __init__(self, valobj: lldb.SBValue, internal_dict) -> None: | |
| self.valobj = valobj | |
| self.update() | |
| def num_children(self) -> int: | |
| return 2 | |
| def get_child_index(self, name: str) -> int: | |
| if name == "Pointer": | |
| return 0 | |
| if name == "Int": | |
| return 1 | |
| return -1 | |
| def _get_raw_value(self) -> Optional[bytes]: | |
| data: lldb.SBData = self.value.GetData() | |
| error = lldb.SBError() | |
| raw_bytes = data.ReadRawData(error, 0, self.ptr_size) | |
| if error.Fail(): | |
| return None | |
| return raw_bytes | |
| def _get_pointer( | |
| self, pointer_bit_mask: int, pointer_ty: lldb.SBType | |
| ) -> Optional[lldb.SBValue]: | |
| raw_bytes = self._get_raw_value() | |
| if raw_bytes is None: | |
| return | |
| unmasked_pointer = int.from_bytes(raw_bytes, self.byteorder) | |
| pointer_value = unmasked_pointer & pointer_bit_mask | |
| data = lldb.SBData() | |
| data.SetDataFromUInt64Array([pointer_value]) | |
| return self.valobj.CreateValueFromData("Pointer", data, pointer_ty) | |
| def _get_int( | |
| self, int_shift: int, int_mask: int, int_ty: lldb.SBType | |
| ) -> Optional[lldb.SBValue]: | |
| raw_bytes = self._get_raw_value() | |
| if raw_bytes is None: | |
| return | |
| unmasked_pointer = int.from_bytes(raw_bytes, self.byteorder) | |
| int_value = (unmasked_pointer >> int_shift) & int_mask | |
| data = lldb.SBData() | |
| data.SetDataFromUInt64Array([int_value]) | |
| return self.valobj.CreateValueFromData("Int", data, int_ty) | |
| def get_child_at_index(self, index) -> Optional[lldb.SBValue]: | |
| if index == 0: | |
| return self.pointer_valobj | |
| if index == 1: | |
| return self.int_valobj | |
| return None | |
| def update(self): | |
| self.byteorder = ( | |
| "big" | |
| if self.valobj.target.GetByteOrder() == lldb.eByteOrderBig | |
| else "little" | |
| ) | |
| self.ptr_size = self.valobj.target.GetAddressByteSize() | |
| self.value: lldb.SBValue = self.valobj.GetChildMemberWithName("Value") | |
| if not self.value: | |
| return | |
| valobj_type = self.valobj.GetType() | |
| pointer_ty: lldb.SBType = valobj_type.GetTemplateArgumentType(0) | |
| if not pointer_ty: | |
| return | |
| int_ty: lldb.SBType = valobj_type.GetTemplateArgumentType(2) | |
| if not int_ty: | |
| return | |
| pointer_info = valobj_type.GetTemplateArgumentType(4) | |
| if not pointer_info: | |
| return | |
| mask_and_shift_constants = pointer_info.FindDirectNestedType( | |
| "MaskAndShiftConstants" | |
| ).GetEnumMembers() | |
| # FIXME: SBAPI should provide a way to retrieve an enum member | |
| # by name. | |
| pointer_bit_mask: lldb.SBTypeEnumMember = ( | |
| mask_and_shift_constants.GetTypeEnumMemberAtIndex(0) | |
| ) | |
| if pointer_bit_mask.name != "PointerBitMask": | |
| return | |
| int_shift: lldb.SBTypeEnumMember = ( | |
| mask_and_shift_constants.GetTypeEnumMemberAtIndex(1) | |
| ) | |
| if int_shift.name != "IntShift": | |
| return | |
| int_mask: lldb.SBTypeEnumMember = ( | |
| mask_and_shift_constants.GetTypeEnumMemberAtIndex(2) | |
| ) | |
| if int_mask.name != "IntMask": | |
| return | |
| self.pointer_valobj = self._get_pointer( | |
| pointer_bit_mask.GetValueAsUnsigned(), pointer_ty | |
| ) | |
| self.int_valobj = self._get_int( | |
| int_shift.GetValueAsUnsigned(), int_mask.GetValueAsUnsigned(), int_ty | |
| ) | |
| class PointerUnionSynthProvider: | |
| valobj: lldb.SBValue | |
| pointer_valobj: lldb.SBValue | |
| def __init__(self, valobj: lldb.SBValue, internal_dict) -> None: | |
| self.valobj = valobj | |
| self.update() | |
| def num_children(self) -> int: | |
| return 1 | |
| def get_child_index(self, name: str) -> int: | |
| if name == "Pointer": | |
| return 0 | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[lldb.SBValue]: | |
| if index != 0: | |
| return None | |
| return self.pointer_valobj | |
| @staticmethod | |
| def _get_low_bits_for_type(ty: lldb.SBType) -> int: | |
| """Return NumLowBitsAvailable for a pointer type (from pointee byte alignment).""" | |
| pointee = ty.GetPointeeType() | |
| if pointee.IsValid(): | |
| align = pointee.GetByteAlign() | |
| return align.bit_length() - 1 if align > 0 else 0 | |
| return 0 | |
| def _make_pointer_value(self, name, pointer, ty): | |
| """Create an SBValue for a pointer, using proper byte order and address size.""" | |
| data = lldb.SBData.CreateDataFromUInt64Array( | |
| self.valobj.target.GetByteOrder(), | |
| self.valobj.process.GetAddressByteSize(), | |
| [pointer], | |
| ) | |
| return self.valobj.CreateValueFromData(name, data, ty) | |
| def update(self): | |
| self.pointer_valobj = None | |
| valobj_type = self.valobj.GetType() | |
| num_args = valobj_type.GetNumberOfTemplateArguments() | |
| if num_args == 0: | |
| return | |
| # Read the raw uintptr_t from PunnedPointer<void*>. | |
| val: lldb.SBValue = self.valobj.GetChildMemberWithName("Val") | |
| if not val: | |
| return | |
| byteorder = ( | |
| "big" | |
| if self.valobj.target.GetByteOrder() == lldb.eByteOrderBig | |
| else "little" | |
| ) | |
| raw_bytes = val.GetData().uint8s | |
| if not raw_bytes: | |
| return | |
| raw_value = int.from_bytes(raw_bytes, byteorder) | |
| # Compute tag from type alignments (fixed-width encoding). | |
| tag_bits = (num_args - 1).bit_length() | |
| min_low_bits = min( | |
| self._get_low_bits_for_type(valobj_type.GetTemplateArgumentType(i)) | |
| for i in range(num_args) | |
| ) | |
| if tag_bits > min_low_bits: | |
| return self._set_raw_pointer(raw_value, min_low_bits) | |
| tag_shift = min_low_bits - tag_bits | |
| tag_mask = (1 << tag_bits) - 1 | |
| active_tag = (raw_value >> tag_shift) & tag_mask | |
| if active_tag >= num_args: | |
| return self._set_raw_pointer(raw_value, min_low_bits) | |
| active_type: lldb.SBType = valobj_type.GetTemplateArgumentType(active_tag) | |
| if not active_type: | |
| return self._set_raw_pointer(raw_value, min_low_bits) | |
| # Clear the active type's low bits to recover the pointer. | |
| low_bits = self._get_low_bits_for_type(active_type) | |
| pointer = raw_value & ~((1 << low_bits) - 1) | |
| self.pointer_valobj = self._make_pointer_value("Pointer", pointer, active_type) | |
| def _set_raw_pointer(self, raw_value, min_low_bits): | |
| """Fallback: strip tag bits and show as void* when active type is unknown.""" | |
| pointer = raw_value & ~((1 << min_low_bits) - 1) | |
| void_ptr_ty = self.valobj.target.FindFirstType("void").GetPointerType() | |
| if void_ptr_ty.IsValid(): | |
| self.pointer_valobj = self._make_pointer_value( | |
| "Pointer", pointer, void_ptr_ty | |
| ) | |
| def DenseMapSummary(valobj: lldb.SBValue, _) -> str: | |
| raw_value = valobj.GetNonSyntheticValue() | |
| storage = raw_value.GetChildMemberWithName("Storage") | |
| num_entries = storage.GetChildMemberWithName("NumEntries").unsigned | |
| return f"size={num_entries}" | |
| class DenseMapSynthetic: | |
| valobj: lldb.SBValue | |
| storage: lldb.SBValue | |
| # The indexes into `Buckets` that contain valid map entries. | |
| child_buckets: list[int] | |
| def __init__(self, valobj: lldb.SBValue, _) -> None: | |
| self.valobj = valobj | |
| def num_children(self) -> int: | |
| return len(self.child_buckets) | |
| def get_child_at_index(self, child_index: int) -> lldb.SBValue: | |
| bucket_index = self.child_buckets[child_index] | |
| entry = self.storage.GetValueForExpressionPath(f".Buckets[{bucket_index}]") | |
| return entry.Clone(f"[{child_index}]") | |
| def update(self): | |
| self.child_buckets = [] | |
| self.storage = self.valobj.GetChildMemberWithName("Storage") | |
| num_entries = self.storage.GetChildMemberWithName("NumEntries").unsigned | |
| if num_entries == 0: | |
| return | |
| num_buckets = self.storage.GetChildMemberWithName("NumBuckets").unsigned | |
| used = self.storage.GetChildMemberWithName("Used") | |
| # Occupancy is tracked in a packed 1-bit-per-bucket "used" array of | |
| # uint32_t words. A bucket holds a valid entry iff its bit is set; | |
| # empty and erased buckets are clear. Read the whole array in one go | |
| # rather than fetching each word with a separate expression path. | |
| num_words = (num_buckets + 31) // 32 | |
| words = used.GetPointeeData(0, num_words).uint32s | |
| for index in range(num_buckets): | |
| if (words[index >> 5] >> (index & 31)) & 1: | |
| self.child_buckets.append(index) | |
| class DenseSetSynthetic: | |
| valobj: lldb.SBValue | |
| map: lldb.SBValue | |
| def __init__(self, valobj: lldb.SBValue, _) -> None: | |
| self.valobj = valobj | |
| def num_children(self) -> int: | |
| return self.map.num_children | |
| def get_child_at_index(self, idx: int) -> lldb.SBValue: | |
| map_entry = self.map.child[idx] | |
| set_entry = map_entry.GetChildAtIndex(0) | |
| return set_entry.Clone(f"[{idx}]") | |
| def update(self): | |
| raw_map = self.valobj.GetChildMemberWithName("TheMap") | |
| self.map = raw_map.GetSyntheticValue() | |
| class ExpectedSynthetic: | |
| # The llvm::Expected<T> value. | |
| expected: lldb.SBValue | |
| # The stored success value or error value. | |
| stored_value: lldb.SBValue | |
| def __init__(self, valobj: lldb.SBValue, _) -> None: | |
| self.expected = valobj | |
| def update(self) -> None: | |
| has_error = self.expected.GetChildMemberWithName("HasError").unsigned | |
| if not has_error: | |
| name = "value" | |
| member = "TStorage" | |
| else: | |
| name = "error" | |
| member = "ErrorStorage" | |
| # Anonymous union. | |
| union = self.expected.child[0] | |
| storage = union.GetChildMemberWithName(member) | |
| # For reference types, storage is std::reference_wrapper<T>, so we | |
| # unwrap to get T. For non-reference types, storage is T directly. | |
| # Use GetCanonicalType() to resolve the typedef to the underlying type. | |
| canonical_type_name = storage.type.GetCanonicalType().name | |
| if "reference_wrapper<" in canonical_type_name: | |
| # reference_wrapper<T> stores a T* pointer to the referenced value. | |
| # Get the first child (the pointer member) and dereference it. | |
| ptr_member = storage.GetChildAtIndex(0) | |
| if ptr_member and ptr_member.IsValid() and ptr_member.type.IsPointerType(): | |
| self.stored_value = ptr_member.Dereference().Clone(name) | |
| else: | |
| # Fallback: just use storage as-is. | |
| self.stored_value = storage.Clone(name) | |
| else: | |
| self.stored_value = storage.Clone(name) | |
| def num_children(self) -> int: | |
| return 1 | |
| def get_child_index(self, name: str) -> int: | |
| if name == self.stored_value.name: | |
| return 0 | |
| # Allow dereferencing for values, not errors. | |
| if name == "$$dereference$$" and self.stored_value.name == "value": | |
| return 0 | |
| return -1 | |
| def get_child_at_index(self, idx: int) -> lldb.SBValue: | |
| if idx == 0: | |
| return self.stored_value | |
| return lldb.SBValue() | |
| def SmallBitVectorSummary(valobj, _) -> str: | |
| underlyingValue = valobj.GetChildMemberWithName("X").unsigned | |
| numBaseBits = valobj.target.addr_size * 8 | |
| smallNumRawBits = numBaseBits - 1 | |
| smallNumSizeBits = None | |
| if numBaseBits == 32: | |
| smallNumSizeBits = 5 | |
| elif numBaseBits == 64: | |
| smallNumSizeBits = 6 | |
| else: | |
| smallNumSizeBits = smallNumRawBits | |
| smallNumDataBits = smallNumRawBits - smallNumSizeBits | |
| # If our underlying value is not small, print we can not dump large values. | |
| isSmallMask = 1 | |
| if underlyingValue & isSmallMask == 0: | |
| return "<can not read large SmallBitVector>" | |
| smallRawBits = underlyingValue >> 1 | |
| smallSize = smallRawBits >> smallNumDataBits | |
| bits = smallRawBits & ((1 << (smallSize + 1)) - 1) | |
| # format `bits` in binary (b), with 0 padding, of width `smallSize`, and left aligned (>) | |
| return f"[{bits:0>{smallSize}b}]" |