Add tools

This commit is contained in:
2024-12-16 20:57:17 -08:00
parent fbb975674f
commit 3207a2c7ee
3678 changed files with 2074383 additions and 0 deletions

View File

@ -0,0 +1,15 @@
# Add this folder to the python sys path; GDB Python-interpreter will now find modules in this path
import sys
from os import path
self_dir = path.dirname(path.realpath(__file__))
sys.path.append(self_dir)
# ruff: noqa: E402
import gdb
import gdb_lookup
# current_objfile can be none; even with `gdb foo-app`; sourcing this file after gdb init now works
try:
gdb_lookup.register_printers(gdb.current_objfile())
except Exception:
gdb_lookup.register_printers(gdb.selected_inferior().progspace)

View File

@ -0,0 +1,118 @@
import gdb
import gdb.printing
import re
from gdb_providers import *
from rust_types import *
_gdb_version_matched = re.search('([0-9]+)\\.([0-9]+)', gdb.VERSION)
gdb_version = [int(num) for num in _gdb_version_matched.groups()] if _gdb_version_matched else []
def register_printers(objfile):
objfile.pretty_printers.append(printer)
# BACKCOMPAT: rust 1.35
def is_hashbrown_hashmap(hash_map):
return len(hash_map.type.fields()) == 1
def classify_rust_type(type):
type_class = type.code
if type_class == gdb.TYPE_CODE_STRUCT:
return classify_struct(type.tag, type.fields())
if type_class == gdb.TYPE_CODE_UNION:
return classify_union(type.fields())
return RustType.OTHER
def check_enum_discriminant(valobj):
content = valobj[valobj.type.fields()[0]]
fields = content.type.fields()
if len(fields) > 1:
discriminant = int(content[fields[0]]) + 1
if discriminant > len(fields):
# invalid discriminant
return False
return True
# Helper for enum printing that checks the discriminant. Only used in
# older gdb.
def enum_provider(valobj):
if check_enum_discriminant(valobj):
return EnumProvider(valobj)
return None
# Helper to handle both old and new hash maps.
def hashmap_provider(valobj):
if is_hashbrown_hashmap(valobj):
return StdHashMapProvider(valobj)
else:
return StdOldHashMapProvider(valobj)
# Helper to handle both old and new hash sets.
def hashset_provider(valobj):
hash_map = valobj[valobj.type.fields()[0]]
if is_hashbrown_hashmap(hash_map):
return StdHashMapProvider(valobj, show_values=False)
else:
return StdOldHashMapProvider(hash_map, show_values=False)
class PrintByRustType(gdb.printing.SubPrettyPrinter):
def __init__(self, name, provider):
super(PrintByRustType, self).__init__(name)
self.provider = provider
def __call__(self, val):
if self.enabled:
return self.provider(val)
return None
class RustPrettyPrinter(gdb.printing.PrettyPrinter):
def __init__(self, name):
super(RustPrettyPrinter, self).__init__(name, [])
self.type_map = {}
def add(self, rust_type, provider):
# Just use the rust_type as the name.
printer = PrintByRustType(rust_type, provider)
self.type_map[rust_type] = printer
self.subprinters.append(printer)
def __call__(self, valobj):
rust_type = classify_rust_type(valobj.type)
if rust_type in self.type_map:
return self.type_map[rust_type](valobj)
return None
printer = RustPrettyPrinter("rust")
# use enum provider only for GDB <7.12
if gdb_version[0] < 7 or (gdb_version[0] == 7 and gdb_version[1] < 12):
printer.add(RustType.ENUM, enum_provider)
printer.add(RustType.STD_STRING, StdStringProvider)
printer.add(RustType.STD_OS_STRING, StdOsStringProvider)
printer.add(RustType.STD_STR, StdStrProvider)
printer.add(RustType.STD_SLICE, StdSliceProvider)
printer.add(RustType.STD_VEC, StdVecProvider)
printer.add(RustType.STD_VEC_DEQUE, StdVecDequeProvider)
printer.add(RustType.STD_BTREE_SET, StdBTreeSetProvider)
printer.add(RustType.STD_BTREE_MAP, StdBTreeMapProvider)
printer.add(RustType.STD_HASH_MAP, hashmap_provider)
printer.add(RustType.STD_HASH_SET, hashset_provider)
printer.add(RustType.STD_RC, StdRcProvider)
printer.add(RustType.STD_ARC, lambda valobj: StdRcProvider(valobj, is_atomic=True))
printer.add(RustType.STD_CELL, StdCellProvider)
printer.add(RustType.STD_REF, StdRefProvider)
printer.add(RustType.STD_REF_MUT, StdRefProvider)
printer.add(RustType.STD_REF_CELL, StdRefCellProvider)
printer.add(RustType.STD_NONZERO_NUMBER, StdNonZeroNumberProvider)

View File

@ -0,0 +1,458 @@
from sys import version_info
import gdb
if version_info[0] >= 3:
xrange = range
ZERO_FIELD = "__0"
FIRST_FIELD = "__1"
def unwrap_unique_or_non_null(unique_or_nonnull):
# BACKCOMPAT: rust 1.32
# https://github.com/rust-lang/rust/commit/7a0911528058e87d22ea305695f4047572c5e067
# BACKCOMPAT: rust 1.60
# https://github.com/rust-lang/rust/commit/2a91eeac1a2d27dd3de1bf55515d765da20fd86f
ptr = unique_or_nonnull["pointer"]
return ptr if ptr.type.code == gdb.TYPE_CODE_PTR else ptr[ptr.type.fields()[0]]
# GDB 14 has a tag class that indicates that extension methods are ok
# to call. Use of this tag only requires that printers hide local
# attributes and methods by prefixing them with "_".
if hasattr(gdb, 'ValuePrinter'):
printer_base = gdb.ValuePrinter
else:
printer_base = object
class EnumProvider(printer_base):
def __init__(self, valobj):
content = valobj[valobj.type.fields()[0]]
fields = content.type.fields()
self._empty = len(fields) == 0
if not self._empty:
if len(fields) == 1:
discriminant = 0
else:
discriminant = int(content[fields[0]]) + 1
self._active_variant = content[fields[discriminant]]
self._name = fields[discriminant].name
self._full_name = "{}::{}".format(valobj.type.name, self._name)
else:
self._full_name = valobj.type.name
def to_string(self):
return self._full_name
def children(self):
if not self._empty:
yield self._name, self._active_variant
class StdStringProvider(printer_base):
def __init__(self, valobj):
self._valobj = valobj
vec = valobj["vec"]
self._length = int(vec["len"])
self._data_ptr = unwrap_unique_or_non_null(vec["buf"]["ptr"])
def to_string(self):
return self._data_ptr.lazy_string(encoding="utf-8", length=self._length)
@staticmethod
def display_hint():
return "string"
class StdOsStringProvider(printer_base):
def __init__(self, valobj):
self._valobj = valobj
buf = self._valobj["inner"]["inner"]
is_windows = "Wtf8Buf" in buf.type.name
vec = buf[ZERO_FIELD] if is_windows else buf
self._length = int(vec["len"])
self._data_ptr = unwrap_unique_or_non_null(vec["buf"]["ptr"])
def to_string(self):
return self._data_ptr.lazy_string(encoding="utf-8", length=self._length)
def display_hint(self):
return "string"
class StdStrProvider(printer_base):
def __init__(self, valobj):
self._valobj = valobj
self._length = int(valobj["length"])
self._data_ptr = valobj["data_ptr"]
def to_string(self):
return self._data_ptr.lazy_string(encoding="utf-8", length=self._length)
@staticmethod
def display_hint():
return "string"
def _enumerate_array_elements(element_ptrs):
for (i, element_ptr) in enumerate(element_ptrs):
key = "[{}]".format(i)
element = element_ptr.dereference()
try:
# rust-lang/rust#64343: passing deref expr to `str` allows
# catching exception on garbage pointer
str(element)
except RuntimeError:
yield key, "inaccessible"
break
yield key, element
class StdSliceProvider(printer_base):
def __init__(self, valobj):
self._valobj = valobj
self._length = int(valobj["length"])
self._data_ptr = valobj["data_ptr"]
def to_string(self):
return "{}(size={})".format(self._valobj.type, self._length)
def children(self):
return _enumerate_array_elements(
self._data_ptr + index for index in xrange(self._length)
)
@staticmethod
def display_hint():
return "array"
class StdVecProvider(printer_base):
def __init__(self, valobj):
self._valobj = valobj
self._length = int(valobj["len"])
self._data_ptr = unwrap_unique_or_non_null(valobj["buf"]["ptr"])
def to_string(self):
return "Vec(size={})".format(self._length)
def children(self):
return _enumerate_array_elements(
self._data_ptr + index for index in xrange(self._length)
)
@staticmethod
def display_hint():
return "array"
class StdVecDequeProvider(printer_base):
def __init__(self, valobj):
self._valobj = valobj
self._head = int(valobj["head"])
self._size = int(valobj["len"])
# BACKCOMPAT: rust 1.75
cap = valobj["buf"]["cap"]
if cap.type.code != gdb.TYPE_CODE_INT:
cap = cap[ZERO_FIELD]
self._cap = int(cap)
self._data_ptr = unwrap_unique_or_non_null(valobj["buf"]["ptr"])
def to_string(self):
return "VecDeque(size={})".format(self._size)
def children(self):
return _enumerate_array_elements(
(self._data_ptr + ((self._head + index) % self._cap)) for index in xrange(self._size)
)
@staticmethod
def display_hint():
return "array"
class StdRcProvider(printer_base):
def __init__(self, valobj, is_atomic=False):
self._valobj = valobj
self._is_atomic = is_atomic
self._ptr = unwrap_unique_or_non_null(valobj["ptr"])
self._value = self._ptr["data" if is_atomic else "value"]
self._strong = self._ptr["strong"]["v" if is_atomic else "value"]["value"]
self._weak = self._ptr["weak"]["v" if is_atomic else "value"]["value"] - 1
def to_string(self):
if self._is_atomic:
return "Arc(strong={}, weak={})".format(int(self._strong), int(self._weak))
else:
return "Rc(strong={}, weak={})".format(int(self._strong), int(self._weak))
def children(self):
yield "value", self._value
yield "strong", self._strong
yield "weak", self._weak
class StdCellProvider(printer_base):
def __init__(self, valobj):
self._value = valobj["value"]["value"]
def to_string(self):
return "Cell"
def children(self):
yield "value", self._value
class StdRefProvider(printer_base):
def __init__(self, valobj):
self._value = valobj["value"].dereference()
self._borrow = valobj["borrow"]["borrow"]["value"]["value"]
def to_string(self):
borrow = int(self._borrow)
if borrow >= 0:
return "Ref(borrow={})".format(borrow)
else:
return "Ref(borrow_mut={})".format(-borrow)
def children(self):
yield "*value", self._value
yield "borrow", self._borrow
class StdRefCellProvider(printer_base):
def __init__(self, valobj):
self._value = valobj["value"]["value"]
self._borrow = valobj["borrow"]["value"]["value"]
def to_string(self):
borrow = int(self._borrow)
if borrow >= 0:
return "RefCell(borrow={})".format(borrow)
else:
return "RefCell(borrow_mut={})".format(-borrow)
def children(self):
yield "value", self._value
yield "borrow", self._borrow
class StdNonZeroNumberProvider(printer_base):
def __init__(self, valobj):
fields = valobj.type.fields()
assert len(fields) == 1
field = list(fields)[0]
inner_valobj = valobj[field.name]
inner_fields = inner_valobj.type.fields()
assert len(inner_fields) == 1
inner_field = list(inner_fields)[0]
self._value = str(inner_valobj[inner_field.name])
def to_string(self):
return self._value
# Yields children (in a provider's sense of the word) for a BTreeMap.
def children_of_btree_map(map):
# Yields each key/value pair in the node and in any child nodes.
def children_of_node(node_ptr, height):
def cast_to_internal(node):
internal_type_name = node.type.target().name.replace("LeafNode", "InternalNode", 1)
internal_type = gdb.lookup_type(internal_type_name)
return node.cast(internal_type.pointer())
if node_ptr.type.name.startswith("alloc::collections::btree::node::BoxedNode<"):
# BACKCOMPAT: rust 1.49
node_ptr = node_ptr["ptr"]
node_ptr = unwrap_unique_or_non_null(node_ptr)
leaf = node_ptr.dereference()
keys = leaf["keys"]
vals = leaf["vals"]
edges = cast_to_internal(node_ptr)["edges"] if height > 0 else None
length = leaf["len"]
for i in xrange(0, length + 1):
if height > 0:
child_ptr = edges[i]["value"]["value"]
for child in children_of_node(child_ptr, height - 1):
yield child
if i < length:
# Avoid "Cannot perform pointer math on incomplete type" on zero-sized arrays.
key_type_size = keys.type.sizeof
val_type_size = vals.type.sizeof
key = keys[i]["value"]["value"] if key_type_size > 0 else gdb.parse_and_eval("()")
val = vals[i]["value"]["value"] if val_type_size > 0 else gdb.parse_and_eval("()")
yield key, val
if map["length"] > 0:
root = map["root"]
if root.type.name.startswith("core::option::Option<"):
root = root.cast(gdb.lookup_type(root.type.name[21:-1]))
node_ptr = root["node"]
height = root["height"]
for child in children_of_node(node_ptr, height):
yield child
class StdBTreeSetProvider(printer_base):
def __init__(self, valobj):
self._valobj = valobj
def to_string(self):
return "BTreeSet(size={})".format(self._valobj["map"]["length"])
def children(self):
inner_map = self._valobj["map"]
for i, (child, _) in enumerate(children_of_btree_map(inner_map)):
yield "[{}]".format(i), child
@staticmethod
def display_hint():
return "array"
class StdBTreeMapProvider(printer_base):
def __init__(self, valobj):
self._valobj = valobj
def to_string(self):
return "BTreeMap(size={})".format(self._valobj["length"])
def children(self):
for i, (key, val) in enumerate(children_of_btree_map(self._valobj)):
yield "key{}".format(i), key
yield "val{}".format(i), val
@staticmethod
def display_hint():
return "map"
# BACKCOMPAT: rust 1.35
class StdOldHashMapProvider(printer_base):
def __init__(self, valobj, show_values=True):
self._valobj = valobj
self._show_values = show_values
self._table = self._valobj["table"]
self._size = int(self._table["size"])
self._hashes = self._table["hashes"]
self._hash_uint_type = self._hashes.type
self._hash_uint_size = self._hashes.type.sizeof
self._modulo = 2 ** self._hash_uint_size
self._data_ptr = self._hashes[ZERO_FIELD]["pointer"]
self._capacity_mask = int(self._table["capacity_mask"])
self._capacity = (self._capacity_mask + 1) % self._modulo
marker = self._table["marker"].type
self._pair_type = marker.template_argument(0)
self._pair_type_size = self._pair_type.sizeof
self._valid_indices = []
for idx in range(self._capacity):
data_ptr = self._data_ptr.cast(self._hash_uint_type.pointer())
address = data_ptr + idx
hash_uint = address.dereference()
hash_ptr = hash_uint[ZERO_FIELD]["pointer"]
if int(hash_ptr) != 0:
self._valid_indices.append(idx)
def to_string(self):
if self._show_values:
return "HashMap(size={})".format(self._size)
else:
return "HashSet(size={})".format(self._size)
def children(self):
start = int(self._data_ptr) & ~1
hashes = self._hash_uint_size * self._capacity
align = self._pair_type_size
len_rounded_up = (((((hashes + align) % self._modulo - 1) % self._modulo) & ~(
(align - 1) % self._modulo)) % self._modulo - hashes) % self._modulo
pairs_offset = hashes + len_rounded_up
pairs_start = gdb.Value(start + pairs_offset).cast(self._pair_type.pointer())
for index in range(self._size):
table_index = self._valid_indices[index]
idx = table_index & self._capacity_mask
element = (pairs_start + idx).dereference()
if self._show_values:
yield "key{}".format(index), element[ZERO_FIELD]
yield "val{}".format(index), element[FIRST_FIELD]
else:
yield "[{}]".format(index), element[ZERO_FIELD]
def display_hint(self):
return "map" if self._show_values else "array"
class StdHashMapProvider(printer_base):
def __init__(self, valobj, show_values=True):
self._valobj = valobj
self._show_values = show_values
table = self._table()
table_inner = table["table"]
capacity = int(table_inner["bucket_mask"]) + 1
ctrl = table_inner["ctrl"]["pointer"]
self._size = int(table_inner["items"])
self._pair_type = table.type.template_argument(0).strip_typedefs()
self._new_layout = not table_inner.type.has_key("data")
if self._new_layout:
self._data_ptr = ctrl.cast(self._pair_type.pointer())
else:
self._data_ptr = table_inner["data"]["pointer"]
self._valid_indices = []
for idx in range(capacity):
address = ctrl + idx
value = address.dereference()
is_presented = value & 128 == 0
if is_presented:
self._valid_indices.append(idx)
def _table(self):
if self._show_values:
hashbrown_hashmap = self._valobj["base"]
elif self._valobj.type.fields()[0].name == "map":
# BACKCOMPAT: rust 1.47
# HashSet wraps std::collections::HashMap, which wraps hashbrown::HashMap
hashbrown_hashmap = self._valobj["map"]["base"]
else:
# HashSet wraps hashbrown::HashSet, which wraps hashbrown::HashMap
hashbrown_hashmap = self._valobj["base"]["map"]
return hashbrown_hashmap["table"]
def to_string(self):
if self._show_values:
return "HashMap(size={})".format(self._size)
else:
return "HashSet(size={})".format(self._size)
def children(self):
pairs_start = self._data_ptr
for index in range(self._size):
idx = self._valid_indices[index]
if self._new_layout:
idx = -(idx + 1)
element = (pairs_start + idx).dereference()
if self._show_values:
yield "key{}".format(index), element[ZERO_FIELD]
yield "val{}".format(index), element[FIRST_FIELD]
else:
yield "[{}]".format(index), element[ZERO_FIELD]
def display_hint(self):
return "map" if self._show_values else "array"

View File

@ -0,0 +1,22 @@
type synthetic add -l lldb_lookup.synthetic_lookup -x ".*" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(alloc::([a-z_]+::)+)String$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^&(mut )?str$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^&(mut )?\\[.+\\]$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(std::ffi::([a-z_]+::)+)OsString$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(alloc::([a-z_]+::)+)Vec<.+>$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(alloc::([a-z_]+::)+)VecDeque<.+>$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(alloc::([a-z_]+::)+)BTreeSet<.+>$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(alloc::([a-z_]+::)+)BTreeMap<.+>$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(std::collections::([a-z_]+::)+)HashMap<.+>$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(std::collections::([a-z_]+::)+)HashSet<.+>$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(alloc::([a-z_]+::)+)Rc<.+>$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(alloc::([a-z_]+::)+)Arc<.+>$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(core::([a-z_]+::)+)Cell<.+>$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(core::([a-z_]+::)+)Ref<.+>$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(core::([a-z_]+::)+)RefMut<.+>$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(core::([a-z_]+::)+)RefCell<.+>$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(core::([a-z_]+::)+)NonZero<.+>$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^core::num::([a-z_]+::)*NonZero.+$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^(std::([a-z_]+::)+)PathBuf$" --category Rust
type summary add -F lldb_lookup.summary_lookup -e -x -h "^&(mut )?(std::([a-z_]+::)+)Path$" --category Rust
type category enable Rust

View File

@ -0,0 +1,124 @@
import lldb
from lldb_providers import *
from rust_types import RustType, classify_struct, classify_union
# BACKCOMPAT: rust 1.35
def is_hashbrown_hashmap(hash_map):
return len(hash_map.type.fields) == 1
def classify_rust_type(type):
type_class = type.GetTypeClass()
if type_class == lldb.eTypeClassStruct:
return classify_struct(type.name, type.fields)
if type_class == lldb.eTypeClassUnion:
return classify_union(type.fields)
return RustType.OTHER
def summary_lookup(valobj, dict):
# type: (SBValue, dict) -> str
"""Returns the summary provider for the given value"""
rust_type = classify_rust_type(valobj.GetType())
if rust_type == RustType.STD_STRING:
return StdStringSummaryProvider(valobj, dict)
if rust_type == RustType.STD_OS_STRING:
return StdOsStringSummaryProvider(valobj, dict)
if rust_type == RustType.STD_STR:
return StdStrSummaryProvider(valobj, dict)
if rust_type == RustType.STD_VEC:
return SizeSummaryProvider(valobj, dict)
if rust_type == RustType.STD_VEC_DEQUE:
return SizeSummaryProvider(valobj, dict)
if rust_type == RustType.STD_SLICE:
return SizeSummaryProvider(valobj, dict)
if rust_type == RustType.STD_HASH_MAP:
return SizeSummaryProvider(valobj, dict)
if rust_type == RustType.STD_HASH_SET:
return SizeSummaryProvider(valobj, dict)
if rust_type == RustType.STD_RC:
return StdRcSummaryProvider(valobj, dict)
if rust_type == RustType.STD_ARC:
return StdRcSummaryProvider(valobj, dict)
if rust_type == RustType.STD_REF:
return StdRefSummaryProvider(valobj, dict)
if rust_type == RustType.STD_REF_MUT:
return StdRefSummaryProvider(valobj, dict)
if rust_type == RustType.STD_REF_CELL:
return StdRefSummaryProvider(valobj, dict)
if rust_type == RustType.STD_NONZERO_NUMBER:
return StdNonZeroNumberSummaryProvider(valobj, dict)
if rust_type == RustType.STD_PATHBUF:
return StdPathBufSummaryProvider(valobj, dict)
if rust_type == RustType.STD_PATH:
return StdPathSummaryProvider(valobj, dict)
return ""
def synthetic_lookup(valobj, dict):
# type: (SBValue, dict) -> object
"""Returns the synthetic provider for the given value"""
rust_type = classify_rust_type(valobj.GetType())
if rust_type == RustType.STRUCT:
return StructSyntheticProvider(valobj, dict)
if rust_type == RustType.STRUCT_VARIANT:
return StructSyntheticProvider(valobj, dict, is_variant=True)
if rust_type == RustType.TUPLE:
return TupleSyntheticProvider(valobj, dict)
if rust_type == RustType.TUPLE_VARIANT:
return TupleSyntheticProvider(valobj, dict, is_variant=True)
if rust_type == RustType.EMPTY:
return EmptySyntheticProvider(valobj, dict)
if rust_type == RustType.REGULAR_ENUM:
discriminant = valobj.GetChildAtIndex(0).GetChildAtIndex(0).GetValueAsUnsigned()
return synthetic_lookup(valobj.GetChildAtIndex(discriminant), dict)
if rust_type == RustType.SINGLETON_ENUM:
return synthetic_lookup(valobj.GetChildAtIndex(0), dict)
if rust_type == RustType.ENUM:
return ClangEncodedEnumProvider(valobj, dict)
if rust_type == RustType.STD_VEC:
return StdVecSyntheticProvider(valobj, dict)
if rust_type == RustType.STD_VEC_DEQUE:
return StdVecDequeSyntheticProvider(valobj, dict)
if rust_type == RustType.STD_SLICE:
return StdSliceSyntheticProvider(valobj, dict)
if rust_type == RustType.STD_HASH_MAP:
if is_hashbrown_hashmap(valobj):
return StdHashMapSyntheticProvider(valobj, dict)
else:
return StdOldHashMapSyntheticProvider(valobj, dict)
if rust_type == RustType.STD_HASH_SET:
hash_map = valobj.GetChildAtIndex(0)
if is_hashbrown_hashmap(hash_map):
return StdHashMapSyntheticProvider(valobj, dict, show_values=False)
else:
return StdOldHashMapSyntheticProvider(hash_map, dict, show_values=False)
if rust_type == RustType.STD_RC:
return StdRcSyntheticProvider(valobj, dict)
if rust_type == RustType.STD_ARC:
return StdRcSyntheticProvider(valobj, dict, is_atomic=True)
if rust_type == RustType.STD_CELL:
return StdCellSyntheticProvider(valobj, dict)
if rust_type == RustType.STD_REF:
return StdRefSyntheticProvider(valobj, dict)
if rust_type == RustType.STD_REF_MUT:
return StdRefSyntheticProvider(valobj, dict)
if rust_type == RustType.STD_REF_CELL:
return StdRefSyntheticProvider(valobj, dict, is_cell=True)
return DefaultSyntheticProvider(valobj, dict)

View File

@ -0,0 +1,835 @@
import sys
from lldb import SBData, SBError, eBasicTypeLong, eBasicTypeUnsignedLong, \
eBasicTypeUnsignedChar
# from lldb.formatters import Logger
####################################################################################################
# This file contains two kinds of pretty-printers: summary and synthetic.
#
# Important classes from LLDB module:
# SBValue: the value of a variable, a register, or an expression
# SBType: the data type; each SBValue has a corresponding SBType
#
# Summary provider is a function with the type `(SBValue, dict) -> str`.
# The first parameter is the object encapsulating the actual variable being displayed;
# The second parameter is an internal support parameter used by LLDB, and you should not touch it.
#
# Synthetic children is the way to provide a children-based representation of the object's value.
# Synthetic provider is a class that implements the following interface:
#
# class SyntheticChildrenProvider:
# def __init__(self, SBValue, dict)
# def num_children(self)
# def get_child_index(self, str)
# def get_child_at_index(self, int)
# def update(self)
# def has_children(self)
# def get_value(self)
#
#
# You can find more information and examples here:
# 1. https://lldb.llvm.org/varformats.html
# 2. https://lldb.llvm.org/use/python-reference.html
# 3. https://lldb.llvm.org/python_reference/lldb.formatters.cpp.libcxx-pysrc.html
# 4. https://github.com/llvm-mirror/lldb/tree/master/examples/summaries/cocoa
####################################################################################################
PY3 = sys.version_info[0] == 3
class ValueBuilder:
def __init__(self, valobj):
# type: (SBValue) -> ValueBuilder
self.valobj = valobj
process = valobj.GetProcess()
self.endianness = process.GetByteOrder()
self.pointer_size = process.GetAddressByteSize()
def from_int(self, name, value):
# type: (str, int) -> SBValue
type = self.valobj.GetType().GetBasicType(eBasicTypeLong)
data = SBData.CreateDataFromSInt64Array(self.endianness, self.pointer_size, [value])
return self.valobj.CreateValueFromData(name, data, type)
def from_uint(self, name, value):
# type: (str, int) -> SBValue
type = self.valobj.GetType().GetBasicType(eBasicTypeUnsignedLong)
data = SBData.CreateDataFromUInt64Array(self.endianness, self.pointer_size, [value])
return self.valobj.CreateValueFromData(name, data, type)
def unwrap_unique_or_non_null(unique_or_nonnull):
# BACKCOMPAT: rust 1.32
# https://github.com/rust-lang/rust/commit/7a0911528058e87d22ea305695f4047572c5e067
# BACKCOMPAT: rust 1.60
# https://github.com/rust-lang/rust/commit/2a91eeac1a2d27dd3de1bf55515d765da20fd86f
ptr = unique_or_nonnull.GetChildMemberWithName("pointer")
return ptr if ptr.TypeIsPointerType() else ptr.GetChildAtIndex(0)
class DefaultSyntheticProvider:
def __init__(self, valobj, dict):
# type: (SBValue, dict) -> DefaultSyntheticProvider
# logger = Logger.Logger()
# logger >> "Default synthetic provider for " + str(valobj.GetName())
self.valobj = valobj
def num_children(self):
# type: () -> int
return self.valobj.GetNumChildren()
def get_child_index(self, name):
# type: (str) -> int
return self.valobj.GetIndexOfChildWithName(name)
def get_child_at_index(self, index):
# type: (int) -> SBValue
return self.valobj.GetChildAtIndex(index)
def update(self):
# type: () -> None
pass
def has_children(self):
# type: () -> bool
return self.valobj.MightHaveChildren()
class EmptySyntheticProvider:
def __init__(self, valobj, dict):
# type: (SBValue, dict) -> EmptySyntheticProvider
# logger = Logger.Logger()
# logger >> "[EmptySyntheticProvider] for " + str(valobj.GetName())
self.valobj = valobj
def num_children(self):
# type: () -> int
return 0
def get_child_index(self, name):
# type: (str) -> int
return None
def get_child_at_index(self, index):
# type: (int) -> SBValue
return None
def update(self):
# type: () -> None
pass
def has_children(self):
# type: () -> bool
return False
def SizeSummaryProvider(valobj, dict):
# type: (SBValue, dict) -> str
return 'size=' + str(valobj.GetNumChildren())
def vec_to_string(vec):
length = vec.GetNumChildren()
chars = [vec.GetChildAtIndex(i).GetValueAsUnsigned() for i in range(length)]
return bytes(chars).decode(errors='replace') if PY3 else "".join(chr(char) for char in chars)
def StdStringSummaryProvider(valobj, dict):
# type: (SBValue, dict) -> str
# logger = Logger.Logger()
# logger >> "[StdStringSummaryProvider] for " + str(valobj.GetName())
vec = valobj.GetChildAtIndex(0)
return '"%s"' % vec_to_string(vec)
def StdOsStringSummaryProvider(valobj, dict):
# type: (SBValue, dict) -> str
# logger = Logger.Logger()
# logger >> "[StdOsStringSummaryProvider] for " + str(valobj.GetName())
buf = valobj.GetChildAtIndex(0).GetChildAtIndex(0)
is_windows = "Wtf8Buf" in buf.type.name
vec = buf.GetChildAtIndex(0) if is_windows else buf
return '"%s"' % vec_to_string(vec)
def StdStrSummaryProvider(valobj, dict):
# type: (SBValue, dict) -> str
# logger = Logger.Logger()
# logger >> "[StdStrSummaryProvider] for " + str(valobj.GetName())
length = valobj.GetChildMemberWithName("length").GetValueAsUnsigned()
if length == 0:
return '""'
data_ptr = valobj.GetChildMemberWithName("data_ptr")
start = data_ptr.GetValueAsUnsigned()
error = SBError()
process = data_ptr.GetProcess()
data = process.ReadMemory(start, length, error)
data = data.decode(encoding='UTF-8') if PY3 else data
return '"%s"' % data
def StdPathBufSummaryProvider(valobj, dict):
# type: (SBValue, dict) -> str
# logger = Logger.Logger()
# logger >> "[StdPathBufSummaryProvider] for " + str(valobj.GetName())
return StdOsStringSummaryProvider(valobj.GetChildMemberWithName("inner"), dict)
def StdPathSummaryProvider(valobj, dict):
# type: (SBValue, dict) -> str
# logger = Logger.Logger()
# logger >> "[StdPathSummaryProvider] for " + str(valobj.GetName())
length = valobj.GetChildMemberWithName("length").GetValueAsUnsigned()
if length == 0:
return '""'
data_ptr = valobj.GetChildMemberWithName("data_ptr")
start = data_ptr.GetValueAsUnsigned()
error = SBError()
process = data_ptr.GetProcess()
data = process.ReadMemory(start, length, error)
if PY3:
try:
data = data.decode(encoding='UTF-8')
except UnicodeDecodeError:
return '%r' % data
return '"%s"' % data
class StructSyntheticProvider:
"""Pretty-printer for structs and struct enum variants"""
def __init__(self, valobj, dict, is_variant=False):
# type: (SBValue, dict, bool) -> StructSyntheticProvider
# logger = Logger.Logger()
self.valobj = valobj
self.is_variant = is_variant
self.type = valobj.GetType()
self.fields = {}
if is_variant:
self.fields_count = self.type.GetNumberOfFields() - 1
real_fields = self.type.fields[1:]
else:
self.fields_count = self.type.GetNumberOfFields()
real_fields = self.type.fields
for number, field in enumerate(real_fields):
self.fields[field.name] = number
def num_children(self):
# type: () -> int
return self.fields_count
def get_child_index(self, name):
# type: (str) -> int
return self.fields.get(name, -1)
def get_child_at_index(self, index):
# type: (int) -> SBValue
if self.is_variant:
field = self.type.GetFieldAtIndex(index + 1)
else:
field = self.type.GetFieldAtIndex(index)
return self.valobj.GetChildMemberWithName(field.name)
def update(self):
# type: () -> None
pass
def has_children(self):
# type: () -> bool
return True
class ClangEncodedEnumProvider:
"""Pretty-printer for 'clang-encoded' enums support implemented in LLDB"""
DISCRIMINANT_MEMBER_NAME = "$discr$"
VALUE_MEMBER_NAME = "value"
def __init__(self, valobj, dict):
self.valobj = valobj
self.update()
def has_children(self):
return True
def num_children(self):
if self.is_default:
return 1
return 2
def get_child_index(self, name):
if name == ClangEncodedEnumProvider.VALUE_MEMBER_NAME:
return 0
if name == ClangEncodedEnumProvider.DISCRIMINANT_MEMBER_NAME:
return 1
return -1
def get_child_at_index(self, index):
if index == 0:
return self.variant.GetChildMemberWithName(ClangEncodedEnumProvider.VALUE_MEMBER_NAME)
if index == 1:
return self.variant.GetChildMemberWithName(
ClangEncodedEnumProvider.DISCRIMINANT_MEMBER_NAME)
def update(self):
all_variants = self.valobj.GetChildAtIndex(0)
index = self._getCurrentVariantIndex(all_variants)
self.variant = all_variants.GetChildAtIndex(index)
self.is_default = self.variant.GetIndexOfChildWithName(
ClangEncodedEnumProvider.DISCRIMINANT_MEMBER_NAME) == -1
def _getCurrentVariantIndex(self, all_variants):
default_index = 0
for i in range(all_variants.GetNumChildren()):
variant = all_variants.GetChildAtIndex(i)
discr = variant.GetChildMemberWithName(
ClangEncodedEnumProvider.DISCRIMINANT_MEMBER_NAME)
if discr.IsValid():
discr_unsigned_value = discr.GetValueAsUnsigned()
if variant.GetName() == f"$variant${discr_unsigned_value}":
return i
else:
default_index = i
return default_index
class TupleSyntheticProvider:
"""Pretty-printer for tuples and tuple enum variants"""
def __init__(self, valobj, dict, is_variant=False):
# type: (SBValue, dict, bool) -> TupleSyntheticProvider
# logger = Logger.Logger()
self.valobj = valobj
self.is_variant = is_variant
self.type = valobj.GetType()
if is_variant:
self.size = self.type.GetNumberOfFields() - 1
else:
self.size = self.type.GetNumberOfFields()
def num_children(self):
# type: () -> int
return self.size
def get_child_index(self, name):
# type: (str) -> int
if name.isdigit():
return int(name)
else:
return -1
def get_child_at_index(self, index):
# type: (int) -> SBValue
if self.is_variant:
field = self.type.GetFieldAtIndex(index + 1)
else:
field = self.type.GetFieldAtIndex(index)
element = self.valobj.GetChildMemberWithName(field.name)
return self.valobj.CreateValueFromData(str(index), element.GetData(), element.GetType())
def update(self):
# type: () -> None
pass
def has_children(self):
# type: () -> bool
return True
class StdVecSyntheticProvider:
"""Pretty-printer for alloc::vec::Vec<T>
struct Vec<T> { buf: RawVec<T>, len: usize }
rust 1.75: struct RawVec<T> { ptr: Unique<T>, cap: usize, ... }
rust 1.76: struct RawVec<T> { ptr: Unique<T>, cap: Cap(usize), ... }
rust 1.31.1: struct Unique<T: ?Sized> { pointer: NonZero<*const T>, ... }
rust 1.33.0: struct Unique<T: ?Sized> { pointer: *const T, ... }
rust 1.62.0: struct Unique<T: ?Sized> { pointer: NonNull<T>, ... }
struct NonZero<T>(T)
struct NonNull<T> { pointer: *const T }
"""
def __init__(self, valobj, dict):
# type: (SBValue, dict) -> StdVecSyntheticProvider
# logger = Logger.Logger()
# logger >> "[StdVecSyntheticProvider] for " + str(valobj.GetName())
self.valobj = valobj
self.update()
def num_children(self):
# type: () -> int
return self.length
def get_child_index(self, name):
# type: (str) -> int
index = name.lstrip('[').rstrip(']')
if index.isdigit():
return int(index)
else:
return -1
def get_child_at_index(self, index):
# type: (int) -> SBValue
start = self.data_ptr.GetValueAsUnsigned()
address = start + index * self.element_type_size
element = self.data_ptr.CreateValueFromAddress("[%s]" % index, address, self.element_type)
return element
def update(self):
# type: () -> None
self.length = self.valobj.GetChildMemberWithName("len").GetValueAsUnsigned()
self.buf = self.valobj.GetChildMemberWithName("buf")
self.data_ptr = unwrap_unique_or_non_null(self.buf.GetChildMemberWithName("ptr"))
self.element_type = self.data_ptr.GetType().GetPointeeType()
self.element_type_size = self.element_type.GetByteSize()
def has_children(self):
# type: () -> bool
return True
class StdSliceSyntheticProvider:
def __init__(self, valobj, dict):
self.valobj = valobj
self.update()
def num_children(self):
# type: () -> int
return self.length
def get_child_index(self, name):
# type: (str) -> int
index = name.lstrip('[').rstrip(']')
if index.isdigit():
return int(index)
else:
return -1
def get_child_at_index(self, index):
# type: (int) -> SBValue
start = self.data_ptr.GetValueAsUnsigned()
address = start + index * self.element_type_size
element = self.data_ptr.CreateValueFromAddress("[%s]" % index, address, self.element_type)
return element
def update(self):
# type: () -> None
self.length = self.valobj.GetChildMemberWithName("length").GetValueAsUnsigned()
self.data_ptr = self.valobj.GetChildMemberWithName("data_ptr")
self.element_type = self.data_ptr.GetType().GetPointeeType()
self.element_type_size = self.element_type.GetByteSize()
def has_children(self):
# type: () -> bool
return True
class StdVecDequeSyntheticProvider:
"""Pretty-printer for alloc::collections::vec_deque::VecDeque<T>
struct VecDeque<T> { head: usize, len: usize, buf: RawVec<T> }
"""
def __init__(self, valobj, dict):
# type: (SBValue, dict) -> StdVecDequeSyntheticProvider
# logger = Logger.Logger()
# logger >> "[StdVecDequeSyntheticProvider] for " + str(valobj.GetName())
self.valobj = valobj
self.update()
def num_children(self):
# type: () -> int
return self.size
def get_child_index(self, name):
# type: (str) -> int
index = name.lstrip('[').rstrip(']')
if index.isdigit() and int(index) < self.size:
return int(index)
else:
return -1
def get_child_at_index(self, index):
# type: (int) -> SBValue
start = self.data_ptr.GetValueAsUnsigned()
address = start + ((index + self.head) % self.cap) * self.element_type_size
element = self.data_ptr.CreateValueFromAddress("[%s]" % index, address, self.element_type)
return element
def update(self):
# type: () -> None
self.head = self.valobj.GetChildMemberWithName("head").GetValueAsUnsigned()
self.size = self.valobj.GetChildMemberWithName("len").GetValueAsUnsigned()
self.buf = self.valobj.GetChildMemberWithName("buf")
cap = self.buf.GetChildMemberWithName("cap")
if cap.GetType().num_fields == 1:
cap = cap.GetChildAtIndex(0)
self.cap = cap.GetValueAsUnsigned()
self.data_ptr = unwrap_unique_or_non_null(self.buf.GetChildMemberWithName("ptr"))
self.element_type = self.data_ptr.GetType().GetPointeeType()
self.element_type_size = self.element_type.GetByteSize()
def has_children(self):
# type: () -> bool
return True
# BACKCOMPAT: rust 1.35
class StdOldHashMapSyntheticProvider:
"""Pretty-printer for std::collections::hash::map::HashMap<K, V, S>
struct HashMap<K, V, S> {..., table: RawTable<K, V>, ... }
struct RawTable<K, V> { capacity_mask: usize, size: usize, hashes: TaggedHashUintPtr, ... }
"""
def __init__(self, valobj, dict, show_values=True):
# type: (SBValue, dict, bool) -> StdOldHashMapSyntheticProvider
self.valobj = valobj
self.show_values = show_values
self.update()
def num_children(self):
# type: () -> int
return self.size
def get_child_index(self, name):
# type: (str) -> int
index = name.lstrip('[').rstrip(']')
if index.isdigit():
return int(index)
else:
return -1
def get_child_at_index(self, index):
# type: (int) -> SBValue
# logger = Logger.Logger()
start = self.data_ptr.GetValueAsUnsigned() & ~1
# See `libstd/collections/hash/table.rs:raw_bucket_at
hashes = self.hash_uint_size * self.capacity
align = self.pair_type_size
# See `libcore/alloc.rs:padding_needed_for`
len_rounded_up = (((((hashes + align) % self.modulo - 1) % self.modulo) & ~(
(align - 1) % self.modulo)) % self.modulo - hashes) % self.modulo
# len_rounded_up = ((hashes + align - 1) & ~(align - 1)) - hashes
pairs_offset = hashes + len_rounded_up
pairs_start = start + pairs_offset
table_index = self.valid_indices[index]
idx = table_index & self.capacity_mask
address = pairs_start + idx * self.pair_type_size
element = self.data_ptr.CreateValueFromAddress("[%s]" % index, address, self.pair_type)
if self.show_values:
return element
else:
key = element.GetChildAtIndex(0)
return self.valobj.CreateValueFromData("[%s]" % index, key.GetData(), key.GetType())
def update(self):
# type: () -> None
# logger = Logger.Logger()
self.table = self.valobj.GetChildMemberWithName("table") # type: SBValue
self.size = self.table.GetChildMemberWithName("size").GetValueAsUnsigned()
self.hashes = self.table.GetChildMemberWithName("hashes")
self.hash_uint_type = self.hashes.GetType()
self.hash_uint_size = self.hashes.GetType().GetByteSize()
self.modulo = 2 ** self.hash_uint_size
self.data_ptr = self.hashes.GetChildAtIndex(0).GetChildAtIndex(0)
self.capacity_mask = self.table.GetChildMemberWithName("capacity_mask").GetValueAsUnsigned()
self.capacity = (self.capacity_mask + 1) % self.modulo
marker = self.table.GetChildMemberWithName("marker").GetType() # type: SBType
self.pair_type = marker.template_args[0]
self.pair_type_size = self.pair_type.GetByteSize()
self.valid_indices = []
for idx in range(self.capacity):
address = self.data_ptr.GetValueAsUnsigned() + idx * self.hash_uint_size
hash_uint = self.data_ptr.CreateValueFromAddress("[%s]" % idx, address,
self.hash_uint_type)
hash_ptr = hash_uint.GetChildAtIndex(0).GetChildAtIndex(0)
if hash_ptr.GetValueAsUnsigned() != 0:
self.valid_indices.append(idx)
# logger >> "Valid indices: {}".format(str(self.valid_indices))
def has_children(self):
# type: () -> bool
return True
class StdHashMapSyntheticProvider:
"""Pretty-printer for hashbrown's HashMap"""
def __init__(self, valobj, dict, show_values=True):
# type: (SBValue, dict, bool) -> StdHashMapSyntheticProvider
self.valobj = valobj
self.show_values = show_values
self.update()
def num_children(self):
# type: () -> int
return self.size
def get_child_index(self, name):
# type: (str) -> int
index = name.lstrip('[').rstrip(']')
if index.isdigit():
return int(index)
else:
return -1
def get_child_at_index(self, index):
# type: (int) -> SBValue
pairs_start = self.data_ptr.GetValueAsUnsigned()
idx = self.valid_indices[index]
if self.new_layout:
idx = -(idx + 1)
address = pairs_start + idx * self.pair_type_size
element = self.data_ptr.CreateValueFromAddress("[%s]" % index, address, self.pair_type)
if self.show_values:
return element
else:
key = element.GetChildAtIndex(0)
return self.valobj.CreateValueFromData("[%s]" % index, key.GetData(), key.GetType())
def update(self):
# type: () -> None
table = self.table()
inner_table = table.GetChildMemberWithName("table")
capacity = inner_table.GetChildMemberWithName("bucket_mask").GetValueAsUnsigned() + 1
ctrl = inner_table.GetChildMemberWithName("ctrl").GetChildAtIndex(0)
self.size = inner_table.GetChildMemberWithName("items").GetValueAsUnsigned()
self.pair_type = table.type.template_args[0]
if self.pair_type.IsTypedefType():
self.pair_type = self.pair_type.GetTypedefedType()
self.pair_type_size = self.pair_type.GetByteSize()
self.new_layout = not inner_table.GetChildMemberWithName("data").IsValid()
if self.new_layout:
self.data_ptr = ctrl.Cast(self.pair_type.GetPointerType())
else:
self.data_ptr = inner_table.GetChildMemberWithName("data").GetChildAtIndex(0)
u8_type = self.valobj.GetTarget().GetBasicType(eBasicTypeUnsignedChar)
u8_type_size = self.valobj.GetTarget().GetBasicType(eBasicTypeUnsignedChar).GetByteSize()
self.valid_indices = []
for idx in range(capacity):
address = ctrl.GetValueAsUnsigned() + idx * u8_type_size
value = ctrl.CreateValueFromAddress("ctrl[%s]" % idx, address,
u8_type).GetValueAsUnsigned()
is_present = value & 128 == 0
if is_present:
self.valid_indices.append(idx)
def table(self):
# type: () -> SBValue
if self.show_values:
hashbrown_hashmap = self.valobj.GetChildMemberWithName("base")
else:
# BACKCOMPAT: rust 1.47
# HashSet wraps either std HashMap or hashbrown::HashSet, which both
# wrap hashbrown::HashMap, so either way we "unwrap" twice.
hashbrown_hashmap = self.valobj.GetChildAtIndex(0).GetChildAtIndex(0)
return hashbrown_hashmap.GetChildMemberWithName("table")
def has_children(self):
# type: () -> bool
return True
def StdRcSummaryProvider(valobj, dict):
# type: (SBValue, dict) -> str
strong = valobj.GetChildMemberWithName("strong").GetValueAsUnsigned()
weak = valobj.GetChildMemberWithName("weak").GetValueAsUnsigned()
return "strong={}, weak={}".format(strong, weak)
class StdRcSyntheticProvider:
"""Pretty-printer for alloc::rc::Rc<T> and alloc::sync::Arc<T>
struct Rc<T> { ptr: NonNull<RcBox<T>>, ... }
rust 1.31.1: struct NonNull<T> { pointer: NonZero<*const T> }
rust 1.33.0: struct NonNull<T> { pointer: *const T }
struct NonZero<T>(T)
struct RcBox<T> { strong: Cell<usize>, weak: Cell<usize>, value: T }
struct Cell<T> { value: UnsafeCell<T> }
struct UnsafeCell<T> { value: T }
struct Arc<T> { ptr: NonNull<ArcInner<T>>, ... }
struct ArcInner<T> { strong: atomic::AtomicUsize, weak: atomic::AtomicUsize, data: T }
struct AtomicUsize { v: UnsafeCell<usize> }
"""
def __init__(self, valobj, dict, is_atomic=False):
# type: (SBValue, dict, bool) -> StdRcSyntheticProvider
self.valobj = valobj
self.ptr = unwrap_unique_or_non_null(self.valobj.GetChildMemberWithName("ptr"))
self.value = self.ptr.GetChildMemberWithName("data" if is_atomic else "value")
self.strong = self.ptr.GetChildMemberWithName("strong").GetChildAtIndex(
0).GetChildMemberWithName("value")
self.weak = self.ptr.GetChildMemberWithName("weak").GetChildAtIndex(
0).GetChildMemberWithName("value")
self.value_builder = ValueBuilder(valobj)
self.update()
def num_children(self):
# type: () -> int
# Actually there are 3 children, but only the `value` should be shown as a child
return 1
def get_child_index(self, name):
# type: (str) -> int
if name == "value":
return 0
if name == "strong":
return 1
if name == "weak":
return 2
return -1
def get_child_at_index(self, index):
# type: (int) -> SBValue
if index == 0:
return self.value
if index == 1:
return self.value_builder.from_uint("strong", self.strong_count)
if index == 2:
return self.value_builder.from_uint("weak", self.weak_count)
return None
def update(self):
# type: () -> None
self.strong_count = self.strong.GetValueAsUnsigned()
self.weak_count = self.weak.GetValueAsUnsigned() - 1
def has_children(self):
# type: () -> bool
return True
class StdCellSyntheticProvider:
"""Pretty-printer for std::cell::Cell"""
def __init__(self, valobj, dict):
# type: (SBValue, dict) -> StdCellSyntheticProvider
self.valobj = valobj
self.value = valobj.GetChildMemberWithName("value").GetChildAtIndex(0)
def num_children(self):
# type: () -> int
return 1
def get_child_index(self, name):
# type: (str) -> int
if name == "value":
return 0
return -1
def get_child_at_index(self, index):
# type: (int) -> SBValue
if index == 0:
return self.value
return None
def update(self):
# type: () -> None
pass
def has_children(self):
# type: () -> bool
return True
def StdRefSummaryProvider(valobj, dict):
# type: (SBValue, dict) -> str
borrow = valobj.GetChildMemberWithName("borrow").GetValueAsSigned()
return "borrow={}".format(borrow) if borrow >= 0 else "borrow_mut={}".format(-borrow)
class StdRefSyntheticProvider:
"""Pretty-printer for std::cell::Ref, std::cell::RefMut, and std::cell::RefCell"""
def __init__(self, valobj, dict, is_cell=False):
# type: (SBValue, dict, bool) -> StdRefSyntheticProvider
self.valobj = valobj
borrow = valobj.GetChildMemberWithName("borrow")
value = valobj.GetChildMemberWithName("value")
if is_cell:
self.borrow = borrow.GetChildMemberWithName("value").GetChildMemberWithName("value")
self.value = value.GetChildMemberWithName("value")
else:
self.borrow = borrow.GetChildMemberWithName("borrow").GetChildMemberWithName(
"value").GetChildMemberWithName("value")
self.value = value.Dereference()
self.value_builder = ValueBuilder(valobj)
self.update()
def num_children(self):
# type: () -> int
# Actually there are 2 children, but only the `value` should be shown as a child
return 1
def get_child_index(self, name):
if name == "value":
return 0
if name == "borrow":
return 1
return -1
def get_child_at_index(self, index):
# type: (int) -> SBValue
if index == 0:
return self.value
if index == 1:
return self.value_builder.from_int("borrow", self.borrow_count)
return None
def update(self):
# type: () -> None
self.borrow_count = self.borrow.GetValueAsSigned()
def has_children(self):
# type: () -> bool
return True
def StdNonZeroNumberSummaryProvider(valobj, _dict):
# type: (SBValue, dict) -> str
inner = valobj.GetChildAtIndex(0)
inner_inner = inner.GetChildAtIndex(0)
# FIXME: Avoid printing as character literal,
# see https://github.com/llvm/llvm-project/issues/65076.
if inner_inner.GetTypeName() in ['char', 'unsigned char']:
return str(inner_inner.GetValueAsSigned())
else:
return inner_inner.GetValue()

View File

@ -0,0 +1,127 @@
import re
class RustType(object):
OTHER = "Other"
STRUCT = "Struct"
TUPLE = "Tuple"
CSTYLE_VARIANT = "CStyleVariant"
TUPLE_VARIANT = "TupleVariant"
STRUCT_VARIANT = "StructVariant"
ENUM = "Enum"
EMPTY = "Empty"
SINGLETON_ENUM = "SingletonEnum"
REGULAR_ENUM = "RegularEnum"
COMPRESSED_ENUM = "CompressedEnum"
REGULAR_UNION = "RegularUnion"
STD_STRING = "StdString"
STD_OS_STRING = "StdOsString"
STD_STR = "StdStr"
STD_SLICE = "StdSlice"
STD_VEC = "StdVec"
STD_VEC_DEQUE = "StdVecDeque"
STD_BTREE_SET = "StdBTreeSet"
STD_BTREE_MAP = "StdBTreeMap"
STD_HASH_MAP = "StdHashMap"
STD_HASH_SET = "StdHashSet"
STD_RC = "StdRc"
STD_ARC = "StdArc"
STD_CELL = "StdCell"
STD_REF = "StdRef"
STD_REF_MUT = "StdRefMut"
STD_REF_CELL = "StdRefCell"
STD_NONZERO_NUMBER = "StdNonZeroNumber"
STD_PATH = "StdPath"
STD_PATHBUF = "StdPathBuf"
STD_STRING_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)String$")
STD_STR_REGEX = re.compile(r"^&(mut )?str$")
STD_SLICE_REGEX = re.compile(r"^&(mut )?\[.+\]$")
STD_OS_STRING_REGEX = re.compile(r"^(std::ffi::([a-z_]+::)+)OsString$")
STD_VEC_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)Vec<.+>$")
STD_VEC_DEQUE_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)VecDeque<.+>$")
STD_BTREE_SET_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)BTreeSet<.+>$")
STD_BTREE_MAP_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)BTreeMap<.+>$")
STD_HASH_MAP_REGEX = re.compile(r"^(std::collections::([a-z_]+::)+)HashMap<.+>$")
STD_HASH_SET_REGEX = re.compile(r"^(std::collections::([a-z_]+::)+)HashSet<.+>$")
STD_RC_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)Rc<.+>$")
STD_ARC_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)Arc<.+>$")
STD_CELL_REGEX = re.compile(r"^(core::([a-z_]+::)+)Cell<.+>$")
STD_REF_REGEX = re.compile(r"^(core::([a-z_]+::)+)Ref<.+>$")
STD_REF_MUT_REGEX = re.compile(r"^(core::([a-z_]+::)+)RefMut<.+>$")
STD_REF_CELL_REGEX = re.compile(r"^(core::([a-z_]+::)+)RefCell<.+>$")
STD_NONZERO_NUMBER_REGEX = re.compile(r"^(core::([a-z_]+::)+)NonZero<.+>$")
STD_PATHBUF_REGEX = re.compile(r"^(std::([a-z_]+::)+)PathBuf$")
STD_PATH_REGEX = re.compile(r"^&(mut )?(std::([a-z_]+::)+)Path$")
TUPLE_ITEM_REGEX = re.compile(r"__\d+$")
ENCODED_ENUM_PREFIX = "RUST$ENCODED$ENUM$"
ENUM_DISR_FIELD_NAME = "<<variant>>"
ENUM_LLDB_ENCODED_VARIANTS = "$variants$"
STD_TYPE_TO_REGEX = {
RustType.STD_STRING: STD_STRING_REGEX,
RustType.STD_OS_STRING: STD_OS_STRING_REGEX,
RustType.STD_STR: STD_STR_REGEX,
RustType.STD_SLICE: STD_SLICE_REGEX,
RustType.STD_VEC: STD_VEC_REGEX,
RustType.STD_VEC_DEQUE: STD_VEC_DEQUE_REGEX,
RustType.STD_HASH_MAP: STD_HASH_MAP_REGEX,
RustType.STD_HASH_SET: STD_HASH_SET_REGEX,
RustType.STD_BTREE_SET: STD_BTREE_SET_REGEX,
RustType.STD_BTREE_MAP: STD_BTREE_MAP_REGEX,
RustType.STD_RC: STD_RC_REGEX,
RustType.STD_ARC: STD_ARC_REGEX,
RustType.STD_REF: STD_REF_REGEX,
RustType.STD_REF_MUT: STD_REF_MUT_REGEX,
RustType.STD_REF_CELL: STD_REF_CELL_REGEX,
RustType.STD_CELL: STD_CELL_REGEX,
RustType.STD_NONZERO_NUMBER: STD_NONZERO_NUMBER_REGEX,
RustType.STD_PATHBUF: STD_PATHBUF_REGEX,
RustType.STD_PATH: STD_PATH_REGEX,
}
def is_tuple_fields(fields):
# type: (list) -> bool
return all(TUPLE_ITEM_REGEX.match(str(field.name)) for field in fields)
def classify_struct(name, fields):
if len(fields) == 0:
return RustType.EMPTY
for ty, regex in STD_TYPE_TO_REGEX.items():
if regex.match(name):
return ty
# <<variant>> is emitted by GDB while LLDB(18.1+) emits "$variants$"
if (
fields[0].name == ENUM_DISR_FIELD_NAME
or fields[0].name == ENUM_LLDB_ENCODED_VARIANTS
):
return RustType.ENUM
if is_tuple_fields(fields):
return RustType.TUPLE
return RustType.STRUCT
def classify_union(fields):
if len(fields) == 0:
return RustType.EMPTY
first_variant_name = fields[0].name
if first_variant_name is None:
if len(fields) == 1:
return RustType.SINGLETON_ENUM
else:
return RustType.REGULAR_ENUM
elif first_variant_name.startswith(ENCODED_ENUM_PREFIX):
assert len(fields) == 1
return RustType.COMPRESSED_ENUM
else:
return RustType.REGULAR_UNION