webkit.py revision 81bc750723a18f21cd17d1b173cd2a4dda9cea6e
1# Copyright (C) 2010, Google Inc. All rights reserved.
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions are
5# met:
6#
7#     * Redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer.
9#     * Redistributions in binary form must reproduce the above
10# copyright notice, this list of conditions and the following disclaimer
11# in the documentation and/or other materials provided with the
12# distribution.
13#     * Neither the name of Google Inc. nor the names of its
14# contributors may be used to endorse or promote products derived from
15# this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29"""GDB support for WebKit types.
30
31Add this to your gdb by amending your ~/.gdbinit as follows:
32  python
33  import sys
34  sys.path.insert(0, "/path/to/tools/gdb/")
35  import webkit
36"""
37
38import gdb
39import re
40import struct
41
42
43def ustring_to_string(ptr, length=None):
44    """Convert a pointer to UTF-16 data into a Python Unicode string.
45
46    ptr and length are both gdb.Value objects.
47    If length is unspecified, will guess at the length."""
48    extra = ''
49    if length is None:
50        # Try to guess at the length.
51        for i in xrange(0, 2048):
52            try:
53                if int((ptr + i).dereference()) == 0:
54                    length = i
55                    break
56            except RuntimeError:
57                # We indexed into inaccessible memory; give up.
58                length = i
59                extra = u' (gdb hit inaccessible memory)'
60                break
61        if length is None:
62            length = 256
63            extra = u' (gdb found no trailing NUL)'
64    else:
65        length = int(length)
66
67    char_vals = [int((ptr + i).dereference()) for i in xrange(length)]
68    string = struct.pack('H' * length, *char_vals).decode('utf-16', 'replace')
69
70    return string + extra
71
72
73class StringPrinter(object):
74    "Shared code between different string-printing classes"
75    def __init__(self, val):
76        self.val = val
77
78    def display_hint(self):
79        return 'string'
80
81
82class UCharStringPrinter(StringPrinter):
83    "Print a UChar*; we must guess at the length"
84    def to_string(self):
85        return ustring_to_string(self.val)
86
87
88class WTFAtomicStringPrinter(StringPrinter):
89    "Print a WTF::AtomicString"
90    def to_string(self):
91        return self.val['m_string']
92
93
94class WTFCStringPrinter(StringPrinter):
95    "Print a WTF::CString"
96    def to_string(self):
97        # The CString holds a buffer, which is a refptr to a WTF::Vector of chars.
98        vector = self.val['m_buffer']['m_ptr']['m_vector']
99        # The vector has two more layers of buffer members.
100        return vector['m_buffer']['m_buffer']
101
102
103class WTFStringPrinter(StringPrinter):
104    "Print a WTF::String"
105    def get_length(self):
106        if not self.val['m_impl']['m_ptr']:
107            return 0
108        return self.val['m_impl']['m_ptr']['m_length']
109
110    def to_string(self):
111        if self.get_length() == 0:
112            return '(null)'
113
114        return ustring_to_string(self.val['m_impl']['m_ptr']['m_data'],
115                                 self.get_length())
116
117
118class JSCUStringPrinter(StringPrinter):
119    "Print a JSC::UString"
120    def get_length(self):
121        if not self.val['m_impl']['m_ptr']:
122            return 0
123        return self.val['m_impl']['m_ptr']['m_length']
124
125    def to_string(self):
126        if self.get_length() == 0:
127            return ''
128
129        return ustring_to_string(self.val['m_impl']['m_ptr']['m_data'],
130                                 self.get_length())
131
132
133class JSCIdentifierPrinter(StringPrinter):
134    "Print a JSC::Identifier"
135    def to_string(self):
136        return JSCUStringPrinter(self.val['m_string']).to_string()
137
138
139class JSCJSStringPrinter(StringPrinter):
140    "Print a JSC::JSString"
141    def to_string(self):
142        if self.val['m_length'] == 0:
143            return ''
144
145        return JSCUStringPrinter(self.val['m_value']).to_string()
146
147
148class WebCoreKURLGooglePrivatePrinter(StringPrinter):
149    "Print a WebCore::KURLGooglePrivate"
150    def to_string(self):
151        return WTFCStringPrinter(self.val['m_utf8']).to_string()
152
153
154class WebCoreQualifiedNamePrinter(StringPrinter):
155    "Print a WebCore::QualifiedName"
156
157    def __init__(self, val):
158        super(WebCoreQualifiedNamePrinter, self).__init__(val)
159        self.prefix_length = 0
160        self.length = 0
161        if self.val['m_impl']:
162            self.prefix_printer = WTFStringPrinter(
163                self.val['m_impl']['m_prefix']['m_string'])
164            self.local_name_printer = WTFStringPrinter(
165                self.val['m_impl']['m_localName']['m_string'])
166            self.prefix_length = self.prefix_printer.get_length()
167            if self.prefix_length > 0:
168                self.length = (self.prefix_length + 1 +
169                    self.local_name_printer.get_length())
170            else:
171                self.length = self.local_name_printer.get_length()
172
173    def get_length(self):
174        return self.length
175
176    def to_string(self):
177        if self.get_length() == 0:
178            return "(null)"
179        else:
180            if self.prefix_length > 0:
181                return (self.prefix_printer.to_string() + ":" +
182                    self.local_name_printer.to_string())
183            else:
184                return self.local_name_printer.to_string()
185
186
187class WTFVectorPrinter:
188    """Pretty Printer for a WTF::Vector.
189
190    The output of this pretty printer is similar to the output of std::vector's
191    pretty printer, which is bundled in gcc.
192
193    Example gdb session should look like:
194    (gdb) p v
195    $3 = WTF::Vector of length 7, capacity 16 = {7, 17, 27, 37, 47, 57, 67}
196    (gdb) set print elements 3
197    (gdb) p v
198    $6 = WTF::Vector of length 7, capacity 16 = {7, 17, 27...}
199    (gdb) set print array
200    (gdb) p v
201    $7 = WTF::Vector of length 7, capacity 16 = {
202      7,
203      17,
204      27
205      ...
206    }
207    (gdb) set print elements 200
208    (gdb) p v
209    $8 = WTF::Vector of length 7, capacity 16 = {
210      7,
211      17,
212      27,
213      37,
214      47,
215      57,
216      67
217    }
218    """
219
220    class Iterator:
221        def __init__(self, start, finish):
222            self.item = start
223            self.finish = finish
224            self.count = 0
225
226        def __iter__(self):
227            return self
228
229        def next(self):
230            if self.item == self.finish:
231                raise StopIteration
232            count = self.count
233            self.count += 1
234            element = self.item.dereference()
235            self.item += 1
236            return ('[%d]' % count, element)
237
238    def __init__(self, val):
239        self.val = val
240
241    def children(self):
242        start = self.val['m_buffer']['m_buffer']
243        return self.Iterator(start, start + self.val['m_size'])
244
245    def to_string(self):
246        return ('%s of length %d, capacity %d'
247                % ('WTF::Vector', self.val['m_size'], self.val['m_buffer']['m_capacity']))
248
249    def display_hint(self):
250        return 'array'
251
252def add_pretty_printers():
253    pretty_printers = (
254        (re.compile("^WTF::Vector<.*>$"), WTFVectorPrinter),
255        (re.compile("^WTF::AtomicString$"), WTFAtomicStringPrinter),
256        (re.compile("^WTF::CString$"), WTFCStringPrinter),
257        (re.compile("^WTF::String$"), WTFStringPrinter),
258        (re.compile("^WebCore::KURLGooglePrivate$"), WebCoreKURLGooglePrivatePrinter),
259        (re.compile("^WebCore::QualifiedName$"), WebCoreQualifiedNamePrinter),
260        (re.compile("^JSC::UString$"), JSCUStringPrinter),
261        (re.compile("^JSC::Identifier$"), JSCIdentifierPrinter),
262        (re.compile("^JSC::JSString$"), JSCJSStringPrinter),
263    )
264
265    def lookup_function(val):
266        """Function used to load pretty printers; will be passed to GDB."""
267        type = val.type
268        if type.code == gdb.TYPE_CODE_REF:
269            type = type.target()
270        type = type.unqualified().strip_typedefs()
271        tag = type.tag
272        if tag:
273            for function, pretty_printer in pretty_printers:
274                if function.search(tag):
275                    return pretty_printer(val)
276
277        if type.code == gdb.TYPE_CODE_PTR:
278            name = str(type.target().unqualified())
279            if name == 'UChar':
280                return UCharStringPrinter(val)
281        return None
282
283    gdb.pretty_printers.append(lookup_function)
284
285
286add_pretty_printers()
287
288
289class PrintPathToRootCommand(gdb.Command):
290    """Command for printing WebKit Node trees.
291
292    Usage: printpathtoroot variable_name"""
293
294    def __init__(self):
295        super(PrintPathToRootCommand, self).__init__("printpathtoroot",
296            gdb.COMMAND_SUPPORT,
297            gdb.COMPLETE_NONE)
298
299    def invoke(self, arg, from_tty):
300        element_type = gdb.lookup_type('WebCore::Element')
301        node_type = gdb.lookup_type('WebCore::Node')
302        frame = gdb.selected_frame()
303        try:
304            val = gdb.Frame.read_var(frame, arg)
305        except:
306            print "No such variable, or invalid type"
307            return
308
309        target_type = str(val.type.target().strip_typedefs())
310        if target_type == str(node_type):
311            stack = []
312            while val:
313                stack.append([val,
314                    val.cast(element_type.pointer()).dereference()['m_tagName']])
315                val = val.dereference()['m_parent']
316
317            padding = ''
318            while len(stack) > 0:
319                pair = stack.pop()
320                print padding, pair[1], pair[0]
321                padding = padding + '  '
322        else:
323            print 'Sorry: I don\'t know how to deal with %s yet.' % target_type
324
325
326PrintPathToRootCommand()
327