| #===----------------------------------------------------------------------===## |
| # |
| # 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 math |
| 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") |
| |
| _libcpp_big_endian = False |
| |
| 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"), |
| ("std::basic_string_view<char, std::char_traits<char> >", |
| "std::string_view"), |
| ] |
| |
| |
| 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 = next(self.child_iter) |
| 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 |
| |
| # 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 StdStringViewPrinter(object): |
| """Print a std::string_view.""" |
| |
| def __init__(self, val): |
| self.val = val |
| |
| def to_string(self): # pylint: disable=g-bad-name |
| """GDB calls this to compute the pretty-printed form.""" |
| |
| ptr = self.val["__data"] |
| length = self.val["__size"] |
| print_length = length |
| # We print more than just a simple string (i.e. we also print |
| # "of length %d"). Thus we can't use the "string" display_hint, |
| # and thus we have to handle "print elements" ourselves. |
| # For reference sake, gdb ensures limit == None or limit > 0. |
| limit = gdb.parameter("print elements") |
| if limit is not None: |
| print_length = min(print_length, limit) |
| # FIXME: Passing ISO-8859-1 here isn't always correct. |
| string = ptr.string("ISO-8859-1", "ignore", print_length) |
| if length > print_length: |
| string += "..." |
| return "std::string_view of length %d: \"%s\"" % (length, 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: |
| try: |
| 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) |
| except: |
| # Debug info for a class with virtual functions is emitted |
| # in the same place as its key function. That means that |
| # for std::shared_ptr, __shared_owners_ is emitted into |
| # into libcxx.[so|a] itself, rather than into the shared_ptr |
| # instantiation point. So if libcxx.so was built without |
| # debug info, these fields will be missing. |
| state = "count ?, weak ? (libc++ missing debug info)" |
| 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"]) |
| self.bit_count = self.val.type.template_argument(0) |
| 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 _list_it(self): |
| for bit in range(self.bit_count): |
| word = bit // self.bits_per_word |
| word_bit = bit % self.bits_per_word |
| if self.values[word] & (1 << word_bit): |
| yield ("[%d]" % bit, 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.strip_typedefs()) + "::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.strip_typedefs()) + "::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, |
| "string_view": StdStringViewPrinter, |
| "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 not None and 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 |
| # It would be ideal to get the endianness at print time, but |
| # gdb.execute clears gdb's internal wrap buffer, removing any values |
| # already generated as part of a larger data structure, and there is |
| # no python api to get the endianness. Mixed-endianness debugging |
| # rare enough that this workaround should be adequate. |
| _libcpp_big_endian = "big endian" in gdb.execute("show endian", |
| to_string=True) |
| |
| 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) |