|
|
@@ -0,0 +1,992 @@ |
|
|
#===----------------------------------------------------------------------===## |
|
|
# |
|
|
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
|
|
# See https://llvm.org/LICENSE.txt for license information. |
|
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
|
|
# |
|
|
#===----------------------------------------------------------------------===## |
|
|
"""GDB pretty-printers for libc++. |
|
|
|
|
|
These should work for objects compiled when _LIBCPP_ABI_UNSTABLE is defined |
|
|
and when it is undefined. |
|
|
""" |
|
|
|
|
|
from __future__ import print_function |
|
|
|
|
|
import re |
|
|
import gdb |
|
|
|
|
|
# One under-documented feature of the gdb pretty-printer API |
|
|
# is that clients can call any other member of the API |
|
|
# before they call to_string. |
|
|
# Therefore all self.FIELDs must be set in the pretty-printer's |
|
|
# __init__ function. |
|
|
|
|
|
_void_pointer_type = gdb.lookup_type("void").pointer() |
|
|
|
|
|
|
|
|
_long_int_type = gdb.lookup_type("unsigned long long") |
|
|
|
|
|
|
|
|
def addr_as_long(addr): |
|
|
return int(addr.cast(_long_int_type)) |
|
|
|
|
|
|
|
|
# The size of a pointer in bytes. |
|
|
_pointer_size = _void_pointer_type.sizeof |
|
|
|
|
|
|
|
|
def _remove_cxx_namespace(typename): |
|
|
"""Removed libc++ specific namespace from the type. |
|
|
|
|
|
Arguments: |
|
|
typename(string): A type, such as std::__u::something. |
|
|
|
|
|
Returns: |
|
|
A string without the libc++ specific part, such as std::something. |
|
|
""" |
|
|
|
|
|
return re.sub("std::__.*?::", "std::", typename) |
|
|
|
|
|
|
|
|
def _remove_generics(typename): |
|
|
"""Remove generics part of the type. Assumes typename is not empty. |
|
|
|
|
|
Arguments: |
|
|
typename(string): A type such as std::my_collection<element>. |
|
|
|
|
|
Returns: |
|
|
The prefix up to the generic part, such as std::my_collection. |
|
|
""" |
|
|
|
|
|
match = re.match("^([^<]+)", typename) |
|
|
return match.group(1) |
|
|
|
|
|
|
|
|
# Some common substitutions on the types to reduce visual clutter (A user who |
|
|
# wants to see the actual details can always use print/r). |
|
|
_common_substitutions = [ |
|
|
("std::basic_string<char, std::char_traits<char>, std::allocator<char> >", |
|
|
"std::string"), |
|
|
] |
|
|
|
|
|
|
|
|
def _prettify_typename(gdb_type): |
|
|
"""Returns a pretty name for the type, or None if no name can be found. |
|
|
|
|
|
Arguments: |
|
|
gdb_type(gdb.Type): A type object. |
|
|
|
|
|
Returns: |
|
|
A string, without type_defs, libc++ namespaces, and common substitutions |
|
|
applied. |
|
|
""" |
|
|
|
|
|
type_without_typedefs = gdb_type.strip_typedefs() |
|
|
typename = type_without_typedefs.name or type_without_typedefs.tag or \ |
|
|
str(type_without_typedefs) |
|
|
result = _remove_cxx_namespace(typename) |
|
|
for find_str, subst_str in _common_substitutions: |
|
|
result = re.sub(find_str, subst_str, result) |
|
|
return result |
|
|
|
|
|
|
|
|
def _typename_for_nth_generic_argument(gdb_type, n): |
|
|
"""Returns a pretty string for the nth argument of the given type. |
|
|
|
|
|
Arguments: |
|
|
gdb_type(gdb.Type): A type object, such as the one for std::map<int, int> |
|
|
n: The (zero indexed) index of the argument to return. |
|
|
|
|
|
Returns: |
|
|
A string for the nth argument, such a "std::string" |
|
|
""" |
|
|
element_type = gdb_type.template_argument(n) |
|
|
return _prettify_typename(element_type) |
|
|
|
|
|
|
|
|
def _typename_with_n_generic_arguments(gdb_type, n): |
|
|
"""Return a string for the type with the first n (1, ...) generic args.""" |
|
|
|
|
|
base_type = _remove_generics(_prettify_typename(gdb_type)) |
|
|
arg_list = [base_type] |
|
|
template = "%s<" |
|
|
for i in range(n): |
|
|
arg_list.append(_typename_for_nth_generic_argument(gdb_type, i)) |
|
|
template += "%s, " |
|
|
result = (template[:-2] + ">") % tuple(arg_list) |
|
|
return result |
|
|
|
|
|
|
|
|
def _typename_with_first_generic_argument(gdb_type): |
|
|
return _typename_with_n_generic_arguments(gdb_type, 1) |
|
|
|
|
|
|
|
|
class StdTuplePrinter(object): |
|
|
"""Print a std::tuple.""" |
|
|
|
|
|
class _Children(object): |
|
|
"""Class to iterate over the tuple's children.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self.val = val |
|
|
self.child_iter = iter(self.val["__base_"].type.fields()) |
|
|
self.count = 0 |
|
|
|
|
|
def __iter__(self): |
|
|
return self |
|
|
|
|
|
def next(self): |
|
|
# child_iter raises StopIteration when appropriate. |
|
|
field_name = self.child_iter.next() |
|
|
child = self.val["__base_"][field_name]["__value_"] |
|
|
self.count += 1 |
|
|
return ("[%d]" % self.count, child) |
|
|
|
|
|
def __init__(self, val): |
|
|
self.val = val |
|
|
|
|
|
def to_string(self): |
|
|
typename = _remove_generics(_prettify_typename(self.val.type)) |
|
|
if not self.val.type.fields(): |
|
|
return "empty %s" % typename |
|
|
return "%s containing" % typename |
|
|
|
|
|
def children(self): |
|
|
if not self.val.type.fields(): |
|
|
return iter(()) |
|
|
return self._Children(self.val) |
|
|
|
|
|
|
|
|
def _get_base_subobject(child_class_value, index=0): |
|
|
"""Returns the object's value in the form of the parent class at index. |
|
|
|
|
|
This function effectively casts the child_class_value to the base_class's |
|
|
type, but the type-to-cast to is stored in the field at index, and once |
|
|
we know the field, we can just return the data. |
|
|
|
|
|
Args: |
|
|
child_class_value: the value to cast |
|
|
index: the parent class index |
|
|
|
|
|
Raises: |
|
|
Exception: field at index was not a base-class field. |
|
|
""" |
|
|
|
|
|
field = child_class_value.type.fields()[index] |
|
|
if not field.is_base_class: |
|
|
raise Exception("Not a base-class field.") |
|
|
return child_class_value[field] |
|
|
|
|
|
|
|
|
def _value_of_pair_first(value): |
|
|
"""Convenience for _get_base_subobject, for the common case.""" |
|
|
return _get_base_subobject(value, 0)["__value_"] |
|
|
|
|
|
|
|
|
class StdStringPrinter(object): |
|
|
"""Print a std::string.""" |
|
|
|
|
|
def _get_short_size(self, short_field, short_size): |
|
|
"""Short size depends on both endianness and a compile-time define.""" |
|
|
|
|
|
# If the padding field is present after all this indirection, then string |
|
|
# was compiled with _LIBCPP_ABI_ALTERNATE_STRING_LAYOUT defined. |
|
|
field = short_field.type.fields()[1].type.fields()[0] |
|
|
libcpp_abi_alternate_string_layout = field.name and "__padding" in field.name |
|
|
|
|
|
# Strictly, this only tells us the current mode, not how libcxx was |
|
|
# compiled. |
|
|
libcpp_big_endian = "big endian" in gdb.execute("show endian", |
|
|
to_string=True) |
|
|
|
|
|
# This logical structure closely follows the original code (which is clearer |
|
|
# in C++). Keep them parallel to make them easier to compare. |
|
|
if libcpp_abi_alternate_string_layout: |
|
|
if libcpp_big_endian: |
|
|
return short_size >> 1 |
|
|
else: |
|
|
return short_size |
|
|
elif libcpp_big_endian: |
|
|
return short_size |
|
|
else: |
|
|
return short_size >> 1 |
|
|
|
|
|
def __init__(self, val): |
|
|
self.val = val |
|
|
|
|
|
def to_string(self): |
|
|
"""Build a python string from the data whether stored inline or separately.""" |
|
|
|
|
|
value_field = _value_of_pair_first(self.val["__r_"]) |
|
|
short_field = value_field["__s"] |
|
|
short_size = short_field["__size_"] |
|
|
if short_size == 0: |
|
|
return "" |
|
|
short_mask = self.val["__short_mask"] |
|
|
# Counter intuitive to compare the size and short_mask to see if the string |
|
|
# is long, but that's the way the implementation does it. Note that |
|
|
# __is_long() doesn't use get_short_size in C++. |
|
|
is_long = short_size & short_mask |
|
|
if is_long: |
|
|
long_field = value_field["__l"] |
|
|
data = long_field["__data_"] |
|
|
size = long_field["__size_"] |
|
|
else: |
|
|
data = short_field["__data_"] |
|
|
size = self._get_short_size(short_field, short_size) |
|
|
if hasattr(data, "lazy_string"): |
|
|
return data.lazy_string(length=size) |
|
|
return data.string(length=size) |
|
|
|
|
|
def display_hint(self): |
|
|
return "string" |
|
|
|
|
|
|
|
|
class StdUniquePtrPrinter(object): |
|
|
"""Print a std::unique_ptr.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self.val = val |
|
|
self.addr = _value_of_pair_first(self.val["__ptr_"]) |
|
|
self.pointee_type = self.val.type.template_argument(0) |
|
|
|
|
|
def to_string(self): |
|
|
typename = _remove_generics(_prettify_typename(self.val.type)) |
|
|
if not self.addr: |
|
|
return "%s is nullptr" % typename |
|
|
return ("%s<%s> containing" % |
|
|
(typename, |
|
|
_remove_generics(_prettify_typename(self.pointee_type)))) |
|
|
|
|
|
def __iter__(self): |
|
|
if self.addr: |
|
|
yield "__ptr_", self.addr.cast(self.pointee_type.pointer()) |
|
|
|
|
|
def children(self): |
|
|
return self |
|
|
|
|
|
|
|
|
class StdSharedPointerPrinter(object): |
|
|
"""Print a std::shared_ptr.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self.val = val |
|
|
self.addr = self.val["__ptr_"] |
|
|
|
|
|
def to_string(self): |
|
|
"""Returns self as a string.""" |
|
|
typename = _remove_generics(_prettify_typename(self.val.type)) |
|
|
pointee_type = _remove_generics( |
|
|
_prettify_typename(self.val.type.template_argument(0))) |
|
|
if not self.addr: |
|
|
return "%s is nullptr" % typename |
|
|
refcount = self.val["__cntrl_"] |
|
|
if refcount != 0: |
|
|
usecount = refcount["__shared_owners_"] + 1 |
|
|
weakcount = refcount["__shared_weak_owners_"] |
|
|
if usecount == 0: |
|
|
state = "expired, weak %d" % weakcount |
|
|
else: |
|
|
state = "count %d, weak %d" % (usecount, weakcount) |
|
|
return "%s<%s> %s containing" % (typename, pointee_type, state) |
|
|
|
|
|
def __iter__(self): |
|
|
if self.addr: |
|
|
yield "__ptr_", self.addr |
|
|
|
|
|
def children(self): |
|
|
return self |
|
|
|
|
|
|
|
|
class StdVectorPrinter(object): |
|
|
"""Print a std::vector.""" |
|
|
|
|
|
class _VectorBoolIterator(object): |
|
|
"""Class to iterate over the bool vector's children.""" |
|
|
|
|
|
def __init__(self, begin, size, bits_per_word): |
|
|
self.item = begin |
|
|
self.size = size |
|
|
self.bits_per_word = bits_per_word |
|
|
self.count = 0 |
|
|
self.offset = 0 |
|
|
|
|
|
def __iter__(self): |
|
|
return self |
|
|
|
|
|
def next(self): |
|
|
"""Retrieve the next element.""" |
|
|
|
|
|
self.count += 1 |
|
|
if self.count > self.size: |
|
|
raise StopIteration |
|
|
entry = self.item.dereference() |
|
|
if entry & (1 << self.offset): |
|
|
outbit = 1 |
|
|
else: |
|
|
outbit = 0 |
|
|
self.offset += 1 |
|
|
if self.offset >= self.bits_per_word: |
|
|
self.item += 1 |
|
|
self.offset = 0 |
|
|
return ("[%d]" % self.count, outbit) |
|
|
|
|
|
class _VectorIterator(object): |
|
|
"""Class to iterate over the non-bool vector's children.""" |
|
|
|
|
|
def __init__(self, begin, end): |
|
|
self.item = begin |
|
|
self.end = end |
|
|
self.count = 0 |
|
|
|
|
|
def __iter__(self): |
|
|
return self |
|
|
|
|
|
def next(self): |
|
|
self.count += 1 |
|
|
if self.item == self.end: |
|
|
raise StopIteration |
|
|
entry = self.item.dereference() |
|
|
self.item += 1 |
|
|
return ("[%d]" % self.count, entry) |
|
|
|
|
|
def __init__(self, val): |
|
|
"""Set val, length, capacity, and iterator for bool and normal vectors.""" |
|
|
self.val = val |
|
|
self.typename = _remove_generics(_prettify_typename(val.type)) |
|
|
begin = self.val["__begin_"] |
|
|
if self.val.type.template_argument(0).code == gdb.TYPE_CODE_BOOL: |
|
|
self.typename += "<bool>" |
|
|
self.length = self.val["__size_"] |
|
|
bits_per_word = self.val["__bits_per_word"] |
|
|
self.capacity = _value_of_pair_first( |
|
|
self.val["__cap_alloc_"]) * bits_per_word |
|
|
self.iterator = self._VectorBoolIterator( |
|
|
begin, self.length, bits_per_word) |
|
|
else: |
|
|
end = self.val["__end_"] |
|
|
self.length = end - begin |
|
|
self.capacity = _get_base_subobject( |
|
|
self.val["__end_cap_"])["__value_"] - begin |
|
|
self.iterator = self._VectorIterator(begin, end) |
|
|
|
|
|
def to_string(self): |
|
|
return ("%s of length %d, capacity %d" % |
|
|
(self.typename, self.length, self.capacity)) |
|
|
|
|
|
def children(self): |
|
|
return self.iterator |
|
|
|
|
|
def display_hint(self): |
|
|
return "array" |
|
|
|
|
|
|
|
|
class StdBitsetPrinter(object): |
|
|
"""Print a std::bitset.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self.val = val |
|
|
self.n_words = int(self.val["__n_words"]) |
|
|
self.bits_per_word = int(self.val["__bits_per_word"]) |
|
|
if self.n_words == 1: |
|
|
self.values = [int(self.val["__first_"])] |
|
|
else: |
|
|
self.values = [int(self.val["__first_"][index]) |
|
|
for index in range(self.n_words)] |
|
|
|
|
|
def to_string(self): |
|
|
typename = _prettify_typename(self.val.type) |
|
|
return "%s" % typename |
|
|
|
|
|
def _byte_it(self, value): |
|
|
index = -1 |
|
|
while value: |
|
|
index += 1 |
|
|
will_yield = value % 2 |
|
|
value /= 2 |
|
|
if will_yield: |
|
|
yield index |
|
|
|
|
|
def _list_it(self): |
|
|
for word_index in range(self.n_words): |
|
|
current = self.values[word_index] |
|
|
if current: |
|
|
for n in self._byte_it(current): |
|
|
yield ("[%d]" % (word_index * self.bits_per_word + n), 1) |
|
|
|
|
|
def __iter__(self): |
|
|
return self._list_it() |
|
|
|
|
|
def children(self): |
|
|
return self |
|
|
|
|
|
|
|
|
class StdDequePrinter(object): |
|
|
"""Print a std::deque.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self.val = val |
|
|
self.size = int(_value_of_pair_first(val["__size_"])) |
|
|
self.start_ptr = self.val["__map_"]["__begin_"] |
|
|
self.first_block_start_index = int(self.val["__start_"]) |
|
|
self.node_type = self.start_ptr.type |
|
|
self.block_size = self._calculate_block_size( |
|
|
val.type.template_argument(0)) |
|
|
|
|
|
def _calculate_block_size(self, element_type): |
|
|
"""Calculates the number of elements in a full block.""" |
|
|
size = element_type.sizeof |
|
|
# Copied from struct __deque_block_size implementation of libcxx. |
|
|
return 4096 / size if size < 256 else 16 |
|
|
|
|
|
def _bucket_it(self, start_addr, start_index, end_index): |
|
|
for i in range(start_index, end_index): |
|
|
yield i, (start_addr.dereference() + i).dereference() |
|
|
|
|
|
def _list_it(self): |
|
|
"""Primary iteration worker.""" |
|
|
num_emitted = 0 |
|
|
current_addr = self.start_ptr |
|
|
start_index = self.first_block_start_index |
|
|
while num_emitted < self.size: |
|
|
end_index = min(start_index + self.size - |
|
|
num_emitted, self.block_size) |
|
|
for _, elem in self._bucket_it(current_addr, start_index, end_index): |
|
|
yield "", elem |
|
|
num_emitted += end_index - start_index |
|
|
current_addr = gdb.Value(addr_as_long(current_addr) + _pointer_size) \ |
|
|
.cast(self.node_type) |
|
|
start_index = 0 |
|
|
|
|
|
def to_string(self): |
|
|
typename = _remove_generics(_prettify_typename(self.val.type)) |
|
|
if self.size: |
|
|
return "%s with %d elements" % (typename, self.size) |
|
|
return "%s is empty" % typename |
|
|
|
|
|
def __iter__(self): |
|
|
return self._list_it() |
|
|
|
|
|
def children(self): |
|
|
return self |
|
|
|
|
|
def display_hint(self): |
|
|
return "array" |
|
|
|
|
|
|
|
|
class StdListPrinter(object): |
|
|
"""Print a std::list.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self.val = val |
|
|
size_alloc_field = self.val["__size_alloc_"] |
|
|
self.size = int(_value_of_pair_first(size_alloc_field)) |
|
|
dummy_node = self.val["__end_"] |
|
|
self.nodetype = gdb.lookup_type( |
|
|
re.sub("__list_node_base", "__list_node", |
|
|
str(dummy_node.type.strip_typedefs()))).pointer() |
|
|
self.first_node = dummy_node["__next_"] |
|
|
|
|
|
def to_string(self): |
|
|
typename = _remove_generics(_prettify_typename(self.val.type)) |
|
|
if self.size: |
|
|
return "%s with %d elements" % (typename, self.size) |
|
|
return "%s is empty" % typename |
|
|
|
|
|
def _list_iter(self): |
|
|
current_node = self.first_node |
|
|
for _ in range(self.size): |
|
|
yield "", current_node.cast(self.nodetype).dereference()["__value_"] |
|
|
current_node = current_node.dereference()["__next_"] |
|
|
|
|
|
def __iter__(self): |
|
|
return self._list_iter() |
|
|
|
|
|
def children(self): |
|
|
return self if self.nodetype else iter(()) |
|
|
|
|
|
def display_hint(self): |
|
|
return "array" |
|
|
|
|
|
|
|
|
class StdQueueOrStackPrinter(object): |
|
|
"""Print a std::queue or std::stack.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self.val = val |
|
|
self.underlying = val["c"] |
|
|
|
|
|
def to_string(self): |
|
|
typename = _remove_generics(_prettify_typename(self.val.type)) |
|
|
return "%s wrapping" % typename |
|
|
|
|
|
def children(self): |
|
|
return iter([("", self.underlying)]) |
|
|
|
|
|
def display_hint(self): |
|
|
return "array" |
|
|
|
|
|
|
|
|
class StdPriorityQueuePrinter(object): |
|
|
"""Print a std::priority_queue.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self.val = val |
|
|
self.underlying = val["c"] |
|
|
|
|
|
def to_string(self): |
|
|
# TODO(tamur): It would be nice to print the top element. The technical |
|
|
# difficulty is that, the implementation refers to the underlying |
|
|
# container, which is a generic class. libstdcxx pretty printers do not |
|
|
# print the top element. |
|
|
typename = _remove_generics(_prettify_typename(self.val.type)) |
|
|
return "%s wrapping" % typename |
|
|
|
|
|
def children(self): |
|
|
return iter([("", self.underlying)]) |
|
|
|
|
|
def display_hint(self): |
|
|
return "array" |
|
|
|
|
|
|
|
|
class RBTreeUtils(object): |
|
|
"""Utility class for std::(multi)map, and std::(multi)set and iterators.""" |
|
|
|
|
|
def __init__(self, cast_type, root): |
|
|
self.cast_type = cast_type |
|
|
self.root = root |
|
|
|
|
|
def left_child(self, node): |
|
|
result = node.cast(self.cast_type).dereference()["__left_"] |
|
|
return result |
|
|
|
|
|
def right_child(self, node): |
|
|
result = node.cast(self.cast_type).dereference()["__right_"] |
|
|
return result |
|
|
|
|
|
def parent(self, node): |
|
|
"""Return the parent of node, if it exists.""" |
|
|
# If this is the root, then from the algorithm's point of view, it has no |
|
|
# parent. |
|
|
if node == self.root: |
|
|
return None |
|
|
|
|
|
# We don't have enough information to tell if this is the end_node (which |
|
|
# doesn't have a __parent_ field), or the root (which doesn't have a parent |
|
|
# from the algorithm's point of view), so cast_type may not be correct for |
|
|
# this particular node. Use heuristics. |
|
|
|
|
|
# The end_node's left child is the root. Note that when printing interators |
|
|
# in isolation, the root is unknown. |
|
|
if self.left_child(node) == self.root: |
|
|
return None |
|
|
|
|
|
parent = node.cast(self.cast_type).dereference()["__parent_"] |
|
|
# If the value at the offset of __parent_ doesn't look like a valid pointer, |
|
|
# then assume that node is the end_node (and therefore has no parent). |
|
|
# End_node type has a pointer embedded, so should have pointer alignment. |
|
|
if addr_as_long(parent) % _void_pointer_type.alignof: |
|
|
return None |
|
|
# This is ugly, but the only other option is to dereference an invalid |
|
|
# pointer. 0x8000 is fairly arbitrary, but has had good results in |
|
|
# practice. If there was a way to tell if a pointer is invalid without |
|
|
# actually dereferencing it and spewing error messages, that would be ideal. |
|
|
if parent < 0x8000: |
|
|
return None |
|
|
return parent |
|
|
|
|
|
def is_left_child(self, node): |
|
|
parent = self.parent(node) |
|
|
return parent is not None and self.left_child(parent) == node |
|
|
|
|
|
def is_right_child(self, node): |
|
|
parent = self.parent(node) |
|
|
return parent is not None and self.right_child(parent) == node |
|
|
|
|
|
|
|
|
class AbstractRBTreePrinter(object): |
|
|
"""Abstract super class for std::(multi)map, and std::(multi)set.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self.val = val |
|
|
tree = self.val["__tree_"] |
|
|
self.size = int(_value_of_pair_first(tree["__pair3_"])) |
|
|
dummy_root = tree["__pair1_"] |
|
|
root = _value_of_pair_first(dummy_root)["__left_"] |
|
|
cast_type = self._init_cast_type(val.type) |
|
|
self.util = RBTreeUtils(cast_type, root) |
|
|
|
|
|
def _get_key_value(self, node): |
|
|
"""Subclasses should override to return a list of values to yield.""" |
|
|
raise NotImplementedError |
|
|
|
|
|
def _traverse(self): |
|
|
"""Traverses the binary search tree in order.""" |
|
|
current = self.util.root |
|
|
skip_left_child = False |
|
|
while True: |
|
|
if not skip_left_child and self.util.left_child(current): |
|
|
current = self.util.left_child(current) |
|
|
continue |
|
|
skip_left_child = False |
|
|
for key_value in self._get_key_value(current): |
|
|
yield "", key_value |
|
|
right_child = self.util.right_child(current) |
|
|
if right_child: |
|
|
current = right_child |
|
|
continue |
|
|
while self.util.is_right_child(current): |
|
|
current = self.util.parent(current) |
|
|
if self.util.is_left_child(current): |
|
|
current = self.util.parent(current) |
|
|
skip_left_child = True |
|
|
continue |
|
|
break |
|
|
|
|
|
def __iter__(self): |
|
|
return self._traverse() |
|
|
|
|
|
def children(self): |
|
|
return self if self.util.cast_type and self.size > 0 else iter(()) |
|
|
|
|
|
def to_string(self): |
|
|
typename = _remove_generics(_prettify_typename(self.val.type)) |
|
|
if self.size: |
|
|
return "%s with %d elements" % (typename, self.size) |
|
|
return "%s is empty" % typename |
|
|
|
|
|
|
|
|
class StdMapPrinter(AbstractRBTreePrinter): |
|
|
"""Print a std::map or std::multimap.""" |
|
|
|
|
|
def _init_cast_type(self, val_type): |
|
|
map_it_type = gdb.lookup_type( |
|
|
str(val_type) + "::iterator").strip_typedefs() |
|
|
tree_it_type = map_it_type.template_argument(0) |
|
|
node_ptr_type = tree_it_type.template_argument(1) |
|
|
return node_ptr_type |
|
|
|
|
|
def display_hint(self): |
|
|
return "map" |
|
|
|
|
|
def _get_key_value(self, node): |
|
|
key_value = node.cast(self.util.cast_type).dereference()[ |
|
|
"__value_"]["__cc"] |
|
|
return [key_value["first"], key_value["second"]] |
|
|
|
|
|
|
|
|
class StdSetPrinter(AbstractRBTreePrinter): |
|
|
"""Print a std::set.""" |
|
|
|
|
|
def _init_cast_type(self, val_type): |
|
|
set_it_type = gdb.lookup_type( |
|
|
str(val_type) + "::iterator").strip_typedefs() |
|
|
node_ptr_type = set_it_type.template_argument(1) |
|
|
return node_ptr_type |
|
|
|
|
|
def display_hint(self): |
|
|
return "array" |
|
|
|
|
|
def _get_key_value(self, node): |
|
|
key_value = node.cast(self.util.cast_type).dereference()["__value_"] |
|
|
return [key_value] |
|
|
|
|
|
|
|
|
class AbstractRBTreeIteratorPrinter(object): |
|
|
"""Abstract super class for std::(multi)map, and std::(multi)set iterator.""" |
|
|
|
|
|
def _initialize(self, val, typename): |
|
|
self.typename = typename |
|
|
self.val = val |
|
|
self.addr = self.val["__ptr_"] |
|
|
cast_type = self.val.type.template_argument(1) |
|
|
self.util = RBTreeUtils(cast_type, None) |
|
|
if self.addr: |
|
|
self.node = self.addr.cast(cast_type).dereference() |
|
|
|
|
|
def _is_valid_node(self): |
|
|
if not self.util.parent(self.addr): |
|
|
return False |
|
|
return self.util.is_left_child(self.addr) or \ |
|
|
self.util.is_right_child(self.addr) |
|
|
|
|
|
def to_string(self): |
|
|
if not self.addr: |
|
|
return "%s is nullptr" % self.typename |
|
|
return "%s " % self.typename |
|
|
|
|
|
def _get_node_value(self, node): |
|
|
raise NotImplementedError |
|
|
|
|
|
def __iter__(self): |
|
|
addr_str = "[%s]" % str(self.addr) |
|
|
if not self._is_valid_node(): |
|
|
yield addr_str, " end()" |
|
|
else: |
|
|
yield addr_str, self._get_node_value(self.node) |
|
|
|
|
|
def children(self): |
|
|
return self if self.addr else iter(()) |
|
|
|
|
|
|
|
|
class MapIteratorPrinter(AbstractRBTreeIteratorPrinter): |
|
|
"""Print a std::(multi)map iterator.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self._initialize(val["__i_"], |
|
|
_remove_generics(_prettify_typename(val.type))) |
|
|
|
|
|
def _get_node_value(self, node): |
|
|
return node["__value_"]["__cc"] |
|
|
|
|
|
|
|
|
class SetIteratorPrinter(AbstractRBTreeIteratorPrinter): |
|
|
"""Print a std::(multi)set iterator.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self._initialize(val, _remove_generics(_prettify_typename(val.type))) |
|
|
|
|
|
def _get_node_value(self, node): |
|
|
return node["__value_"] |
|
|
|
|
|
|
|
|
class StdFposPrinter(object): |
|
|
"""Print a std::fpos or std::streampos.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self.val = val |
|
|
|
|
|
def to_string(self): |
|
|
typename = _remove_generics(_prettify_typename(self.val.type)) |
|
|
offset = self.val["__off_"] |
|
|
state = self.val["__st_"] |
|
|
count = state["__count"] |
|
|
value = state["__value"]["__wch"] |
|
|
return "%s with stream offset:%s with state: {count:%s value:%s}" % ( |
|
|
typename, offset, count, value) |
|
|
|
|
|
|
|
|
class AbstractUnorderedCollectionPrinter(object): |
|
|
"""Abstract super class for std::unordered_(multi)[set|map].""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self.val = val |
|
|
self.table = val["__table_"] |
|
|
self.sentinel = self.table["__p1_"] |
|
|
self.size = int(_value_of_pair_first(self.table["__p2_"])) |
|
|
node_base_type = self.sentinel.type.template_argument(0) |
|
|
self.cast_type = node_base_type.template_argument(0) |
|
|
|
|
|
def _list_it(self, sentinel_ptr): |
|
|
next_ptr = _value_of_pair_first(sentinel_ptr)["__next_"] |
|
|
while str(next_ptr.cast(_void_pointer_type)) != "0x0": |
|
|
next_val = next_ptr.cast(self.cast_type).dereference() |
|
|
for key_value in self._get_key_value(next_val): |
|
|
yield "", key_value |
|
|
next_ptr = next_val["__next_"] |
|
|
|
|
|
def to_string(self): |
|
|
typename = _remove_generics(_prettify_typename(self.val.type)) |
|
|
if self.size: |
|
|
return "%s with %d elements" % (typename, self.size) |
|
|
return "%s is empty" % typename |
|
|
|
|
|
def _get_key_value(self, node): |
|
|
"""Subclasses should override to return a list of values to yield.""" |
|
|
raise NotImplementedError |
|
|
|
|
|
def children(self): |
|
|
return self if self.cast_type and self.size > 0 else iter(()) |
|
|
|
|
|
def __iter__(self): |
|
|
return self._list_it(self.sentinel) |
|
|
|
|
|
|
|
|
class StdUnorderedSetPrinter(AbstractUnorderedCollectionPrinter): |
|
|
"""Print a std::unordered_(multi)set.""" |
|
|
|
|
|
def _get_key_value(self, node): |
|
|
return [node["__value_"]] |
|
|
|
|
|
def display_hint(self): |
|
|
return "array" |
|
|
|
|
|
|
|
|
class StdUnorderedMapPrinter(AbstractUnorderedCollectionPrinter): |
|
|
"""Print a std::unordered_(multi)map.""" |
|
|
|
|
|
def _get_key_value(self, node): |
|
|
key_value = node["__value_"]["__cc"] |
|
|
return [key_value["first"], key_value["second"]] |
|
|
|
|
|
def display_hint(self): |
|
|
return "map" |
|
|
|
|
|
|
|
|
class AbstractHashMapIteratorPrinter(object): |
|
|
"""Abstract class for unordered collection iterators.""" |
|
|
|
|
|
def _initialize(self, val, addr): |
|
|
self.val = val |
|
|
self.typename = _remove_generics(_prettify_typename(self.val.type)) |
|
|
self.addr = addr |
|
|
if self.addr: |
|
|
self.node = self.addr.cast(self.cast_type).dereference() |
|
|
|
|
|
def _get_key_value(self): |
|
|
"""Subclasses should override to return a list of values to yield.""" |
|
|
raise NotImplementedError |
|
|
|
|
|
def to_string(self): |
|
|
if not self.addr: |
|
|
return "%s = end()" % self.typename |
|
|
return "%s " % self.typename |
|
|
|
|
|
def children(self): |
|
|
return self if self.addr else iter(()) |
|
|
|
|
|
def __iter__(self): |
|
|
for key_value in self._get_key_value(): |
|
|
yield "", key_value |
|
|
|
|
|
|
|
|
class StdUnorderedSetIteratorPrinter(AbstractHashMapIteratorPrinter): |
|
|
"""Print a std::(multi)set iterator.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self.cast_type = val.type.template_argument(0) |
|
|
self._initialize(val, val["__node_"]) |
|
|
|
|
|
def _get_key_value(self): |
|
|
return [self.node["__value_"]] |
|
|
|
|
|
def display_hint(self): |
|
|
return "array" |
|
|
|
|
|
|
|
|
class StdUnorderedMapIteratorPrinter(AbstractHashMapIteratorPrinter): |
|
|
"""Print a std::(multi)map iterator.""" |
|
|
|
|
|
def __init__(self, val): |
|
|
self.cast_type = val.type.template_argument(0).template_argument(0) |
|
|
self._initialize(val, val["__i_"]["__node_"]) |
|
|
|
|
|
def _get_key_value(self): |
|
|
key_value = self.node["__value_"]["__cc"] |
|
|
return [key_value["first"], key_value["second"]] |
|
|
|
|
|
def display_hint(self): |
|
|
return "map" |
|
|
|
|
|
|
|
|
def _remove_std_prefix(typename): |
|
|
match = re.match("^std::(.+)", typename) |
|
|
return match.group(1) if match is not None else "" |
|
|
|
|
|
|
|
|
class LibcxxPrettyPrinter(object): |
|
|
"""PrettyPrinter object so gdb-commands like 'info pretty-printers' work.""" |
|
|
|
|
|
def __init__(self, name): |
|
|
super(LibcxxPrettyPrinter, self).__init__() |
|
|
self.name = name |
|
|
self.enabled = True |
|
|
|
|
|
self.lookup = { |
|
|
"basic_string": StdStringPrinter, |
|
|
"string": StdStringPrinter, |
|
|
"tuple": StdTuplePrinter, |
|
|
"unique_ptr": StdUniquePtrPrinter, |
|
|
"shared_ptr": StdSharedPointerPrinter, |
|
|
"weak_ptr": StdSharedPointerPrinter, |
|
|
"bitset": StdBitsetPrinter, |
|
|
"deque": StdDequePrinter, |
|
|
"list": StdListPrinter, |
|
|
"queue": StdQueueOrStackPrinter, |
|
|
"stack": StdQueueOrStackPrinter, |
|
|
"priority_queue": StdPriorityQueuePrinter, |
|
|
"map": StdMapPrinter, |
|
|
"multimap": StdMapPrinter, |
|
|
"set": StdSetPrinter, |
|
|
"multiset": StdSetPrinter, |
|
|
"vector": StdVectorPrinter, |
|
|
"__map_iterator": MapIteratorPrinter, |
|
|
"__map_const_iterator": MapIteratorPrinter, |
|
|
"__tree_iterator": SetIteratorPrinter, |
|
|
"__tree_const_iterator": SetIteratorPrinter, |
|
|
"fpos": StdFposPrinter, |
|
|
"unordered_set": StdUnorderedSetPrinter, |
|
|
"unordered_multiset": StdUnorderedSetPrinter, |
|
|
"unordered_map": StdUnorderedMapPrinter, |
|
|
"unordered_multimap": StdUnorderedMapPrinter, |
|
|
"__hash_map_iterator": StdUnorderedMapIteratorPrinter, |
|
|
"__hash_map_const_iterator": StdUnorderedMapIteratorPrinter, |
|
|
"__hash_iterator": StdUnorderedSetIteratorPrinter, |
|
|
"__hash_const_iterator": StdUnorderedSetIteratorPrinter, |
|
|
} |
|
|
|
|
|
self.subprinters = [] |
|
|
for name, subprinter in self.lookup.items(): |
|
|
# Subprinters and names are used only for the rarely used command "info |
|
|
# pretty" (and related), so the name of the first data structure it prints |
|
|
# is a reasonable choice. |
|
|
if subprinter not in self.subprinters: |
|
|
subprinter.name = name |
|
|
self.subprinters.append(subprinter) |
|
|
|
|
|
def __call__(self, val): |
|
|
"""Return the pretty printer for a val, if the type is supported.""" |
|
|
|
|
|
# Do not handle any type that is not a struct/class. |
|
|
if val.type.strip_typedefs().code != gdb.TYPE_CODE_STRUCT: |
|
|
return None |
|
|
|
|
|
# Don't attempt types known to be inside libstdcxx. |
|
|
typename = val.type.name or val.type.tag or str(val.type) |
|
|
match = re.match("^std::(__.*?)::", typename) |
|
|
if match is None or match.group(1) in ["__cxx1998", |
|
|
"__debug", |
|
|
"__7", |
|
|
"__g"]: |
|
|
return None |
|
|
|
|
|
# Handle any using declarations or other typedefs. |
|
|
typename = _prettify_typename(val.type) |
|
|
if not typename: |
|
|
return None |
|
|
without_generics = _remove_generics(typename) |
|
|
lookup_name = _remove_std_prefix(without_generics) |
|
|
if lookup_name in self.lookup: |
|
|
return self.lookup[lookup_name](val) |
|
|
return None |
|
|
|
|
|
|
|
|
_libcxx_printer_name = "libcxx_pretty_printer" |
|
|
|
|
|
|
|
|
# These are called for every binary object file, which could be thousands in |
|
|
# certain pathological cases. Limit our pretty printers to the progspace. |
|
|
def _register_libcxx_printers(event): |
|
|
progspace = event.new_objfile.progspace |
|
|
if not getattr(progspace, _libcxx_printer_name, False): |
|
|
print("Loading libc++ pretty-printers.") |
|
|
gdb.printing.register_pretty_printer( |
|
|
progspace, LibcxxPrettyPrinter(_libcxx_printer_name)) |
|
|
setattr(progspace, _libcxx_printer_name, True) |
|
|
|
|
|
|
|
|
def _unregister_libcxx_printers(event): |
|
|
progspace = event.progspace |
|
|
if getattr(progspace, _libcxx_printer_name, False): |
|
|
for printer in progspace.pretty_printers: |
|
|
if getattr(printer, "name", "none") == _libcxx_printer_name: |
|
|
progspace.pretty_printers.remove(printer) |
|
|
setattr(progspace, _libcxx_printer_name, False) |
|
|
break |
|
|
|
|
|
|
|
|
def register_libcxx_printer_loader(): |
|
|
"""Register event handlers to load libc++ pretty-printers.""" |
|
|
gdb.events.new_objfile.connect(_register_libcxx_printers) |
|
|
gdb.events.clear_objfiles.connect(_unregister_libcxx_printers) |