1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import gdb
from gdbpp import GeckoPrettyPrinter
def walk_template_to_given_base(value, desired_tag_prefix):
"""Given a value of some template subclass, walk up its ancestry until we
hit the desired type, then return the appropriate value (which will then
have that type).
"""
# Base case
t = value.type
# It's possible that we're dealing with an alias template that looks like:
# template<typename Protocol>
# using ManagedContainer = nsTHashtable<nsPtrHashKey<Protocol>>;
# In which case we want to strip the indirection, and strip_typedefs()
# accomplishes this. (Disclaimer: I tried it and it worked and it didn't
# break my other use cases, if things start exploding, do reconsider.)
t = t.strip_typedefs()
if t.tag.startswith(desired_tag_prefix):
return value
for f in t.fields():
# we only care about the inheritance hierarchy
if not f.is_base_class:
continue
# This is the answer or something we're going to need to recurse into.
fv = value[f]
ft = fv.type
# slightly optimize by checking the tag rather than in the recursion
if ft.tag.startswith(desired_tag_prefix):
# found it!
return fv
return walk_template_to_given_base(fv, desired_tag_prefix)
return None
# The templates and their inheritance hierarchy form an onion of types around
# the nsTHashtable core at the center. All we care about is that nsTHashtable,
# but we register for the descendant types in order to avoid the default pretty
# printers having to unwrap those onion layers, wasting precious lines.
@GeckoPrettyPrinter("nsClassHashtable", "^nsClassHashtable<.*>$")
@GeckoPrettyPrinter("nsDataHashtable", "^nsDataHashtable<.*>$")
@GeckoPrettyPrinter("nsInterfaceHashtable", "^nsInterfaceHashtable<.*>$")
@GeckoPrettyPrinter("nsRefPtrHashtable", "^nsRefPtrHashtable<.*>$")
@GeckoPrettyPrinter("nsBaseHashtable", "^nsBaseHashtable<.*>$")
@GeckoPrettyPrinter("nsTHashtable", "^nsTHashtable<.*>$")
class thashtable_printer(object):
def __init__(self, outer_value):
self.outermost_type = outer_value.type
value = walk_template_to_given_base(outer_value, "nsTHashtable<")
self.value = value
self.entry_type = value.type.template_argument(0)
# -- Determine whether we're a hashTABLE or a hashSET
# If we're a table, the entry type will be a nsBaseHashtableET template.
# If we're a set, it will be something like nsPtrHashKey.
#
# So, assume we're a set if we're not nsBaseHashtableET<
# (It should ideally also be true that the type ends with HashKey, but
# since nsBaseHashtableET causes us to assume "mData" exists, let's
# pivot based on that.)
self.is_table = self.entry_type.tag.startswith("nsBaseHashtableET<")
# While we know that it has a field `mKeyHash` for the hash-code and
# book-keeping, and a DataType field mData for the value (if we're a
# table), the key field frustratingly varies by key type.
#
# So we want to walk its key type to figure out the field name. And we
# do mean field name. The field object is no good for subscripting the
# value unless the field was directly owned by that value's type. But
# by using a string name, we save ourselves all that fanciness.
if self.is_table:
# For nsBaseHashtableET<KeyClass, DataType>, we want the KeyClass
key_type = self.entry_type.template_argument(0)
else:
# If we're a set, our entry type is the key class already!
key_type = self.entry_type
self.key_field_name = None
for f in key_type.fields():
# No need to traverse up the type hierarchy...
if f.is_base_class:
continue
# ...just to skip the fields we know exist...
if f.name == "mKeyHash" or f.name == "mData":
continue
# ...and assume the first one we find is the key.
self.key_field_name = f.name
break
def children(self):
table = self.value["mTable"]
# mEntryCount is the number of occupied slots/entries in the table.
# We can use this to avoid doing wasted memory reads.
entryCount = table["mEntryCount"]
if entryCount == 0:
return
# The table capacity is tracked "cleverly" in terms of how many bits
# the hash needs to be shifted. CapacityFromHashShift calculates this
# quantity, but may be inlined, so we replicate the calculation here.
hashType = gdb.lookup_type("mozilla::HashNumber")
hashBits = hashType.sizeof * 8
capacity = 1 << (hashBits - table["mHashShift"])
# Pierce generation-tracking EntryStore class to get at buffer. The
# class instance always exists, but this char* may be null.
store = table["mEntryStore"]["mEntryStore"]
key_field_name = self.key_field_name
# The entry store is laid out with hashes for all possible entries
# first, followed by all the entries.
pHashes = store.cast(hashType.pointer())
pEntries = pHashes + capacity
pEntries = pEntries.cast(self.entry_type.pointer())
seenCount = 0
for i in range(0, int(capacity)):
entryHash = (pHashes + i).dereference()
# An entry hash of 0 means empty, 1 means deleted sentinel, so skip
# if that's the case.
if entryHash <= 1:
continue
entry = (pEntries + i).dereference()
yield ("%d" % i, entry[key_field_name])
if self.is_table:
yield ("%d" % i, entry["mData"])
# Stop iterating if we know there are no more occupied slots.
seenCount += 1
if seenCount >= entryCount:
break
def to_string(self):
# The most specific template type is the most interesting.
return str(self.outermost_type)
def display_hint(self):
if self.is_table:
return "map"
else:
return "array"
|