|
1 # Pretty-printers for SpiderMonkey jsvals. |
|
2 |
|
3 import gdb |
|
4 import gdb.types |
|
5 import mozilla.prettyprinters |
|
6 from mozilla.prettyprinters import pretty_printer, ptr_pretty_printer |
|
7 |
|
8 # Forget any printers from previous loads of this module. |
|
9 mozilla.prettyprinters.clear_module_printers(__name__) |
|
10 |
|
11 # Summary of the JS::Value (also known as jsval) type: |
|
12 # |
|
13 # Viewed abstractly, JS::Value is a 64-bit discriminated union, with |
|
14 # JSString *, JSObject *, IEEE 64-bit floating-point, and 32-bit integer |
|
15 # branches (and a few others). (It is not actually a C++ union; |
|
16 # 'discriminated union' just describes the overall effect.) Note that |
|
17 # JS::Value is always 64 bits long, even on 32-bit architectures. |
|
18 # |
|
19 # The ECMAScript standard specifies that ECMAScript numbers are IEEE 64-bit |
|
20 # floating-point values. A JS::Value can represent any JavaScript number |
|
21 # value directly, without referring to additional storage, or represent an |
|
22 # object, string, or other ECMAScript value, and remember which type it is. |
|
23 # This may seem surprising: how can a 64-bit type hold all the 64-bit IEEE |
|
24 # values, and still distinguish them from objects, strings, and so on, |
|
25 # which have 64-bit addresses? |
|
26 # |
|
27 # This is possible for two reasons: |
|
28 # |
|
29 # - First, ECMAScript implementations aren't required to distinguish all |
|
30 # the values the IEEE 64-bit format can represent. The IEEE format |
|
31 # specifies many bitstrings representing NaN values, while ECMAScript |
|
32 # requires only a single NaN value. This means we can use one IEEE NaN to |
|
33 # represent ECMAScript's NaN, and use all the other IEEE NaNs to |
|
34 # represent the other ECMAScript values. |
|
35 # |
|
36 # (IEEE says that any floating-point value whose 11-bit exponent field is |
|
37 # 0x7ff (all ones) and whose 52-bit fraction field is non-zero is a NaN. |
|
38 # So as long as we ensure the fraction field is non-zero, and save a NaN |
|
39 # for ECMAScript, we have 2^52 values to play with.) |
|
40 # |
|
41 # - Second, on the only 64-bit architecture we support, x86_64, only the |
|
42 # lower 48 bits of an address are significant. The upper sixteen bits are |
|
43 # required to be the sign-extension of bit 48. Furthermore, user code |
|
44 # always runs in "positive addresses": those in which bit 48 is zero. So |
|
45 # we only actually need 47 bits to store all possible object or string |
|
46 # addresses, even on 64-bit platforms. |
|
47 # |
|
48 # With a 52-bit fraction field, and 47 bits needed for the 'payload', we |
|
49 # have up to five bits left to store a 'tag' value, to indicate which |
|
50 # branch of our discriminated union is live. |
|
51 # |
|
52 # Thus, we define JS::Value representations in terms of the IEEE 64-bit |
|
53 # floating-point format: |
|
54 # |
|
55 # - Any bitstring that IEEE calls a number or an infinity represents that |
|
56 # ECMAScript number. |
|
57 # |
|
58 # - Any bitstring that IEEE calls a NaN represents either an ECMAScript NaN |
|
59 # or a non-number ECMAScript value, as determined by a tag field stored |
|
60 # towards the most significant end of the fraction field (exactly where |
|
61 # depends on the address size). If the tag field indicates that this |
|
62 # JS::Value is an object, the fraction field's least significant end |
|
63 # holds the address of a JSObject; if a string, the address of a |
|
64 # JSString; and so on. |
|
65 # |
|
66 # On the only 64-bit platform we support, x86_64, only the lower 48 bits of |
|
67 # an address are significant, and only those values whose top bit is zero |
|
68 # are used for user-space addresses. This means that x86_64 addresses are |
|
69 # effectively 47 bits long, and thus fit nicely in the available portion of |
|
70 # the fraction field. |
|
71 # |
|
72 # |
|
73 # In detail: |
|
74 # |
|
75 # - jsval (Value.h) is a typedef for JS::Value. |
|
76 # |
|
77 # - JS::Value (Value.h) is a class with a lot of methods and a single data |
|
78 # member, of type jsval_layout. |
|
79 # |
|
80 # - jsval_layout (Value.h) is a helper type for picking apart values. This |
|
81 # is always 64 bits long, with a variant for each address size (32 bits |
|
82 # or 64 bits) and endianness (little- or big-endian). |
|
83 # |
|
84 # jsval_layout is a union with 'asBits', 'asDouble', and 'asPtr' |
|
85 # branches, and an 's' branch, which is a struct that tries to break out |
|
86 # the bitfields a little for the non-double types. On 64-bit machines, |
|
87 # jsval_layout also has an 'asUIntPtr' branch. |
|
88 # |
|
89 # On 32-bit platforms, the 's' structure has a 'tag' member at the |
|
90 # exponent end of the 's' struct, and a 'payload' union at the mantissa |
|
91 # end. The 'payload' union's branches are things like JSString *, |
|
92 # JSObject *, and so on: the natural representations of the tags. |
|
93 # |
|
94 # On 64-bit platforms, the payload is 47 bits long; since C++ doesn't let |
|
95 # us declare bitfields that hold unions, we can't break it down so |
|
96 # neatly. In this case, we apply bit-shifting tricks to the 'asBits' |
|
97 # branch of the union to extract the tag. |
|
98 |
|
99 class Box(object): |
|
100 def __init__(self, asBits, jtc): |
|
101 self.asBits = asBits |
|
102 self.jtc = jtc |
|
103 # jsval_layout::asBits is uint64, but somebody botches the sign bit, even |
|
104 # though Python integers are arbitrary precision. |
|
105 if self.asBits < 0: |
|
106 self.asBits = self.asBits + (1 << 64) |
|
107 |
|
108 # Return this value's type tag. |
|
109 def tag(self): raise NotImplementedError |
|
110 |
|
111 # Return this value as a 32-bit integer, double, or address. |
|
112 def as_uint32(self): raise NotImplementedError |
|
113 def as_double(self): raise NotImplementedError |
|
114 def as_address(self): raise NotImplementedError |
|
115 |
|
116 # Packed non-number boxing --- the format used on x86_64. It would be nice to simply |
|
117 # call JSVAL_TO_INT, etc. here, but the debugger is likely to see many jsvals, and |
|
118 # doing several inferior calls for each one seems like a bad idea. |
|
119 class Punbox(Box): |
|
120 |
|
121 FULL_WIDTH = 64 |
|
122 TAG_SHIFT = 47 |
|
123 PAYLOAD_MASK = (1 << TAG_SHIFT) - 1 |
|
124 TAG_MASK = (1 << (FULL_WIDTH - TAG_SHIFT)) - 1 |
|
125 TAG_MAX_DOUBLE = 0x1fff0 |
|
126 TAG_TYPE_MASK = 0x0000f |
|
127 |
|
128 def tag(self): |
|
129 tag = self.asBits >> Punbox.TAG_SHIFT |
|
130 if tag <= Punbox.TAG_MAX_DOUBLE: |
|
131 return self.jtc.DOUBLE |
|
132 else: |
|
133 return tag & Punbox.TAG_TYPE_MASK |
|
134 |
|
135 def as_uint32(self): return int(self.asBits & ((1 << 32) - 1)) |
|
136 def as_address(self): return gdb.Value(self.asBits & Punbox.PAYLOAD_MASK) |
|
137 |
|
138 class Nunbox(Box): |
|
139 TAG_SHIFT = 32 |
|
140 TAG_CLEAR = 0xffff0000 |
|
141 PAYLOAD_MASK = 0xffffffff |
|
142 TAG_TYPE_MASK = 0x0000000f |
|
143 |
|
144 def tag(self): |
|
145 tag = self.asBits >> Nunbox.TAG_SHIFT |
|
146 if tag < Nunbox.TAG_CLEAR: |
|
147 return self.jtc.DOUBLE |
|
148 return tag & Nunbox.TAG_TYPE_MASK |
|
149 |
|
150 def as_uint32(self): return int(self.asBits & Nunbox.PAYLOAD_MASK) |
|
151 def as_address(self): return gdb.Value(self.asBits & Nunbox.PAYLOAD_MASK) |
|
152 |
|
153 # Cache information about the jsval type for this objfile. |
|
154 class jsvalTypeCache(object): |
|
155 def __init__(self, cache): |
|
156 # Capture the tag values. |
|
157 d = gdb.types.make_enum_dict(gdb.lookup_type('JSValueType')) |
|
158 self.DOUBLE = d['JSVAL_TYPE_DOUBLE'] |
|
159 self.INT32 = d['JSVAL_TYPE_INT32'] |
|
160 self.UNDEFINED = d['JSVAL_TYPE_UNDEFINED'] |
|
161 self.BOOLEAN = d['JSVAL_TYPE_BOOLEAN'] |
|
162 self.MAGIC = d['JSVAL_TYPE_MAGIC'] |
|
163 self.STRING = d['JSVAL_TYPE_STRING'] |
|
164 self.NULL = d['JSVAL_TYPE_NULL'] |
|
165 self.OBJECT = d['JSVAL_TYPE_OBJECT'] |
|
166 |
|
167 # Let self.magic_names be an array whose i'th element is the name of |
|
168 # the i'th magic value. |
|
169 d = gdb.types.make_enum_dict(gdb.lookup_type('JSWhyMagic')) |
|
170 self.magic_names = range(max(d.itervalues()) + 1) |
|
171 for (k,v) in d.items(): self.magic_names[v] = k |
|
172 |
|
173 # Choose an unboxing scheme for this architecture. |
|
174 self.boxer = Punbox if cache.void_ptr_t.sizeof == 8 else Nunbox |
|
175 |
|
176 @pretty_printer('jsval_layout') |
|
177 class jsval_layout(object): |
|
178 def __init__(self, value, cache): |
|
179 # Save the generic typecache, and create our own, if we haven't already. |
|
180 self.cache = cache |
|
181 if not cache.mod_jsval: |
|
182 cache.mod_jsval = jsvalTypeCache(cache) |
|
183 self.jtc = cache.mod_jsval |
|
184 |
|
185 self.value = value |
|
186 self.box = self.jtc.boxer(value['asBits'], self.jtc) |
|
187 |
|
188 def to_string(self): |
|
189 tag = self.box.tag() |
|
190 if tag == self.jtc.INT32: |
|
191 value = self.box.as_uint32() |
|
192 signbit = 1 << 31 |
|
193 value = (value ^ signbit) - signbit |
|
194 elif tag == self.jtc.UNDEFINED: |
|
195 return 'JSVAL_VOID' |
|
196 elif tag == self.jtc.BOOLEAN: |
|
197 return 'JSVAL_TRUE' if self.box.as_uint32() else 'JSVAL_FALSE' |
|
198 elif tag == self.jtc.MAGIC: |
|
199 value = self.box.as_uint32() |
|
200 if 0 <= value and value < len(self.jtc.magic_names): |
|
201 return '$jsmagic(%s)' % (self.jtc.magic_names[value],) |
|
202 else: |
|
203 return '$jsmagic(%d)' % (value,) |
|
204 elif tag == self.jtc.STRING: |
|
205 value = self.box.as_address().cast(self.cache.JSString_ptr_t) |
|
206 elif tag == self.jtc.NULL: |
|
207 return 'JSVAL_NULL' |
|
208 elif tag == self.jtc.OBJECT: |
|
209 value = self.box.as_address().cast(self.cache.JSObject_ptr_t) |
|
210 elif tag == self.jtc.DOUBLE: |
|
211 value = self.value['asDouble'] |
|
212 else: |
|
213 return '$jsval(unrecognized!)' |
|
214 return '$jsval(%s)' % (value,) |
|
215 |
|
216 @pretty_printer('JS::Value') |
|
217 class JSValue(object): |
|
218 def __new__(cls, value, cache): |
|
219 return jsval_layout(value['data'], cache) |