js/src/gdb/mozilla/jsval.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/gdb/mozilla/jsval.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,219 @@
     1.4 +# Pretty-printers for SpiderMonkey jsvals.
     1.5 +
     1.6 +import gdb
     1.7 +import gdb.types
     1.8 +import mozilla.prettyprinters
     1.9 +from mozilla.prettyprinters import pretty_printer, ptr_pretty_printer
    1.10 +
    1.11 +# Forget any printers from previous loads of this module.
    1.12 +mozilla.prettyprinters.clear_module_printers(__name__)
    1.13 +
    1.14 +# Summary of the JS::Value (also known as jsval) type:
    1.15 +#
    1.16 +# Viewed abstractly, JS::Value is a 64-bit discriminated union, with
    1.17 +# JSString *, JSObject *, IEEE 64-bit floating-point, and 32-bit integer
    1.18 +# branches (and a few others). (It is not actually a C++ union;
    1.19 +# 'discriminated union' just describes the overall effect.) Note that
    1.20 +# JS::Value is always 64 bits long, even on 32-bit architectures.
    1.21 +#
    1.22 +# The ECMAScript standard specifies that ECMAScript numbers are IEEE 64-bit
    1.23 +# floating-point values. A JS::Value can represent any JavaScript number
    1.24 +# value directly, without referring to additional storage, or represent an
    1.25 +# object, string, or other ECMAScript value, and remember which type it is.
    1.26 +# This may seem surprising: how can a 64-bit type hold all the 64-bit IEEE
    1.27 +# values, and still distinguish them from objects, strings, and so on,
    1.28 +# which have 64-bit addresses?
    1.29 +#
    1.30 +# This is possible for two reasons:
    1.31 +#
    1.32 +# - First, ECMAScript implementations aren't required to distinguish all
    1.33 +#   the values the IEEE 64-bit format can represent. The IEEE format
    1.34 +#   specifies many bitstrings representing NaN values, while ECMAScript
    1.35 +#   requires only a single NaN value. This means we can use one IEEE NaN to
    1.36 +#   represent ECMAScript's NaN, and use all the other IEEE NaNs to
    1.37 +#   represent the other ECMAScript values.
    1.38 +#
    1.39 +#   (IEEE says that any floating-point value whose 11-bit exponent field is
    1.40 +#   0x7ff (all ones) and whose 52-bit fraction field is non-zero is a NaN.
    1.41 +#   So as long as we ensure the fraction field is non-zero, and save a NaN
    1.42 +#   for ECMAScript, we have 2^52 values to play with.)
    1.43 +#
    1.44 +# - Second, on the only 64-bit architecture we support, x86_64, only the
    1.45 +#   lower 48 bits of an address are significant. The upper sixteen bits are
    1.46 +#   required to be the sign-extension of bit 48. Furthermore, user code
    1.47 +#   always runs in "positive addresses": those in which bit 48 is zero. So
    1.48 +#   we only actually need 47 bits to store all possible object or string
    1.49 +#   addresses, even on 64-bit platforms.
    1.50 +#
    1.51 +# With a 52-bit fraction field, and 47 bits needed for the 'payload', we
    1.52 +# have up to five bits left to store a 'tag' value, to indicate which
    1.53 +# branch of our discriminated union is live.
    1.54 +#
    1.55 +# Thus, we define JS::Value representations in terms of the IEEE 64-bit
    1.56 +# floating-point format:
    1.57 +#
    1.58 +# - Any bitstring that IEEE calls a number or an infinity represents that
    1.59 +#   ECMAScript number.
    1.60 +#
    1.61 +# - Any bitstring that IEEE calls a NaN represents either an ECMAScript NaN
    1.62 +#   or a non-number ECMAScript value, as determined by a tag field stored
    1.63 +#   towards the most significant end of the fraction field (exactly where
    1.64 +#   depends on the address size). If the tag field indicates that this
    1.65 +#   JS::Value is an object, the fraction field's least significant end
    1.66 +#   holds the address of a JSObject; if a string, the address of a
    1.67 +#   JSString; and so on.
    1.68 +#
    1.69 +# On the only 64-bit platform we support, x86_64, only the lower 48 bits of
    1.70 +# an address are significant, and only those values whose top bit is zero
    1.71 +# are used for user-space addresses. This means that x86_64 addresses are
    1.72 +# effectively 47 bits long, and thus fit nicely in the available portion of
    1.73 +# the fraction field.
    1.74 +#
    1.75 +#
    1.76 +# In detail:
    1.77 +#
    1.78 +# - jsval (Value.h) is a typedef for JS::Value.
    1.79 +#
    1.80 +# - JS::Value (Value.h) is a class with a lot of methods and a single data
    1.81 +#   member, of type jsval_layout.
    1.82 +#
    1.83 +# - jsval_layout (Value.h) is a helper type for picking apart values. This
    1.84 +#   is always 64 bits long, with a variant for each address size (32 bits
    1.85 +#   or 64 bits) and endianness (little- or big-endian).
    1.86 +#
    1.87 +#   jsval_layout is a union with 'asBits', 'asDouble', and 'asPtr'
    1.88 +#   branches, and an 's' branch, which is a struct that tries to break out
    1.89 +#   the bitfields a little for the non-double types. On 64-bit machines,
    1.90 +#   jsval_layout also has an 'asUIntPtr' branch.
    1.91 +#
    1.92 +#   On 32-bit platforms, the 's' structure has a 'tag' member at the
    1.93 +#   exponent end of the 's' struct, and a 'payload' union at the mantissa
    1.94 +#   end. The 'payload' union's branches are things like JSString *,
    1.95 +#   JSObject *, and so on: the natural representations of the tags.
    1.96 +#
    1.97 +#   On 64-bit platforms, the payload is 47 bits long; since C++ doesn't let
    1.98 +#   us declare bitfields that hold unions, we can't break it down so
    1.99 +#   neatly. In this case, we apply bit-shifting tricks to the 'asBits'
   1.100 +#   branch of the union to extract the tag.
   1.101 +
   1.102 +class Box(object):
   1.103 +    def __init__(self, asBits, jtc):
   1.104 +        self.asBits = asBits
   1.105 +        self.jtc = jtc
   1.106 +        # jsval_layout::asBits is uint64, but somebody botches the sign bit, even
   1.107 +        # though Python integers are arbitrary precision.
   1.108 +        if self.asBits < 0:
   1.109 +            self.asBits = self.asBits + (1 << 64)
   1.110 +
   1.111 +    # Return this value's type tag.
   1.112 +    def tag(self): raise NotImplementedError
   1.113 +
   1.114 +    # Return this value as a 32-bit integer, double, or address.
   1.115 +    def as_uint32(self): raise NotImplementedError
   1.116 +    def as_double(self): raise NotImplementedError
   1.117 +    def as_address(self): raise NotImplementedError
   1.118 +
   1.119 +# Packed non-number boxing --- the format used on x86_64. It would be nice to simply
   1.120 +# call JSVAL_TO_INT, etc. here, but the debugger is likely to see many jsvals, and
   1.121 +# doing several inferior calls for each one seems like a bad idea.
   1.122 +class Punbox(Box):
   1.123 +
   1.124 +    FULL_WIDTH     = 64
   1.125 +    TAG_SHIFT      = 47
   1.126 +    PAYLOAD_MASK   = (1 << TAG_SHIFT) - 1
   1.127 +    TAG_MASK       = (1 << (FULL_WIDTH - TAG_SHIFT)) - 1
   1.128 +    TAG_MAX_DOUBLE = 0x1fff0
   1.129 +    TAG_TYPE_MASK  = 0x0000f
   1.130 +
   1.131 +    def tag(self):
   1.132 +        tag = self.asBits >> Punbox.TAG_SHIFT
   1.133 +        if tag <= Punbox.TAG_MAX_DOUBLE:
   1.134 +            return self.jtc.DOUBLE
   1.135 +        else:
   1.136 +            return tag & Punbox.TAG_TYPE_MASK
   1.137 +
   1.138 +    def as_uint32(self): return int(self.asBits & ((1 << 32) - 1))
   1.139 +    def as_address(self): return gdb.Value(self.asBits & Punbox.PAYLOAD_MASK)
   1.140 +
   1.141 +class Nunbox(Box):
   1.142 +    TAG_SHIFT      = 32
   1.143 +    TAG_CLEAR      = 0xffff0000
   1.144 +    PAYLOAD_MASK   = 0xffffffff
   1.145 +    TAG_TYPE_MASK  = 0x0000000f
   1.146 +
   1.147 +    def tag(self):
   1.148 +        tag = self.asBits >> Nunbox.TAG_SHIFT
   1.149 +        if tag < Nunbox.TAG_CLEAR:
   1.150 +            return self.jtc.DOUBLE
   1.151 +        return tag & Nunbox.TAG_TYPE_MASK
   1.152 +
   1.153 +    def as_uint32(self): return int(self.asBits & Nunbox.PAYLOAD_MASK)
   1.154 +    def as_address(self): return gdb.Value(self.asBits & Nunbox.PAYLOAD_MASK)
   1.155 +
   1.156 +# Cache information about the jsval type for this objfile.
   1.157 +class jsvalTypeCache(object):
   1.158 +    def __init__(self, cache):
   1.159 +        # Capture the tag values.
   1.160 +        d = gdb.types.make_enum_dict(gdb.lookup_type('JSValueType'))
   1.161 +        self.DOUBLE    = d['JSVAL_TYPE_DOUBLE']
   1.162 +        self.INT32     = d['JSVAL_TYPE_INT32']
   1.163 +        self.UNDEFINED = d['JSVAL_TYPE_UNDEFINED']
   1.164 +        self.BOOLEAN   = d['JSVAL_TYPE_BOOLEAN']
   1.165 +        self.MAGIC     = d['JSVAL_TYPE_MAGIC']
   1.166 +        self.STRING    = d['JSVAL_TYPE_STRING']
   1.167 +        self.NULL      = d['JSVAL_TYPE_NULL']
   1.168 +        self.OBJECT    = d['JSVAL_TYPE_OBJECT']
   1.169 +
   1.170 +        # Let self.magic_names be an array whose i'th element is the name of
   1.171 +        # the i'th magic value.
   1.172 +        d = gdb.types.make_enum_dict(gdb.lookup_type('JSWhyMagic'))
   1.173 +        self.magic_names = range(max(d.itervalues()) + 1)
   1.174 +        for (k,v) in d.items(): self.magic_names[v] = k
   1.175 +
   1.176 +        # Choose an unboxing scheme for this architecture.
   1.177 +        self.boxer = Punbox if cache.void_ptr_t.sizeof == 8 else Nunbox
   1.178 +
   1.179 +@pretty_printer('jsval_layout')
   1.180 +class jsval_layout(object):
   1.181 +    def __init__(self, value, cache):
   1.182 +        # Save the generic typecache, and create our own, if we haven't already.
   1.183 +        self.cache = cache
   1.184 +        if not cache.mod_jsval:
   1.185 +            cache.mod_jsval = jsvalTypeCache(cache)
   1.186 +        self.jtc = cache.mod_jsval
   1.187 +
   1.188 +        self.value = value
   1.189 +        self.box = self.jtc.boxer(value['asBits'], self.jtc)
   1.190 +
   1.191 +    def to_string(self):
   1.192 +        tag = self.box.tag()
   1.193 +        if tag == self.jtc.INT32:
   1.194 +            value = self.box.as_uint32()
   1.195 +            signbit = 1 << 31
   1.196 +            value = (value ^ signbit) - signbit
   1.197 +        elif tag == self.jtc.UNDEFINED:
   1.198 +            return 'JSVAL_VOID'
   1.199 +        elif tag == self.jtc.BOOLEAN:
   1.200 +            return 'JSVAL_TRUE' if self.box.as_uint32() else 'JSVAL_FALSE'
   1.201 +        elif tag == self.jtc.MAGIC:
   1.202 +            value = self.box.as_uint32()
   1.203 +            if 0 <= value and value < len(self.jtc.magic_names):
   1.204 +                return '$jsmagic(%s)' % (self.jtc.magic_names[value],)
   1.205 +            else:
   1.206 +                return '$jsmagic(%d)' % (value,)
   1.207 +        elif tag == self.jtc.STRING:
   1.208 +            value = self.box.as_address().cast(self.cache.JSString_ptr_t)
   1.209 +        elif tag == self.jtc.NULL:
   1.210 +            return 'JSVAL_NULL'
   1.211 +        elif tag == self.jtc.OBJECT:
   1.212 +            value = self.box.as_address().cast(self.cache.JSObject_ptr_t)
   1.213 +        elif tag == self.jtc.DOUBLE:
   1.214 +            value = self.value['asDouble']
   1.215 +        else:
   1.216 +            return '$jsval(unrecognized!)'
   1.217 +        return '$jsval(%s)' % (value,)
   1.218 +
   1.219 +@pretty_printer('JS::Value')
   1.220 +class JSValue(object):
   1.221 +    def __new__(cls, value, cache):
   1.222 +        return jsval_layout(value['data'], cache)

mercurial