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"]) self._cap = int(valobj["buf"]["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] self._value = str(valobj[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"