# 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/. # Pretty-printers for SpiderMonkey's JS::Value. import struct import gdb import gdb.types import mozilla.prettyprinters from mozilla.prettyprinters import pretty_printer # Forget any printers from previous loads of this module. mozilla.prettyprinters.clear_module_printers(__name__) # See Value.h [SMDOC] JS::Value Boxing Formats for details on the JS::Value boxing # formats handled below. class Box(object): def __init__(self, asBits, jtc): self.asBits = asBits self.jtc = jtc # Value::asBits is uint64_t, but somehow the sign bit can be botched # here, even though Python integers are arbitrary precision. if self.asBits < 0: self.asBits = self.asBits + (1 << 64) # Return this value's type tag. def tag(self): raise NotImplementedError # Return this value as a 32-bit integer, double, or address. def as_uint32(self): raise NotImplementedError def as_double(self): packed = struct.pack("q", self.asBits) (unpacked,) = struct.unpack("d", packed) return unpacked def as_address(self): raise NotImplementedError class Punbox(Box): # Packed non-number boxing --- the format used on x86_64. It would be nice to # simply call Value::toInt32, etc. here, but the debugger is likely to see many # Values, and doing several inferior calls for each one seems like a bad idea. FULL_WIDTH = 64 TAG_SHIFT = 47 PAYLOAD_MASK = (1 << TAG_SHIFT) - 1 TAG_MASK = (1 << (FULL_WIDTH - TAG_SHIFT)) - 1 TAG_MAX_DOUBLE = 0x1FFF0 TAG_TYPE_MASK = 0x0000F def tag(self): tag = self.asBits >> Punbox.TAG_SHIFT if tag <= Punbox.TAG_MAX_DOUBLE: return self.jtc.DOUBLE else: return tag & Punbox.TAG_TYPE_MASK def as_uint32(self): return int(self.asBits & ((1 << 32) - 1)) def as_address(self): return gdb.Value(self.asBits & Punbox.PAYLOAD_MASK) class Nunbox(Box): TAG_SHIFT = 32 TAG_CLEAR = 0xFFFF0000 PAYLOAD_MASK = 0xFFFFFFFF TAG_TYPE_MASK = 0x0000000F def tag(self): tag = self.asBits >> Nunbox.TAG_SHIFT if tag < Nunbox.TAG_CLEAR: return self.jtc.DOUBLE return tag & Nunbox.TAG_TYPE_MASK def as_uint32(self): return int(self.asBits & Nunbox.PAYLOAD_MASK) def as_address(self): return gdb.Value(self.asBits & Nunbox.PAYLOAD_MASK) class JSValueTypeCache(object): # Cache information about the Value type for this objfile. def __init__(self, cache): # Capture the tag values. d = gdb.types.make_enum_dict(gdb.lookup_type("JSValueType")) # The enum keys are prefixed when building with some compilers (clang at # a minimum), so use a helper function to handle either key format. def get(key): val = d.get(key) if val is not None: return val return d["JSValueType::" + key] self.DOUBLE = get("JSVAL_TYPE_DOUBLE") self.INT32 = get("JSVAL_TYPE_INT32") self.UNDEFINED = get("JSVAL_TYPE_UNDEFINED") self.BOOLEAN = get("JSVAL_TYPE_BOOLEAN") self.MAGIC = get("JSVAL_TYPE_MAGIC") self.STRING = get("JSVAL_TYPE_STRING") self.SYMBOL = get("JSVAL_TYPE_SYMBOL") self.BIGINT = get("JSVAL_TYPE_BIGINT") self.NULL = get("JSVAL_TYPE_NULL") self.OBJECT = get("JSVAL_TYPE_OBJECT") # Let self.magic_names be an array whose i'th element is the name of # the i'th magic value. d = gdb.types.make_enum_dict(gdb.lookup_type("JSWhyMagic")) self.magic_names = list(range(max(d.values()) + 1)) for (k, v) in d.items(): self.magic_names[v] = k # Choose an unboxing scheme for this architecture. self.boxer = Punbox if cache.void_ptr_t.sizeof == 8 else Nunbox @pretty_printer("JS::Value") class JSValue(object): def __init__(self, value, cache): # Save the generic typecache, and create our own, if we haven't already. self.cache = cache if not cache.mod_JS_Value: cache.mod_JS_Value = JSValueTypeCache(cache) self.jtc = cache.mod_JS_Value self.value = value self.box = self.jtc.boxer(value["asBits_"], self.jtc) def to_string(self): tag = self.box.tag() if tag == self.jtc.UNDEFINED: return "$JS::UndefinedValue()" if tag == self.jtc.NULL: return "$JS::NullValue()" if tag == self.jtc.BOOLEAN: return "$JS::BooleanValue(%s)" % str(self.box.as_uint32() != 0).lower() if tag == self.jtc.MAGIC: value = self.box.as_uint32() if 0 <= value and value < len(self.jtc.magic_names): return "$JS::MagicValue(%s)" % (self.jtc.magic_names[value],) else: return "$JS::MagicValue(%d)" % (value,) if tag == self.jtc.INT32: value = self.box.as_uint32() signbit = 1 << 31 value = (value ^ signbit) - signbit return "$JS::Int32Value(%s)" % value if tag == self.jtc.DOUBLE: return "$JS::DoubleValue(%s)" % self.box.as_double() if tag == self.jtc.STRING: value = self.box.as_address().cast(self.cache.JSString_ptr_t) elif tag == self.jtc.OBJECT: value = self.box.as_address().cast(self.cache.JSObject_ptr_t) elif tag == self.jtc.SYMBOL: value = self.box.as_address().cast(self.cache.JSSymbol_ptr_t) elif tag == self.jtc.BIGINT: return "$JS::BigIntValue()" else: value = "unrecognized!" return "$JS::Value(%s)" % (value,) def is_undefined(self): return self.box.tag() == self.jtc.UNDEFINED def get_string(self): assert self.box.tag() == self.jtc.STRING return self.box.as_address().cast(self.cache.JSString_ptr_t) def get_private(self): assert self.box.tag() == self.jtc.DOUBLE return self.box.asBits