chromium/third_party/blink/tools/gdb/blink.py

# Copyright (C) 2010, Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#     * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""GDB support for Blink types.

Add this to your gdb by amending your ~/.gdbinit as follows:
  python
  import sys
  sys.path.insert(0, "/path/to/blink/tools/gdb/")
  import blink
"""

from __future__ import print_function

import gdb
import re
import struct


def guess_string_length(ptr):
    """Guess length of string pointed by ptr.

    Returns a tuple of (length, an error message).
    """
    # Try to guess at the length.
    for i in range(0, 2048):
        try:
            if int((ptr + i).dereference()) == 0:
                return i, ''
        except RuntimeError:
            # We indexed into inaccessible memory; give up.
            return i, ' (gdb hit inaccessible memory)'
    return 256, ' (gdb found no trailing NUL)'


def ustring_to_string(ptr, length=None):
    """Convert a pointer to UTF-16 data into a Python string encoded with utf-8.

    ptr and length are both gdb.Value objects.
    If length is unspecified, will guess at the length."""
    error_message = ''
    if length is None:
        length, error_message = guess_string_length(ptr)
    else:
        length = int(length)
    char_vals = [int((ptr + i).dereference()) for i in range(length)]
    string = struct.pack('H' * length, *char_vals).decode(
        'utf-16', 'replace').encode('utf-8')
    return string + error_message.encode('utf-8')


def lstring_to_string(ptr, length=None):
    """Convert a pointer to LChar* data into a Python (non-Unicode) string.

    ptr and length are both gdb.Value objects.
    If length is unspecified, will guess at the length."""
    error_message = ''
    if length is None:
        length, error_message = guess_string_length(ptr)
    else:
        length = int(length)
    string = ''.join([chr((ptr + i).dereference()) for i in range(length)])
    return string + error_message


def dereference_member(obj):
    """Dereferences a pointer held by a Member.

    obj must be a gdb.Value. The type is not one of the cppgc Member types, the
    original object is returned back."""
    obj_typename = obj.type.strip_typedefs().name
    if not obj_typename.startswith('cppgc::internal::BasicMember'):
        return obj

    inner_type = obj.type.template_argument(0)
    decompressed_ptr = (gdb.parse_and_eval(
        "_cppgc_internal_Uncompress_Member((void*){})".format(
            obj.address)).cast(inner_type.pointer()))

    # The sentinel pointer is used when stored in a hash set to indicate
    # deleted entries.
    sentinel = gdb.parse_and_eval('cppgc::kSentinelPointer')['kSentinelValue']
    ptr_value = int(decompressed_ptr)
    if ptr_value == 0 or ptr_value == int(sentinel):
        return None

    return decompressed_ptr.dereference()


class StringPrinter(object):
    "Shared code between different string-printing classes"

    def __init__(self, val):
        self.val = val

    def display_hint(self):
        return 'string'


class UCharStringPrinter(StringPrinter):
    "Print a UChar*; we must guess at the length"

    def to_string(self):
        return ustring_to_string(self.val)


class LCharStringPrinter(StringPrinter):
    "Print a LChar*; we must guess at the length"

    def to_string(self):
        return lstring_to_string(self.val)


class WTFAtomicStringPrinter(StringPrinter):
    "Print a WTF::AtomicString"

    def to_string(self):
        return self.val['string_']


class WTFStringImplPrinter(StringPrinter):
    "Print a WTF::StringImpl"

    def get_length(self):
        return self.val['length_']

    def to_string(self):
        chars_start = self.val.address + 1
        if self.is_8bit():
            return lstring_to_string(
                chars_start.cast(gdb.lookup_type('char').pointer()),
                self.get_length())
        return ustring_to_string(
            chars_start.cast(gdb.lookup_type('UChar').pointer()),
            self.get_length())

    def is_8bit(self):
        return int(str(self.val['hash_and_flags_'])) % 2


class WTFStringPrinter(StringPrinter):
    "Print a WTF::String"

    def stringimpl_ptr(self):
        return self.val['impl_']['ptr_']

    def get_length(self):
        if not self.stringimpl_ptr():
            return 0
        return WTFStringImplPrinter(
            self.stringimpl_ptr().dereference()).get_length()

    def to_string(self):
        if not self.stringimpl_ptr():
            return '(null)'
        return self.stringimpl_ptr().dereference()


class blinkKURLPrinter(StringPrinter):
    "Print a blink::KURL"

    def to_string(self):
        return WTFAtomicStringPrinter(self.val['string_']).to_string()


class blinkLayoutUnitPrinter:
    "Print a blink::LayoutUnit"

    def __init__(self, val):
        self.val = val

    def to_string(self):
        return "%.14gpx" % (self.val['value_'] / 64.0)


class blinkFixedPointPrinter:
    "Print a blink::FixedPoint (LayoutUnit, etc.)"

    def __init__(self, val):
        self.val = val

    def to_string(self):
        return "%.14gpx" % (self.val['value_'] /
                            self.val['kFixedPointDenominator'])


class blinkLayoutPointPrinter:
    "Print a blink::LayoutPoint"

    def __init__(self, val):
        self.val = val

    def to_string(self):
        return 'LayoutPoint(%s, %s)' % (blinkLayoutUnitPrinter(
            self.val['x_']).to_string(), blinkLayoutUnitPrinter(
                self.val['y_']).to_string())


class blinkQualifiedNamePrinter(StringPrinter):
    "Print a blink::QualifiedName"

    def __init__(self, val):
        super(blinkQualifiedNamePrinter, self).__init__(val)
        self.prefix_length = 0
        self.length = 0
        if self.val['impl_']:
            self.prefix_printer = WTFStringPrinter(
                self.val['impl_']['ptr_']['prefix_']['string_'])
            self.local_name_printer = WTFStringPrinter(
                self.val['impl_']['ptr_']['local_name_']['string_'])
            self.prefix_length = self.prefix_printer.get_length()
            if self.prefix_length > 0:
                self.length = (self.prefix_length + 1 +
                               self.local_name_printer.get_length())
            else:
                self.length = self.local_name_printer.get_length()

    def get_length(self):
        return self.length

    def to_string(self):
        if self.get_length() == 0:
            return "(null)"
        else:
            if self.prefix_length > 0:
                return (self.prefix_printer.to_string() + ":" +
                        self.local_name_printer.to_string())
            else:
                return self.local_name_printer.to_string()


class BlinkPixelsAndPercentPrinter:
    "Print a blink::PixelsAndPercent value"

    def __init__(self, val):
        self.val = val

    def to_string(self):
        return "(%gpx, %g%%)" % (self.val['pixels'], self.val['percent'])


class BlinkLengthPrinter:
    """Print a blink::Length."""

    def __init__(self, val):
        self.val = val

    def to_string(self):
        ltype = self.val['type_']
        val = self.val['value_']

        quirk = ''
        if self.val['quirk_']:
            quirk = ', quirk=true'

        if ltype == 0:
            return 'Length(Auto)'
        if ltype == 1:
            return 'Length(%g%%, Percent%s)' % (val, quirk)
        if ltype == 2:
            return 'Length(%g, Fixed%s)' % (val, quirk)
        if ltype == 3:
            return 'Length(MinContent)'
        if ltype == 4:
            return 'Length(MaxContent)'
        if ltype == 5:
            return 'Length(MinIntrinsic)'
        if ltype == 6:
            return 'Length(FillAvailable)'
        if ltype == 7:
            return 'Length(FitContent)'
        if ltype == 8:
            # Would like to print pixelsAndPercent() but can't call member
            # functions - https://sourceware.org/bugzilla/show_bug.cgi?id=13326
            return 'Length(Calculated)'
        if ltype == 9:
            return 'Length(ExtendToZoom)'
        if ltype == 10:
            return 'Length(DeviceWidth)'
        if ltype == 11:
            return 'Length(DeviceHeight)'
        if ltype == 12:
            return 'Length(MaxSizeNone)'
        return 'Length(unknown type %i)' % ltype


class WTFVectorPrinter:
    """Pretty Printer for a WTF::Vector.

    The output of this pretty printer is similar to the output of std::vector's
    pretty printer, which is bundled in gcc.

    Example gdb session should look like:
    (gdb) p v
    $3 = WTF::Vector of length 7, capacity 16 = {7, 17, 27, 37, 47, 57, 67}
    (gdb) set print elements 3
    (gdb) p v
    $6 = WTF::Vector of length 7, capacity 16 = {7, 17, 27...}
    (gdb) set print array
    (gdb) p v
    $7 = WTF::Vector of length 7, capacity 16 = {
      7,
      17,
      27
      ...
    }
    (gdb) set print elements 200
    (gdb) p v
    $8 = WTF::Vector of length 7, capacity 16 = {
      7,
      17,
      27,
      37,
      47,
      57,
      67
    }
    """

    class Iterator:
        def __init__(self, start, finish):
            self.item = start
            self.finish = finish
            self.count = 0

        def __iter__(self):
            return self

        def __next__(self):
            if self.item == self.finish:
                raise StopIteration
            count = self.count
            self.count += 1
            element = dereference_member(self.item.dereference())
            self.item += 1
            return ('[%d]' % count, element)

        # Python version < 3 compatibility:
        def next(self):
            return self.__next__()

    def __init__(self, val):
        self.val = val

    def children(self):
        start = self.val['buffer_']
        return self.Iterator(start, start + self.val['size_'])

    def to_string(self):
        return ('%s of length %d, capacity %d' %
                ('WTF::Vector', self.val['size_'], self.val['capacity_']))

    def display_hint(self):
        return 'array'


class WTFHashTablePrinter:
    """Pretty printer for a WTF::HashTable.

    The output of this pretty printer is similar to the output of
    std::unordered_map's pretty printer, which is bundled with gcc.

    An example gdb session should look like:
    (gdb) print m
    $1 = {impl_ = WTF::HashTable with 2 elements = {
        ["a-start"] = 0, ["a-end"] = 1}}
    """

    class Iterator:

        def __init__(self, start, finish, is_keyval):
            self.item = start
            self.finish = finish
            self.count = 0
            self.is_keyval = is_keyval

        def __iter__(self):
            return self

        def __next__(self):
            # Loop until we find a non-empty bucket.
            while True:
                if self.item == self.finish:
                    raise StopIteration
                count = self.count
                self.count += 1
                element = dereference_member(self.item.dereference())
                self.item += 1

                # If the bucket is not empty, return it.
                # TODO(bokan): This doesn't account for HashTraits of the table
                # so may print empty/deleted buckets, depending on the type.
                if self.is_keyval:
                    derefed_key = dereference_member(element['key'])
                    if derefed_key:
                        derefed_val = dereference_member(element['value'])
                        pretty = '[%s] = %s' % (derefed_key, derefed_val)
                        return ('[%d]' % count, pretty)
                else:
                    if element:
                        pretty = '%s' % (element)
                        return ('[%d]' % count, pretty)

    def __init__(self, val):
        self.val = val

    def children(self):
        start = self.val['table_']
        # HashSets use a HashTable where the value and key are the same so the
        # iteration needs to know whether to look for an explicit key or not.
        extractor_name = self.val.type.template_argument(2).name
        is_keyval = extractor_name != 'WTF::IdentityExtractor'
        return self.Iterator(start, start + self.val['table_size_'], is_keyval)

    def to_string(self):
        return ('%s with %d elements' %
                ('WTF::HashTable', self.val['key_count_']))

    def display_hint(self):
        return 'array'


# Copied from //tools/gdb/gdb_chrome.py
def typed_ptr(ptr):
    """Prints a pointer along with its exact type.

    By default, gdb would print just the address, which takes more
    steps to interpret.
    """
    # Returning this as a cast expression surrounded by parentheses
    # makes it easier to cut+paste inside of gdb.
    return '((%s)%s)' % (ptr.dynamic_type, ptr)


class BlinkDataRefPrinter:
    def __init__(self, val):
        self.val = val

    def to_string(self):
        return 'DataRef(%s)' % (str(self.val['data_']))


class BlinkJSONValuePrinter:
    def __init__(self, val):
        self.val = val

    def to_string(self):
        s = str(
            gdb.parse_and_eval(
                "((blink::JSONValue*) %s)->ToPrettyJSONString().Utf8(0)" %
                self.val.address))
        return s.replace("\\n", "\n").replace('\\"', '"')


class CcPaintOpBufferPrinter:
    def __init__(self, val):
        self.val = val

    def to_string(self):
        return gdb.parse_and_eval(
            "blink::RecordAsJSON(*((cc::PaintOpBuffer*) %s))" %
            self.val.address)


def add_pretty_printers():
    pretty_printers = (
        (re.compile("^WTF::Vector<.*>$"), WTFVectorPrinter),
        (re.compile("^WTF::HashTable<.*>$"), WTFHashTablePrinter),
        (re.compile("^WTF::AtomicString$"), WTFAtomicStringPrinter),
        (re.compile("^WTF::String$"), WTFStringPrinter),
        (re.compile("^WTF::StringImpl$"), WTFStringImplPrinter),
        (re.compile("^blink::FixedPoint<.*>$"), blinkFixedPointPrinter),
        (re.compile("^blink::KURL$"), blinkKURLPrinter),
        (re.compile("^blink::LayoutUnit$"), blinkLayoutUnitPrinter),
        (re.compile("^blink::LayoutPoint$"), blinkLayoutPointPrinter),
        (re.compile("^blink::QualifiedName$"), blinkQualifiedNamePrinter),
        (re.compile("^blink::PixelsAndPercent$"),
         BlinkPixelsAndPercentPrinter),
        (re.compile("^blink::Length$"), BlinkLengthPrinter),
        (re.compile("^blink::DataRef<.*>$"), BlinkDataRefPrinter),
        (re.compile("^blink::JSONValue$"), BlinkJSONValuePrinter),
        (re.compile("^blink::JSONBasicValue$"), BlinkJSONValuePrinter),
        (re.compile("^blink::JSONString$"), BlinkJSONValuePrinter),
        (re.compile("^blink::JSONObject$"), BlinkJSONValuePrinter),
        (re.compile("^blink::JSONArray$"), BlinkJSONValuePrinter),
        (re.compile("^cc::PaintOpBuffer$"), CcPaintOpBufferPrinter),
    )

    def lookup_function(val):
        """Function used to load pretty printers; will be passed to GDB."""
        type = val.type
        if type.code == gdb.TYPE_CODE_REF:
            type = type.target()
        type = type.unqualified().strip_typedefs()
        tag = type.tag
        if tag:
            for function, pretty_printer in pretty_printers:
                if function.search(tag):
                    return pretty_printer(val)

        if type.code == gdb.TYPE_CODE_PTR:
            name = str(type.target().unqualified())
            if name == 'UChar':
                return UCharStringPrinter(val)
            if name == 'LChar':
                return LCharStringPrinter(val)
        return None

    gdb.pretty_printers.append(lookup_function)


add_pretty_printers()


class PrintPathToRootCommand(gdb.Command):
    """Command for printing Blink Node trees.

    Usage: printpathtoroot variable_name"""

    def __init__(self):
        super(PrintPathToRootCommand, self).__init__(
            "printpathtoroot", gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE)

    def invoke(self, arg, from_tty):
        element_type = gdb.lookup_type('blink::Element')
        node_type = gdb.lookup_type('blink::Node')
        frame = gdb.selected_frame()
        try:
            val = gdb.Frame.read_var(frame, arg)
        except:
            print("No such variable, or invalid type")
            return

        target_type = str(val.type.target().strip_typedefs())
        if target_type == str(node_type):
            stack = []
            while val:
                stack.append([
                    val,
                    val.cast(element_type.pointer()).dereference()['tag_name_']
                ])
                val = val.dereference()['parent_']

            padding = ''
            while len(stack) > 0:
                pair = stack.pop()
                print(padding, pair[1], pair[0])
                padding = padding + '  '
        else:
            print(
                'Sorry: I don\'t know how to deal with %s yet.' % target_type)


PrintPathToRootCommand()