michael@0: # Pretty-printers for SpiderMonkey jsvals. michael@0: michael@0: import gdb michael@0: import gdb.types michael@0: import mozilla.prettyprinters michael@0: from mozilla.prettyprinters import pretty_printer, ptr_pretty_printer michael@0: michael@0: # Forget any printers from previous loads of this module. michael@0: mozilla.prettyprinters.clear_module_printers(__name__) michael@0: michael@0: # Summary of the JS::Value (also known as jsval) type: michael@0: # michael@0: # Viewed abstractly, JS::Value is a 64-bit discriminated union, with michael@0: # JSString *, JSObject *, IEEE 64-bit floating-point, and 32-bit integer michael@0: # branches (and a few others). (It is not actually a C++ union; michael@0: # 'discriminated union' just describes the overall effect.) Note that michael@0: # JS::Value is always 64 bits long, even on 32-bit architectures. michael@0: # michael@0: # The ECMAScript standard specifies that ECMAScript numbers are IEEE 64-bit michael@0: # floating-point values. A JS::Value can represent any JavaScript number michael@0: # value directly, without referring to additional storage, or represent an michael@0: # object, string, or other ECMAScript value, and remember which type it is. michael@0: # This may seem surprising: how can a 64-bit type hold all the 64-bit IEEE michael@0: # values, and still distinguish them from objects, strings, and so on, michael@0: # which have 64-bit addresses? michael@0: # michael@0: # This is possible for two reasons: michael@0: # michael@0: # - First, ECMAScript implementations aren't required to distinguish all michael@0: # the values the IEEE 64-bit format can represent. The IEEE format michael@0: # specifies many bitstrings representing NaN values, while ECMAScript michael@0: # requires only a single NaN value. This means we can use one IEEE NaN to michael@0: # represent ECMAScript's NaN, and use all the other IEEE NaNs to michael@0: # represent the other ECMAScript values. michael@0: # michael@0: # (IEEE says that any floating-point value whose 11-bit exponent field is michael@0: # 0x7ff (all ones) and whose 52-bit fraction field is non-zero is a NaN. michael@0: # So as long as we ensure the fraction field is non-zero, and save a NaN michael@0: # for ECMAScript, we have 2^52 values to play with.) michael@0: # michael@0: # - Second, on the only 64-bit architecture we support, x86_64, only the michael@0: # lower 48 bits of an address are significant. The upper sixteen bits are michael@0: # required to be the sign-extension of bit 48. Furthermore, user code michael@0: # always runs in "positive addresses": those in which bit 48 is zero. So michael@0: # we only actually need 47 bits to store all possible object or string michael@0: # addresses, even on 64-bit platforms. michael@0: # michael@0: # With a 52-bit fraction field, and 47 bits needed for the 'payload', we michael@0: # have up to five bits left to store a 'tag' value, to indicate which michael@0: # branch of our discriminated union is live. michael@0: # michael@0: # Thus, we define JS::Value representations in terms of the IEEE 64-bit michael@0: # floating-point format: michael@0: # michael@0: # - Any bitstring that IEEE calls a number or an infinity represents that michael@0: # ECMAScript number. michael@0: # michael@0: # - Any bitstring that IEEE calls a NaN represents either an ECMAScript NaN michael@0: # or a non-number ECMAScript value, as determined by a tag field stored michael@0: # towards the most significant end of the fraction field (exactly where michael@0: # depends on the address size). If the tag field indicates that this michael@0: # JS::Value is an object, the fraction field's least significant end michael@0: # holds the address of a JSObject; if a string, the address of a michael@0: # JSString; and so on. michael@0: # michael@0: # On the only 64-bit platform we support, x86_64, only the lower 48 bits of michael@0: # an address are significant, and only those values whose top bit is zero michael@0: # are used for user-space addresses. This means that x86_64 addresses are michael@0: # effectively 47 bits long, and thus fit nicely in the available portion of michael@0: # the fraction field. michael@0: # michael@0: # michael@0: # In detail: michael@0: # michael@0: # - jsval (Value.h) is a typedef for JS::Value. michael@0: # michael@0: # - JS::Value (Value.h) is a class with a lot of methods and a single data michael@0: # member, of type jsval_layout. michael@0: # michael@0: # - jsval_layout (Value.h) is a helper type for picking apart values. This michael@0: # is always 64 bits long, with a variant for each address size (32 bits michael@0: # or 64 bits) and endianness (little- or big-endian). michael@0: # michael@0: # jsval_layout is a union with 'asBits', 'asDouble', and 'asPtr' michael@0: # branches, and an 's' branch, which is a struct that tries to break out michael@0: # the bitfields a little for the non-double types. On 64-bit machines, michael@0: # jsval_layout also has an 'asUIntPtr' branch. michael@0: # michael@0: # On 32-bit platforms, the 's' structure has a 'tag' member at the michael@0: # exponent end of the 's' struct, and a 'payload' union at the mantissa michael@0: # end. The 'payload' union's branches are things like JSString *, michael@0: # JSObject *, and so on: the natural representations of the tags. michael@0: # michael@0: # On 64-bit platforms, the payload is 47 bits long; since C++ doesn't let michael@0: # us declare bitfields that hold unions, we can't break it down so michael@0: # neatly. In this case, we apply bit-shifting tricks to the 'asBits' michael@0: # branch of the union to extract the tag. michael@0: michael@0: class Box(object): michael@0: def __init__(self, asBits, jtc): michael@0: self.asBits = asBits michael@0: self.jtc = jtc michael@0: # jsval_layout::asBits is uint64, but somebody botches the sign bit, even michael@0: # though Python integers are arbitrary precision. michael@0: if self.asBits < 0: michael@0: self.asBits = self.asBits + (1 << 64) michael@0: michael@0: # Return this value's type tag. michael@0: def tag(self): raise NotImplementedError michael@0: michael@0: # Return this value as a 32-bit integer, double, or address. michael@0: def as_uint32(self): raise NotImplementedError michael@0: def as_double(self): raise NotImplementedError michael@0: def as_address(self): raise NotImplementedError michael@0: michael@0: # Packed non-number boxing --- the format used on x86_64. It would be nice to simply michael@0: # call JSVAL_TO_INT, etc. here, but the debugger is likely to see many jsvals, and michael@0: # doing several inferior calls for each one seems like a bad idea. michael@0: class Punbox(Box): michael@0: michael@0: FULL_WIDTH = 64 michael@0: TAG_SHIFT = 47 michael@0: PAYLOAD_MASK = (1 << TAG_SHIFT) - 1 michael@0: TAG_MASK = (1 << (FULL_WIDTH - TAG_SHIFT)) - 1 michael@0: TAG_MAX_DOUBLE = 0x1fff0 michael@0: TAG_TYPE_MASK = 0x0000f michael@0: michael@0: def tag(self): michael@0: tag = self.asBits >> Punbox.TAG_SHIFT michael@0: if tag <= Punbox.TAG_MAX_DOUBLE: michael@0: return self.jtc.DOUBLE michael@0: else: michael@0: return tag & Punbox.TAG_TYPE_MASK michael@0: michael@0: def as_uint32(self): return int(self.asBits & ((1 << 32) - 1)) michael@0: def as_address(self): return gdb.Value(self.asBits & Punbox.PAYLOAD_MASK) michael@0: michael@0: class Nunbox(Box): michael@0: TAG_SHIFT = 32 michael@0: TAG_CLEAR = 0xffff0000 michael@0: PAYLOAD_MASK = 0xffffffff michael@0: TAG_TYPE_MASK = 0x0000000f michael@0: michael@0: def tag(self): michael@0: tag = self.asBits >> Nunbox.TAG_SHIFT michael@0: if tag < Nunbox.TAG_CLEAR: michael@0: return self.jtc.DOUBLE michael@0: return tag & Nunbox.TAG_TYPE_MASK michael@0: michael@0: def as_uint32(self): return int(self.asBits & Nunbox.PAYLOAD_MASK) michael@0: def as_address(self): return gdb.Value(self.asBits & Nunbox.PAYLOAD_MASK) michael@0: michael@0: # Cache information about the jsval type for this objfile. michael@0: class jsvalTypeCache(object): michael@0: def __init__(self, cache): michael@0: # Capture the tag values. michael@0: d = gdb.types.make_enum_dict(gdb.lookup_type('JSValueType')) michael@0: self.DOUBLE = d['JSVAL_TYPE_DOUBLE'] michael@0: self.INT32 = d['JSVAL_TYPE_INT32'] michael@0: self.UNDEFINED = d['JSVAL_TYPE_UNDEFINED'] michael@0: self.BOOLEAN = d['JSVAL_TYPE_BOOLEAN'] michael@0: self.MAGIC = d['JSVAL_TYPE_MAGIC'] michael@0: self.STRING = d['JSVAL_TYPE_STRING'] michael@0: self.NULL = d['JSVAL_TYPE_NULL'] michael@0: self.OBJECT = d['JSVAL_TYPE_OBJECT'] michael@0: michael@0: # Let self.magic_names be an array whose i'th element is the name of michael@0: # the i'th magic value. michael@0: d = gdb.types.make_enum_dict(gdb.lookup_type('JSWhyMagic')) michael@0: self.magic_names = range(max(d.itervalues()) + 1) michael@0: for (k,v) in d.items(): self.magic_names[v] = k michael@0: michael@0: # Choose an unboxing scheme for this architecture. michael@0: self.boxer = Punbox if cache.void_ptr_t.sizeof == 8 else Nunbox michael@0: michael@0: @pretty_printer('jsval_layout') michael@0: class jsval_layout(object): michael@0: def __init__(self, value, cache): michael@0: # Save the generic typecache, and create our own, if we haven't already. michael@0: self.cache = cache michael@0: if not cache.mod_jsval: michael@0: cache.mod_jsval = jsvalTypeCache(cache) michael@0: self.jtc = cache.mod_jsval michael@0: michael@0: self.value = value michael@0: self.box = self.jtc.boxer(value['asBits'], self.jtc) michael@0: michael@0: def to_string(self): michael@0: tag = self.box.tag() michael@0: if tag == self.jtc.INT32: michael@0: value = self.box.as_uint32() michael@0: signbit = 1 << 31 michael@0: value = (value ^ signbit) - signbit michael@0: elif tag == self.jtc.UNDEFINED: michael@0: return 'JSVAL_VOID' michael@0: elif tag == self.jtc.BOOLEAN: michael@0: return 'JSVAL_TRUE' if self.box.as_uint32() else 'JSVAL_FALSE' michael@0: elif tag == self.jtc.MAGIC: michael@0: value = self.box.as_uint32() michael@0: if 0 <= value and value < len(self.jtc.magic_names): michael@0: return '$jsmagic(%s)' % (self.jtc.magic_names[value],) michael@0: else: michael@0: return '$jsmagic(%d)' % (value,) michael@0: elif tag == self.jtc.STRING: michael@0: value = self.box.as_address().cast(self.cache.JSString_ptr_t) michael@0: elif tag == self.jtc.NULL: michael@0: return 'JSVAL_NULL' michael@0: elif tag == self.jtc.OBJECT: michael@0: value = self.box.as_address().cast(self.cache.JSObject_ptr_t) michael@0: elif tag == self.jtc.DOUBLE: michael@0: value = self.value['asDouble'] michael@0: else: michael@0: return '$jsval(unrecognized!)' michael@0: return '$jsval(%s)' % (value,) michael@0: michael@0: @pretty_printer('JS::Value') michael@0: class JSValue(object): michael@0: def __new__(cls, value, cache): michael@0: return jsval_layout(value['data'], cache)