blob: 93f90dbf3a35a4d0d8be724084c86f71d6ee8164 [file] [log] [blame] [edit]
"""
LLDB Formatters for LLVM data types.
Load into LLDB with 'command script import /path/to/lldbDataFormatters.py'
"""
from __future__ import annotations
import collections
import lldb
def __lldb_init_module(debugger, internal_dict):
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:
def __init__(self, valobj, internal_dict):
self.valobj = valobj
self.update() # initialize this provider
def num_children(self):
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):
# 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"""
def __init__(self, valobj, internal_dict):
self.valobj = valobj
self.update() # initialize this provider
def num_children(self):
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):
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, internal_dict):
# 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, internal_dict):
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, internal_dict):
if valobj.GetNumChildren() == 1:
return valobj.GetChildAtIndex(0).GetSummary()
return ""
class PointerIntPairSynthProvider:
def __init__(self, valobj, internal_dict):
self.valobj = valobj
self.update()
def num_children(self):
return 2
def get_child_index(self, name):
if name == "Pointer":
return 0
if name == "Int":
return 1
return None
def _get_raw_value(self):
data: 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: SBType):
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: SBType):
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):
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: SBValue = self.valobj.GetChildMemberWithName("Value")
if not self.value:
return
valobj_type = self.valobj.GetType()
pointer_ty: SBType = valobj_type.GetTemplateArgumentType(0)
if not pointer_ty:
return
int_ty: 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: SBTypeEnumMember = (
mask_and_shift_constants.GetTypeEnumMemberAtIndex(0)
)
if pointer_bit_mask.name != "PointerBitMask":
return
int_shift: SBTypeEnumMember = mask_and_shift_constants.GetTypeEnumMemberAtIndex(
1
)
if int_shift.name != "IntShift":
return
int_mask: 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:
def __init__(self, valobj, internal_dict):
self.valobj = valobj
self.update()
def num_children(self):
return 1
def get_child_index(self, name):
if name == "Pointer":
return 0
return None
def get_child_at_index(self, index):
if index != 0:
return None
return self.pointer_valobj
def update(self):
pointer_int_pair: SBValue = self.valobj.GetChildMemberWithName(
"Val"
).GetSyntheticValue()
if not pointer_int_pair:
return
pointer: SBValue = pointer_int_pair.GetChildAtIndex(0)
if not pointer:
return
active_tag: SBValue = pointer_int_pair.GetChildAtIndex(1)
if not active_tag:
return
# Index into the parameter pack of llvm::PointerUnion to find the active type.
active_type: SBType = self.valobj.GetType().GetTemplateArgumentType(
active_tag.GetValueAsUnsigned()
)
if not active_type:
return
data = lldb.SBData()
data.SetDataFromUInt64Array([pointer.GetValueAsUnsigned()])
self.pointer_valobj = self.valobj.CreateValueFromData(
"Pointer", data, active_type
)
def DenseMapSummary(valobj: lldb.SBValue, _) -> str:
raw_value = valobj.GetNonSyntheticValue()
num_entries = raw_value.GetChildMemberWithName("NumEntries").unsigned
num_tombstones = raw_value.GetChildMemberWithName("NumTombstones").unsigned
summary = f"size={num_entries}"
if num_tombstones == 1:
# The heuristic to identify valid entries does not handle the case of a
# single tombstone. The summary calls attention to this.
summary = f"tombstones=1, {summary}"
return summary
class DenseMapSynthetic:
valobj: 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.valobj.GetValueForExpressionPath(f".Buckets[{bucket_index}]")
# By default, DenseMap instances use DenseMapPair to hold key-value
# entries. When the entry is a DenseMapPair, unwrap it to expose the
# children as simple std::pair values.
#
# This entry type is customizable (a template parameter). For other
# types, expose the entry type as is.
if entry.type.name.startswith("llvm::detail::DenseMapPair<"):
entry = entry.GetChildAtIndex(0)
return entry.Clone(f"[{child_index}]")
def update(self):
self.child_buckets = []
num_entries = self.valobj.GetChildMemberWithName("NumEntries").unsigned
if num_entries == 0:
return
buckets = self.valobj.GetChildMemberWithName("Buckets")
num_buckets = self.valobj.GetChildMemberWithName("NumBuckets").unsigned
# Bucket entries contain one of the following:
# 1. Valid key-value
# 2. Empty key
# 3. Tombstone key (a deleted entry)
#
# NumBuckets is always greater than NumEntries. The empty key, and
# potentially the tombstone key, will occur multiple times. A key that
# is repeated is either the empty key or the tombstone key.
# For each key, collect a list of buckets it appears in.
key_buckets: dict[str, list[int]] = collections.defaultdict(list)
for index in range(num_buckets):
bucket = buckets.GetValueForExpressionPath(f"[{index}]")
key = bucket.GetChildAtIndex(0)
key_buckets[str(key.data)].append(index)
# Heuristic: This is not a multi-map, any repeated (non-unique) keys are
# either the the empty key or the tombstone key. Populate child_buckets
# with the indexes of entries containing unique keys.
for indexes in key_buckets.values():
if len(indexes) == 1:
self.child_buckets.append(indexes[0])
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)
stored_type = storage.type.template_args[0]
self.stored_value = storage.Cast(stored_type).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, _):
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}]"