summaryrefslogtreecommitdiffstats
path: root/js/src/gdb/mozilla
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/gdb/mozilla')
-rw-r--r--js/src/gdb/mozilla/ExecutableAllocator.py78
-rw-r--r--js/src/gdb/mozilla/GCCellPtr.py49
-rw-r--r--js/src/gdb/mozilla/Interpreter.py80
-rw-r--r--js/src/gdb/mozilla/IonGraph.py199
-rw-r--r--js/src/gdb/mozilla/JSObject.py68
-rw-r--r--js/src/gdb/mozilla/JSString.py72
-rw-r--r--js/src/gdb/mozilla/JSSymbol.py33
-rw-r--r--js/src/gdb/mozilla/Root.py90
-rw-r--r--js/src/gdb/mozilla/__init__.py1
-rw-r--r--js/src/gdb/mozilla/asmjs.py40
-rw-r--r--js/src/gdb/mozilla/autoload.py33
-rw-r--r--js/src/gdb/mozilla/jsid.py69
-rw-r--r--js/src/gdb/mozilla/jsval.py222
-rw-r--r--js/src/gdb/mozilla/prettyprinters.py368
-rw-r--r--js/src/gdb/mozilla/unwind.py591
15 files changed, 1993 insertions, 0 deletions
diff --git a/js/src/gdb/mozilla/ExecutableAllocator.py b/js/src/gdb/mozilla/ExecutableAllocator.py
new file mode 100644
index 000000000..e1ff81240
--- /dev/null
+++ b/js/src/gdb/mozilla/ExecutableAllocator.py
@@ -0,0 +1,78 @@
+"""
+All jitted code is allocated via the ExecutableAllocator class. Make GDB aware
+of them, such that we can query for pages which are containing code which are
+allocated by the Jits.
+"""
+
+import gdb
+import mozilla.prettyprinters
+from mozilla.prettyprinters import pretty_printer, ptr_pretty_printer
+
+# Forget any printers from previous loads of this module.
+mozilla.prettyprinters.clear_module_printers(__name__)
+
+# Cache information about the JSString type for this objfile.
+class jsjitExecutableAllocatorCache(object):
+ def __init__(self):
+ self.d = None
+
+ def __getattr__(self, name):
+ if self.d is None:
+ self.initialize()
+ return self.d[name]
+
+ def initialize(self):
+ self.d = {}
+ self.d['ExecutableAllocator'] = gdb.lookup_type('js::jit::ExecutableAllocator')
+ self.d['ExecutablePool'] = gdb.lookup_type('js::jit::ExecutablePool')
+
+@pretty_printer("js::jit::ExecutableAllocator")
+class jsjitExecutableAllocator(object):
+ def __init__(self, value, cache):
+ if not cache.mod_ExecutableAllocator:
+ cache.mod_ExecutableAllocator = jsjitExecutableAllocatorCache()
+ self.value = value
+ self.cache = cache.mod_ExecutableAllocator
+
+ def to_string(self):
+ return "ExecutableAllocator([%s])" % ', '.join([str(x) for x in self])
+
+ def __iter__(self):
+ return self.PoolIterator(self)
+
+ class PoolIterator(object):
+ def __init__(self, allocator):
+ self.allocator = allocator
+ self.entryType = allocator.cache.ExecutablePool.pointer()
+ # Emulate the HashSet::Range
+ self.table = allocator.value['m_pools']['impl']['table']
+ self.index = 0;
+ HASHNUMBER_BIT_SIZE = 32
+ self.max = 1 << (HASHNUMBER_BIT_SIZE - allocator.value['m_pools']['impl']['hashShift'])
+ if self.table == 0:
+ self.max = 0
+
+ def __iter__(self):
+ return self;
+
+ def next(self):
+ cur = self.index
+ if cur >= self.max:
+ raise StopIteration()
+ self.index = self.index + 1
+ if self.table[cur]['keyHash'] > 1: # table[i]->isLive()
+ return self.table[cur]['mem']['u']['mDummy'].cast(self.entryType)
+ return self.next()
+
+@ptr_pretty_printer("js::jit::ExecutablePool")
+class jsjitExecutablePool(mozilla.prettyprinters.Pointer):
+ def __init__(self, value, cache):
+ if not cache.mod_ExecutableAllocator:
+ cache.mod_ExecutableAllocator = jsjitExecutableAllocatorCache()
+ self.value = value
+ self.cache = cache.mod_ExecutableAllocator
+
+ def to_string(self):
+ pages = self.value['m_allocation']['pages']
+ size = self.value['m_allocation']['size']
+ return "ExecutablePool %08x-%08x" % (pages, pages + size)
diff --git a/js/src/gdb/mozilla/GCCellPtr.py b/js/src/gdb/mozilla/GCCellPtr.py
new file mode 100644
index 000000000..87dcf73da
--- /dev/null
+++ b/js/src/gdb/mozilla/GCCellPtr.py
@@ -0,0 +1,49 @@
+# Pretty-printers for GCCellPtr values.
+
+import gdb
+import mozilla.prettyprinters
+
+from mozilla.prettyprinters import pretty_printer
+
+# Forget any printers from previous loads of this module.
+mozilla.prettyprinters.clear_module_printers(__name__)
+
+# Cache information about the JS::TraceKind type for this objfile.
+class GCCellPtrTypeCache(object):
+ def __init__(self, cache):
+ self.TraceKind_t = gdb.lookup_type('JS::TraceKind')
+
+ # Build a mapping from TraceKind enum values to the types they denote.
+ e = gdb.types.make_enum_dict(self.TraceKind_t)
+ kind_to_type = {}
+ def kind(k, t):
+ kind_to_type[e['JS::TraceKind::' + k]] = gdb.lookup_type(t)
+ kind('Object', 'JSObject')
+ kind('String', 'JSString')
+ kind('Symbol', 'JS::Symbol')
+ kind('Script', 'JSScript')
+ kind('Shape', 'js::Shape')
+ kind('ObjectGroup', 'js::ObjectGroup')
+ kind('BaseShape', 'js::BaseShape')
+ kind('JitCode', 'js::jit::JitCode')
+ kind('LazyScript', 'js::LazyScript')
+ self.kind_to_type = kind_to_type
+
+ self.Null = e['JS::TraceKind::Null']
+ self.mask = gdb.parse_and_eval('JS::OutOfLineTraceKindMask')
+
+@pretty_printer('JS::GCCellPtr')
+class GCCellPtr(object):
+ def __init__(self, value, cache):
+ self.value = value
+ if not cache.mod_GCCellPtr:
+ cache.mod_GCCellPtr = GCCellPtrTypeCache(cache)
+ self.cache = cache
+
+ def to_string(self):
+ ptr = self.value['ptr']
+ kind = ptr & self.cache.mod_GCCellPtr.mask
+ if kind == self.cache.mod_GCCellPtr.Null:
+ return "JS::GCCellPtr(nullptr)"
+ tipe = self.cache.mod_GCCellPtr.kind_to_type[int(kind)]
+ return "JS::GCCellPtr(({}*) {})".format(tipe, ptr.cast(self.cache.void_ptr_t))
diff --git a/js/src/gdb/mozilla/Interpreter.py b/js/src/gdb/mozilla/Interpreter.py
new file mode 100644
index 000000000..e54210e79
--- /dev/null
+++ b/js/src/gdb/mozilla/Interpreter.py
@@ -0,0 +1,80 @@
+# Pretty-printers for InterpreterRegs.
+
+import gdb
+import mozilla.prettyprinters as prettyprinters
+
+prettyprinters.clear_module_printers(__name__)
+
+from mozilla.prettyprinters import pretty_printer
+
+# Cache information about the Interpreter types for this objfile.
+class InterpreterTypeCache(object):
+ def __init__(self):
+ self.tValue = gdb.lookup_type('JS::Value')
+ self.tJSOp = gdb.lookup_type('JSOp')
+ self.tScriptFrameIterData = gdb.lookup_type('js::ScriptFrameIter::Data')
+ self.tInterpreterFrame = gdb.lookup_type('js::InterpreterFrame')
+ self.tBaselineFrame = gdb.lookup_type('js::jit::BaselineFrame')
+ self.tRematerializedFrame = gdb.lookup_type('js::jit::RematerializedFrame')
+
+@pretty_printer('js::InterpreterRegs')
+class InterpreterRegs(object):
+ def __init__(self, value, cache):
+ self.value = value
+ self.cache = cache
+ if not cache.mod_Interpreter:
+ cache.mod_Interpreter = InterpreterTypeCache()
+ self.itc = cache.mod_Interpreter
+
+ # There's basically no way to co-operate with 'set print pretty' (how would
+ # you get the current level of indentation?), so we don't even bother
+ # trying. No 'children', just 'to_string'.
+ def to_string(self):
+ fp_ = 'fp_ = {}'.format(self.value['fp_'])
+ slots = (self.value['fp_'] + 1).cast(self.itc.tValue.pointer())
+ sp = 'sp = fp_.slots() + {}'.format(self.value['sp'] - slots)
+ pc = self.value['pc']
+ try:
+ opcode = pc.dereference().cast(self.itc.tJSOp)
+ except:
+ opcode = 'bad pc'
+ pc = 'pc = {} ({})'.format(pc.cast(self.cache.void_ptr_t), opcode)
+ return '{{ {}, {}, {} }}'.format(fp_, sp, pc)
+
+@pretty_printer('js::AbstractFramePtr')
+class AbstractFramePtr(object):
+ Tag_ScriptFrameIterData = 0x0
+ Tag_InterpreterFrame = 0x1
+ Tag_BaselineFrame = 0x2
+ Tag_RematerializedFrame = 0x3
+ TagMask = 0x3
+
+ def __init__(self, value, cache):
+ self.value = value
+ self.cache = cache
+ if not cache.mod_Interpreter:
+ cache.mod_Interpreter = InterpreterTypeCache()
+ self.itc = cache.mod_Interpreter
+
+ def to_string(self):
+ ptr = self.value['ptr_']
+ tag = ptr & AbstractFramePtr.TagMask
+ ptr = ptr & ~AbstractFramePtr.TagMask
+ if tag == AbstractFramePtr.Tag_ScriptFrameIterData:
+ label = 'js::ScriptFrameIter::Data'
+ ptr = ptr.cast(self.itc.tScriptFrameIterData.pointer())
+ if tag == AbstractFramePtr.Tag_InterpreterFrame:
+ label = 'js::InterpreterFrame'
+ ptr = ptr.cast(self.itc.tInterpreterFrame.pointer())
+ if tag == AbstractFramePtr.Tag_BaselineFrame:
+ label = 'js::jit::BaselineFrame'
+ ptr = ptr.cast(self.itc.tBaselineFrame.pointer())
+ if tag == AbstractFramePtr.Tag_RematerializedFrame:
+ label = 'js::jit::RematerializedFrame'
+ ptr = ptr.cast(self.itc.tRematerializedFrame.pointer())
+ return 'AbstractFramePtr (({} *) {})'.format(label, ptr)
+
+ # Provide the ptr_ field as a child, so it prints after the pretty string
+ # provided above.
+ def children(self):
+ yield ('ptr_', self.value['ptr_'])
diff --git a/js/src/gdb/mozilla/IonGraph.py b/js/src/gdb/mozilla/IonGraph.py
new file mode 100644
index 000000000..edd9ef8e4
--- /dev/null
+++ b/js/src/gdb/mozilla/IonGraph.py
@@ -0,0 +1,199 @@
+"""
+Debugging JIT Compilations can be obscure without large context. This python
+script provide commands to let GDB open an image viewer to display the graph of
+any compilation, as they are executed within GDB.
+
+This python script should be sourced within GDB after loading the python scripts
+provided with SpiderMonkey.
+"""
+
+import gdb
+import os
+import io
+import subprocess
+import tempfile
+import time
+import mozilla.prettyprinters
+from mozilla.prettyprinters import pretty_printer
+
+# Forget any printers from previous loads of this module.
+mozilla.prettyprinters.clear_module_printers(__name__)
+
+# Cache information about the JSString type for this objfile.
+class jsvmPrinterCache(object):
+ def __init__(self):
+ self.d = None
+
+ def __getattr__(self, name):
+ if self.d is None:
+ self.initialize()
+ return self.d[name]
+
+ def initialize(self):
+ self.d = {}
+ self.d['char'] = gdb.lookup_type('char')
+
+# Dummy class used to store the content of the type cache in the context of the
+# iongraph command, which uses the jsvmLSprinter.
+class ModuleCache(object):
+ def __init__(self):
+ self.mod_IonGraph = None
+
+@pretty_printer("js::vm::LSprinter")
+class jsvmLSprinter(object):
+ def __init__(self, value, cache):
+ self.value = value
+ if not cache.mod_IonGraph:
+ cache.mod_IonGraph = jsvmPrinterCache()
+ self.cache = cache.mod_IonGraph
+
+ def to_string(self):
+ next = self.value['head_']
+ tail = self.value['tail_']
+ if next == 0:
+ return ""
+ res = ""
+ while next != tail:
+ chars = (next + 1).cast(self.cache.char.pointer())
+ res = res + chars.string('ascii', 'ignore', next['length'])
+ next = next['next']
+ length = next['length'] - self.value['unused_']
+ chars = (next + 1).cast(self.cache.char.pointer())
+ res = res + chars.string('ascii', 'ignore', length)
+ return res
+
+def search_in_path(bin):
+ paths = os.getenv("PATH", "")
+ if paths == "":
+ return ""
+ for d in paths.split(":"):
+ f = os.path.join(d, bin)
+ if os.access(f, os.X_OK):
+ return f
+ return ""
+
+class IonGraphBinParameter(gdb.Parameter):
+ set_doc = "Set the path to iongraph binary, used by iongraph command."
+ show_doc = "Show the path to iongraph binary, used by iongraph command."
+ def get_set_string(self):
+ return "Path to iongraph binary changed to: %s" % self.value
+ def get_show_string(self, value):
+ return "Path to iongraph binary set to: %s" % value
+ def __init__(self):
+ super (IonGraphBinParameter, self).__init__ ("iongraph-bin", gdb.COMMAND_SUPPORT, gdb.PARAM_FILENAME)
+ self.value = os.getenv("GDB_IONGRAPH", "")
+ if self.value == "":
+ self.value = search_in_path("iongraph")
+
+
+class DotBinParameter(gdb.Parameter):
+ set_doc = "Set the path to dot binary, used by iongraph command."
+ show_doc = "Show the path to dot binary, used by iongraph command."
+ def get_set_string(self):
+ return "Path to dot binary changed to: %s" % self.value
+ def get_show_string(self, value):
+ return "Path to dot binary set to: %s" % value
+ def __init__(self):
+ super (DotBinParameter, self).__init__ ("dot-bin", gdb.COMMAND_SUPPORT, gdb.PARAM_FILENAME)
+ self.value = os.getenv("GDB_DOT", "")
+ if self.value == "":
+ self.value = search_in_path("dot")
+
+class PngViewerBinParameter(gdb.Parameter):
+ set_doc = "Set the path to a png viewer binary, used by iongraph command."
+ show_doc = "Show the path to a png viewer binary, used by iongraph command."
+ def get_set_string(self):
+ return "Path to a png viewer binary changed to: %s" % self.value
+ def get_show_string(self):
+ return "Path to a png viewer binary set to: %s" % value
+ def __init__(self):
+ super (PngViewerBinParameter, self).__init__ ("pngviewer-bin", gdb.COMMAND_SUPPORT, gdb.PARAM_FILENAME)
+ self.value = os.getenv("GDB_PNGVIEWER", "")
+ if self.value == "":
+ self.value = search_in_path("xdg-open")
+
+iongraph = IonGraphBinParameter()
+dot = DotBinParameter()
+pngviewer = PngViewerBinParameter()
+
+class IonGraphCommand(gdb.Command):
+ """Command used to display the current state of the MIR graph in a png
+ viewer by providing an expression to access the MIRGenerator.
+ """
+
+ def __init__(self):
+ super (IonGraphCommand, self).__init__ ("iongraph", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION)
+ self.typeCache = ModuleCache()
+
+ def invoke(self, mirGenExpr, from_tty):
+ """Call function from the graph spewer to populate the json printer with
+ the content generated by the jsonSpewer. Then we read the json content
+ from the jsonPrinter internal data, and gives that as input of iongraph
+ command."""
+
+ # From the MIRGenerator, find the graph spewer which contains both the
+ # jsonPrinter (containing the result of the output), and the jsonSpewer
+ # (continaining methods for spewing the graph).
+ mirGen = gdb.parse_and_eval(mirGenExpr)
+ jsonPrinter = mirGen['gs_']['jsonPrinter_']
+ jsonSpewer = mirGen['gs_']['jsonSpewer_']
+ graph = mirGen['graph_']
+
+ # These commands are doing side-effects which are saving the state of
+ # the compiled code on the LSprinter dedicated for logging. Fortunately,
+ # if you are using these gdb command, this probably means that other
+ # ways of getting this content failed you already, so making a mess in
+ # these logging strings should not cause much issues.
+ gdb.parse_and_eval('(*(%s*)(%s)).clear()' % (jsonPrinter.type, jsonPrinter.address,))
+ gdb.parse_and_eval('(*(%s*)(%s)).beginFunction((JSScript*)0)' % (jsonSpewer.type, jsonSpewer.address,))
+ gdb.parse_and_eval('(*(%s*)(%s)).beginPass("gdb")' % (jsonSpewer.type, jsonSpewer.address,))
+ gdb.parse_and_eval('(*(%s*)(%s)).spewMIR((%s)%s)' % (jsonSpewer.type, jsonSpewer.address, graph.type, graph,))
+ gdb.parse_and_eval('(*(%s*)(%s)).spewLIR((%s)%s)' % (jsonSpewer.type, jsonSpewer.address, graph.type, graph,))
+ gdb.parse_and_eval('(*(%s*)(%s)).endPass()' % (jsonSpewer.type, jsonSpewer.address,))
+ gdb.parse_and_eval('(*(%s*)(%s)).endFunction()' % (jsonSpewer.type, jsonSpewer.address,))
+
+ # Dump the content of the LSprinter containing the JSON view of the
+ # graph into a python string.
+ json = jsvmLSprinter(jsonPrinter, self.typeCache).to_string()
+
+ # We are in the middle of the program execution and are messing up with
+ # the state of the logging data. As this might not be the first time we
+ # call beginFunction, we might have an extra comma at the beginning of
+ # the output, just strip it.
+ if json[0] == ",":
+ json = json[1:]
+
+ # Usually this is added by the IonSpewer.
+ json = '{ "functions": [%s] }' % json
+
+ # Display the content of the json with iongraph and other tools.
+ self.displayMIRGraph(json)
+
+ def displayMIRGraph(self, jsonStr):
+ png = tempfile.NamedTemporaryFile()
+
+ # start all processes in a shell-like equivalent of:
+ # iongraph < json | dot > tmp.png; xdg-open tmp.png
+ i = subprocess.Popen([iongraph.value, '--funcnum', '0', '--passnum', '0', '--out-mir', '-', '-'], stdin = subprocess.PIPE, stdout = subprocess.PIPE)
+ d = subprocess.Popen([dot.value, '-Tpng'], stdin = i.stdout, stdout = png)
+
+ # Write the json file as the input of the iongraph command.
+ i.stdin.write(jsonStr)
+ i.stdin.close()
+ i.stdout.close()
+
+ # Wait for iongraph and dot, such that the png file contains all the
+ # bits needed to by the png viewer.
+ i.wait()
+ output = d.communicate()[0]
+
+ # Spawn & detach the png viewer, to which we give the name of the
+ # temporary file. Note, as we do not want to wait on the image viewer,
+ # there is a minor race between the removal of the temporary file, which
+ # would happen at the next garbage collection cycle, and the start of
+ # the png viewer. We could use a pipe, but unfortunately, this does not
+ # seems to be supported by xdg-open.
+ v = subprocess.Popen([pngviewer.value, png.name], stdin = None, stdout = None)
+ time.sleep(1)
+
+iongraph_cmd = IonGraphCommand()
diff --git a/js/src/gdb/mozilla/JSObject.py b/js/src/gdb/mozilla/JSObject.py
new file mode 100644
index 000000000..8112a9d36
--- /dev/null
+++ b/js/src/gdb/mozilla/JSObject.py
@@ -0,0 +1,68 @@
+# Pretty-printers for SpiderMonkey JSObjects.
+
+import re
+import gdb
+import mozilla.JSString
+import mozilla.prettyprinters as prettyprinters
+from mozilla.prettyprinters import ptr_pretty_printer, ref_pretty_printer
+from mozilla.Root import deref
+
+prettyprinters.clear_module_printers(__name__)
+
+class JSObjectTypeCache(object):
+ def __init__(self, value, cache):
+ baseshape_flags = gdb.lookup_type('js::BaseShape::Flag')
+ self.flag_DELEGATE = prettyprinters.enum_value(baseshape_flags, 'js::BaseShape::DELEGATE')
+ self.func_ptr_type = gdb.lookup_type('JSFunction').pointer()
+ self.class_NON_NATIVE = gdb.parse_and_eval('js::Class::NON_NATIVE')
+ self.NativeObject_ptr_t = gdb.lookup_type('js::NativeObject').pointer()
+
+# There should be no need to register this for JSFunction as well, since we
+# search for pretty-printers under the names of base classes, and
+# JSFunction has JSObject as a base class.
+
+gdb_string_regexp = re.compile(r'(?:0x[0-9a-z]+ )?(?:<.*> )?"(.*)"', re.I)
+
+@ptr_pretty_printer('JSObject')
+class JSObjectPtrOrRef(prettyprinters.Pointer):
+ def __init__(self, value, cache):
+ super(JSObjectPtrOrRef, self).__init__(value, cache)
+ if not cache.mod_JSObject:
+ cache.mod_JSObject = JSObjectTypeCache(value, cache)
+ self.otc = cache.mod_JSObject
+
+ def summary(self):
+ group = deref(self.value['group_'])
+ classp = group['clasp_']
+ non_native = classp['flags'] & self.otc.class_NON_NATIVE
+
+ # Use GDB to format the class name, but then strip off the address
+ # and the quotes.
+ class_name = str(classp['name'])
+ m = gdb_string_regexp.match(class_name)
+ if m:
+ class_name = m.group(1)
+
+ if non_native:
+ return '[object {}]'.format(class_name)
+ else:
+ native = self.value.cast(self.otc.NativeObject_ptr_t)
+ shape = deref(native['shape_'])
+ baseshape = deref(shape['base_'])
+ flags = baseshape['flags']
+ is_delegate = bool(flags & self.otc.flag_DELEGATE)
+ name = None
+ if class_name == 'Function':
+ function = self.value
+ concrete_type = function.type.strip_typedefs()
+ if concrete_type.code == gdb.TYPE_CODE_REF:
+ function = function.address
+ function = function.cast(self.otc.func_ptr_type)
+ atom = deref(function['atom_'])
+ name = str(atom) if atom else '<unnamed>'
+ return '[object {}{}]{}'.format(class_name,
+ ' ' + name if name else '',
+ ' delegate' if is_delegate else '')
+
+@ref_pretty_printer('JSObject')
+def JSObjectRef(value, cache): return JSObjectPtrOrRef(value, cache)
diff --git a/js/src/gdb/mozilla/JSString.py b/js/src/gdb/mozilla/JSString.py
new file mode 100644
index 000000000..99d5ce987
--- /dev/null
+++ b/js/src/gdb/mozilla/JSString.py
@@ -0,0 +1,72 @@
+# Pretty-printers for SpiderMonkey strings.
+
+import gdb
+import mozilla.prettyprinters
+from mozilla.prettyprinters import pretty_printer, ptr_pretty_printer
+
+try:
+ chr(10000) # UPPER RIGHT PENCIL
+except ValueError as exc: # yuck, we are in Python 2.x, so chr() is 8-bit
+ chr = unichr # replace with teh unicodes
+
+# Forget any printers from previous loads of this module.
+mozilla.prettyprinters.clear_module_printers(__name__)
+
+# Cache information about the JSString type for this objfile.
+class JSStringTypeCache(object):
+ def __init__(self, cache):
+ dummy = gdb.Value(0).cast(cache.JSString_ptr_t)
+ self.ROPE_FLAGS = dummy['ROPE_FLAGS']
+ self.ATOM_BIT = dummy['ATOM_BIT']
+ self.INLINE_CHARS_BIT = dummy['INLINE_CHARS_BIT']
+ self.TYPE_FLAGS_MASK = dummy['TYPE_FLAGS_MASK']
+ self.LATIN1_CHARS_BIT = dummy['LATIN1_CHARS_BIT']
+
+class Common(mozilla.prettyprinters.Pointer):
+ def __init__(self, value, cache):
+ super(Common, self).__init__(value, cache)
+ if not cache.mod_JSString:
+ cache.mod_JSString = JSStringTypeCache(cache)
+ self.stc = cache.mod_JSString
+
+@ptr_pretty_printer("JSString")
+class JSStringPtr(Common):
+ def display_hint(self):
+ return "string"
+
+ def chars(self):
+ d = self.value['d']
+ length = d['u1']['length']
+ flags = d['u1']['flags']
+ is_rope = ((flags & self.stc.TYPE_FLAGS_MASK) == self.stc.ROPE_FLAGS)
+ if is_rope:
+ for c in JSStringPtr(d['s']['u2']['left'], self.cache).chars():
+ yield c
+ for c in JSStringPtr(d['s']['u3']['right'], self.cache).chars():
+ yield c
+ else:
+ is_inline = (flags & self.stc.INLINE_CHARS_BIT) != 0
+ is_latin1 = (flags & self.stc.LATIN1_CHARS_BIT) != 0
+ if is_inline:
+ if is_latin1:
+ chars = d['inlineStorageLatin1']
+ else:
+ chars = d['inlineStorageTwoByte']
+ else:
+ if is_latin1:
+ chars = d['s']['u2']['nonInlineCharsLatin1']
+ else:
+ chars = d['s']['u2']['nonInlineCharsTwoByte']
+ for i in range(int(length)):
+ yield chars[i]
+
+ def to_string(self):
+ s = u''
+ for c in self.chars():
+ s += chr(c)
+ return s
+
+@ptr_pretty_printer("JSAtom")
+class JSAtomPtr(Common):
+ def to_string(self):
+ return self.value.cast(self.cache.JSString_ptr_t)
diff --git a/js/src/gdb/mozilla/JSSymbol.py b/js/src/gdb/mozilla/JSSymbol.py
new file mode 100644
index 000000000..5d747d107
--- /dev/null
+++ b/js/src/gdb/mozilla/JSSymbol.py
@@ -0,0 +1,33 @@
+# Pretty-printer for SpiderMonkey symbols.
+
+import gdb
+import mozilla.prettyprinters
+from mozilla.prettyprinters import ptr_pretty_printer
+
+# Forget any printers from previous loads of this module.
+mozilla.prettyprinters.clear_module_printers(__name__)
+
+# JS::SymbolCode enumerators
+InSymbolRegistry = 0xfffffffe
+UniqueSymbol = 0xffffffff
+
+@ptr_pretty_printer("JS::Symbol")
+class JSSymbolPtr(mozilla.prettyprinters.Pointer):
+ def __init__(self, value, cache):
+ super(JSSymbolPtr, self).__init__(value, cache)
+ self.value = value
+
+ def to_string(self):
+ code = int(self.value['code_']) & 0xffffffff
+ desc = str(self.value['description_'])
+ if code == InSymbolRegistry:
+ return "Symbol.for({})".format(desc)
+ elif code == UniqueSymbol:
+ return "Symbol({})".format(desc)
+ else:
+ # Well-known symbol. Strip off the quotes added by the JSString *
+ # pretty-printer.
+ assert desc[0] == '"'
+ assert desc[-1] == '"'
+ return desc[1:-1]
+
diff --git a/js/src/gdb/mozilla/Root.py b/js/src/gdb/mozilla/Root.py
new file mode 100644
index 000000000..4656a3852
--- /dev/null
+++ b/js/src/gdb/mozilla/Root.py
@@ -0,0 +1,90 @@
+# Pretty-printers and utilities for SpiderMonkey rooting templates:
+# Rooted, Handle, MutableHandle, etc.
+
+import mozilla.prettyprinters
+from mozilla.prettyprinters import pretty_printer, template_pretty_printer
+
+# Forget any printers from previous loads of this module.
+mozilla.prettyprinters.clear_module_printers(__name__)
+
+# Common base class for all the rooting template pretty-printers. All these
+# templates have one member holding the referent (or a pointer to it), so
+# there's not much to it.
+class Common(object):
+ # The name of the template member holding the referent.
+ member = 'ptr'
+
+ # If True, this is a handle type, and should be dereferenced. If False,
+ # the template member holds the referent directly.
+ handle = False
+
+ # If True, we should strip typedefs from our referent type. (Rooted<T>
+ # uses template magic that gives the referent a noisy type.)
+ strip_typedefs = False
+
+ # Initialize a pretty-printer for |value|, using |cache|.
+ #
+ # If given, |content_printer| is a pretty-printer constructor to use for
+ # this handle/root/etc.'s referent. Usually, we can just omit this argument
+ # and let GDB choose a pretty-printer for the referent given its type, but
+ # when the referent is a typedef of an integral type (say, |jsid| in a
+ # non-|DEBUG| build), the GNU toolchain (at least) loses the typedef name,
+ # and all we know about the referent is its fundamental integer type ---
+ # |JS::Rooted<jsid>|, for example, appears in GDB as |JS::Rooted<long>| ---
+ # and we are left with no way to choose a meaningful pretty-printer based on
+ # the type of the referent alone. However, because we know that the only
+ # integer type for which |JS::Rooted| is likely to be instantiated is
+ # |jsid|, we *can* register a pretty-printer constructor for the full
+ # instantiation |JS::Rooted<long>|. That constructor creates a |JS::Rooted|
+ # pretty-printer, and explicitly specifies the constructor for the referent,
+ # using this initializer's |content_printer| argument.
+ def __init__(self, value, cache, content_printer=None):
+ self.value = value
+ self.cache = cache
+ self.content_printer = content_printer
+ def to_string(self):
+ ptr = self.value[self.member]
+ if self.handle:
+ ptr = ptr.dereference()
+ if self.strip_typedefs:
+ ptr = ptr.cast(ptr.type.strip_typedefs())
+ if self.content_printer:
+ return self.content_printer(ptr, self.cache).to_string()
+ else:
+ # As of 2012-11, GDB suppresses printing pointers in replacement
+ # values; see http://sourceware.org/ml/gdb/2012-11/msg00055.html
+ # That means that simply returning the 'ptr' member won't work.
+ # Instead, just invoke GDB's formatter ourselves.
+ return str(ptr)
+
+@template_pretty_printer("JS::Rooted")
+class Rooted(Common):
+ strip_typedefs = True
+
+@template_pretty_printer("JS::Handle")
+class Handle(Common):
+ handle = True
+
+@template_pretty_printer("JS::MutableHandle")
+class MutableHandle(Common):
+ handle = True
+
+@template_pretty_printer("js::BarrieredBase")
+class BarrieredBase(Common):
+ member = 'value'
+
+# Return the referent of a HeapPtr, Rooted, or Handle.
+def deref(root):
+ tag = root.type.strip_typedefs().tag
+ if not tag:
+ raise TypeError("Can't dereference type with no structure tag: %s" % (root.type,))
+ elif tag.startswith('js::HeapPtr<'):
+ return root['value']
+ elif tag.startswith('JS::Rooted<'):
+ return root['ptr']
+ elif tag.startswith('JS::Handle<'):
+ return root['ptr']
+ elif tag.startswith('js::GCPtr<'):
+ return root['value']
+ else:
+ raise NotImplementedError("Unrecognized tag: " + tag)
diff --git a/js/src/gdb/mozilla/__init__.py b/js/src/gdb/mozilla/__init__.py
new file mode 100644
index 000000000..b879ca264
--- /dev/null
+++ b/js/src/gdb/mozilla/__init__.py
@@ -0,0 +1 @@
+# Yes, Python, this is a package.
diff --git a/js/src/gdb/mozilla/asmjs.py b/js/src/gdb/mozilla/asmjs.py
new file mode 100644
index 000000000..43fac20c7
--- /dev/null
+++ b/js/src/gdb/mozilla/asmjs.py
@@ -0,0 +1,40 @@
+"""
+In asm code, out-of-bounds heap accesses cause segfaults, which the engine
+handles internally. Make GDB ignore them.
+"""
+
+import gdb
+
+SIGSEGV = 11
+
+# A sigaction buffer for each inferior process.
+sigaction_buffers = {}
+
+def on_stop(event):
+ if isinstance(event, gdb.SignalEvent) and event.stop_signal == 'SIGSEGV':
+ # Allocate memory for sigaction, once per js shell process.
+ process = gdb.selected_inferior()
+ buf = sigaction_buffers.get(process)
+ if buf is None:
+ buf = gdb.parse_and_eval("(struct sigaction *) malloc(sizeof(struct sigaction))")
+ sigaction_buffers[process] = buf
+
+ # See if AsmJSFaultHandler is installed as the SIGSEGV signal action.
+ sigaction_fn = gdb.parse_and_eval('__sigaction')
+ sigaction_fn(SIGSEGV, 0, buf)
+ AsmJSFaultHandler = gdb.parse_and_eval("AsmJSFaultHandler")
+ if buf['__sigaction_handler']['sa_handler'] == AsmJSFaultHandler:
+ # Advise the user that magic is happening.
+ print("js/src/gdb/mozilla/asmjs.py: Allowing AsmJSFaultHandler to run.")
+
+ # If AsmJSFaultHandler doesn't handle this segfault, it will unhook
+ # itself and re-raise.
+ gdb.execute("continue")
+
+def on_exited(event):
+ if event.inferior in sigaction_buffers:
+ del sigaction_buffers[event.inferior]
+
+def install():
+ gdb.events.stop.connect(on_stop)
+ gdb.events.exited.connect(on_exited)
diff --git a/js/src/gdb/mozilla/autoload.py b/js/src/gdb/mozilla/autoload.py
new file mode 100644
index 000000000..43cbcdf72
--- /dev/null
+++ b/js/src/gdb/mozilla/autoload.py
@@ -0,0 +1,33 @@
+# mozilla/autoload.py: Autoload SpiderMonkey pretty-printers.
+
+print("Loading JavaScript value pretty-printers; see js/src/gdb/README.")
+print("If they cause trouble, type: disable pretty-printer .* SpiderMonkey")
+
+import gdb.printing
+import mozilla.prettyprinters
+
+# Import the pretty-printer modules. As a side effect, loading these
+# modules registers their printers with mozilla.prettyprinters.
+import mozilla.GCCellPtr
+import mozilla.Interpreter
+import mozilla.IonGraph
+import mozilla.JSObject
+import mozilla.JSString
+import mozilla.JSSymbol
+import mozilla.Root
+import mozilla.jsid
+import mozilla.jsval
+import mozilla.unwind
+
+# The user may have personal pretty-printers. Get those, too, if they exist.
+try:
+ import my_mozilla_printers
+except ImportError:
+ pass
+
+# Register our pretty-printers with |objfile|.
+def register(objfile):
+ lookup = mozilla.prettyprinters.lookup_for_objfile(objfile)
+ if lookup:
+ gdb.printing.register_pretty_printer(objfile, lookup, replace=True)
+ mozilla.unwind.register_unwinder(objfile)
diff --git a/js/src/gdb/mozilla/jsid.py b/js/src/gdb/mozilla/jsid.py
new file mode 100644
index 000000000..b7c0e183b
--- /dev/null
+++ b/js/src/gdb/mozilla/jsid.py
@@ -0,0 +1,69 @@
+# Pretty-printers for JSID values.
+
+import gdb
+import mozilla.prettyprinters
+import mozilla.Root
+
+from mozilla.prettyprinters import pretty_printer
+
+# Forget any printers from previous loads of this module.
+mozilla.prettyprinters.clear_module_printers(__name__)
+
+@pretty_printer('jsid')
+class jsid(object):
+ # Since people don't always build with macro debugging info, I can't
+ # think of any way to avoid copying these values here, short of using
+ # inferior calls for every operation (which, I hear, is broken from
+ # pretty-printers in some recent GDBs).
+ TYPE_STRING = 0x0
+ TYPE_INT = 0x1
+ TYPE_VOID = 0x2
+ TYPE_SYMBOL = 0x4
+ TYPE_MASK = 0x7
+
+ def __init__(self, value, cache):
+ self.value = value
+ self.cache = cache
+ self.concrete_type = self.value.type.strip_typedefs()
+
+ # SpiderMonkey has two alternative definitions of jsid: a typedef for
+ # ptrdiff_t, and a struct with == and != operators defined on it.
+ # Extract the bits from either one.
+ def as_bits(self):
+ if self.concrete_type.code == gdb.TYPE_CODE_STRUCT:
+ return self.value['asBits']
+ elif self.concrete_type.code == gdb.TYPE_CODE_INT:
+ return self.value
+ else:
+ raise RuntimeError("definition of SpiderMonkey 'jsid' type"
+ "neither struct nor integral type")
+
+ def to_string(self):
+ bits = self.as_bits()
+ tag = bits & jsid.TYPE_MASK
+ if tag == jsid.TYPE_STRING:
+ body = bits.cast(self.cache.JSString_ptr_t)
+ elif tag & jsid.TYPE_INT:
+ body = bits >> 1
+ elif tag == jsid.TYPE_VOID:
+ return "JSID_VOID"
+ elif tag == jsid.TYPE_SYMBOL:
+ if bits == jsid.TYPE_SYMBOL:
+ return "JSID_EMPTY"
+ body = ((bits & ~jsid.TYPE_MASK)
+ .cast(self.cache.JSSymbol_ptr_t))
+ else:
+ body = "<unrecognized>"
+ return '$jsid(%s)' % (body,)
+
+# Hard-code the referent type pretty-printer for jsid roots and handles.
+# See the comment for mozilla.Root.Common.__init__.
+@pretty_printer('JS::Rooted<long>')
+def RootedJSID(value, cache):
+ return mozilla.Root.Rooted(value, cache, jsid)
+@pretty_printer('JS::Handle<long>')
+def HandleJSID(value, cache):
+ return mozilla.Root.Handle(value, cache, jsid)
+@pretty_printer('JS::MutableHandle<long>')
+def MutableHandleJSID(value, cache):
+ return mozilla.Root.MutableHandle(value, cache, jsid)
diff --git a/js/src/gdb/mozilla/jsval.py b/js/src/gdb/mozilla/jsval.py
new file mode 100644
index 000000000..f5ae78b1c
--- /dev/null
+++ b/js/src/gdb/mozilla/jsval.py
@@ -0,0 +1,222 @@
+# Pretty-printers for SpiderMonkey jsvals.
+
+import gdb
+import gdb.types
+import mozilla.prettyprinters
+from mozilla.prettyprinters import pretty_printer, ptr_pretty_printer
+
+# Forget any printers from previous loads of this module.
+mozilla.prettyprinters.clear_module_printers(__name__)
+
+# Summary of the JS::Value (also known as jsval) type:
+#
+# Viewed abstractly, JS::Value is a 64-bit discriminated union, with
+# JSString *, JSObject *, IEEE 64-bit floating-point, and 32-bit integer
+# branches (and a few others). (It is not actually a C++ union;
+# 'discriminated union' just describes the overall effect.) Note that
+# JS::Value is always 64 bits long, even on 32-bit architectures.
+#
+# The ECMAScript standard specifies that ECMAScript numbers are IEEE 64-bit
+# floating-point values. A JS::Value can represent any JavaScript number
+# value directly, without referring to additional storage, or represent an
+# object, string, or other ECMAScript value, and remember which type it is.
+# This may seem surprising: how can a 64-bit type hold all the 64-bit IEEE
+# values, and still distinguish them from objects, strings, and so on,
+# which have 64-bit addresses?
+#
+# This is possible for two reasons:
+#
+# - First, ECMAScript implementations aren't required to distinguish all
+# the values the IEEE 64-bit format can represent. The IEEE format
+# specifies many bitstrings representing NaN values, while ECMAScript
+# requires only a single NaN value. This means we can use one IEEE NaN to
+# represent ECMAScript's NaN, and use all the other IEEE NaNs to
+# represent the other ECMAScript values.
+#
+# (IEEE says that any floating-point value whose 11-bit exponent field is
+# 0x7ff (all ones) and whose 52-bit fraction field is non-zero is a NaN.
+# So as long as we ensure the fraction field is non-zero, and save a NaN
+# for ECMAScript, we have 2^52 values to play with.)
+#
+# - Second, on the only 64-bit architecture we support, x86_64, only the
+# lower 48 bits of an address are significant. The upper sixteen bits are
+# required to be the sign-extension of bit 48. Furthermore, user code
+# always runs in "positive addresses": those in which bit 48 is zero. So
+# we only actually need 47 bits to store all possible object or string
+# addresses, even on 64-bit platforms.
+#
+# With a 52-bit fraction field, and 47 bits needed for the 'payload', we
+# have up to five bits left to store a 'tag' value, to indicate which
+# branch of our discriminated union is live.
+#
+# Thus, we define JS::Value representations in terms of the IEEE 64-bit
+# floating-point format:
+#
+# - Any bitstring that IEEE calls a number or an infinity represents that
+# ECMAScript number.
+#
+# - Any bitstring that IEEE calls a NaN represents either an ECMAScript NaN
+# or a non-number ECMAScript value, as determined by a tag field stored
+# towards the most significant end of the fraction field (exactly where
+# depends on the address size). If the tag field indicates that this
+# JS::Value is an object, the fraction field's least significant end
+# holds the address of a JSObject; if a string, the address of a
+# JSString; and so on.
+#
+# On the only 64-bit platform we support, x86_64, only the lower 48 bits of
+# an address are significant, and only those values whose top bit is zero
+# are used for user-space addresses. This means that x86_64 addresses are
+# effectively 47 bits long, and thus fit nicely in the available portion of
+# the fraction field.
+#
+#
+# In detail:
+#
+# - jsval (Value.h) is a typedef for JS::Value.
+#
+# - JS::Value (Value.h) is a class with a lot of methods and a single data
+# member, of type jsval_layout.
+#
+# - jsval_layout (Value.h) is a helper type for picking apart values. This
+# is always 64 bits long, with a variant for each address size (32 bits
+# or 64 bits) and endianness (little- or big-endian).
+#
+# jsval_layout is a union with 'asBits', 'asDouble', and 'asPtr'
+# branches, and an 's' branch, which is a struct that tries to break out
+# the bitfields a little for the non-double types. On 64-bit machines,
+# jsval_layout also has an 'asUIntPtr' branch.
+#
+# On 32-bit platforms, the 's' structure has a 'tag' member at the
+# exponent end of the 's' struct, and a 'payload' union at the mantissa
+# end. The 'payload' union's branches are things like JSString *,
+# JSObject *, and so on: the natural representations of the tags.
+#
+# On 64-bit platforms, the payload is 47 bits long; since C++ doesn't let
+# us declare bitfields that hold unions, we can't break it down so
+# neatly. In this case, we apply bit-shifting tricks to the 'asBits'
+# branch of the union to extract the tag.
+
+class Box(object):
+ def __init__(self, asBits, jtc):
+ self.asBits = asBits
+ self.jtc = jtc
+ # jsval_layout::asBits is uint64, but somebody botches the sign bit, 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): raise NotImplementedError
+ def as_address(self): raise NotImplementedError
+
+# Packed non-number boxing --- the format used on x86_64. It would be nice to simply
+# call JSVAL_TO_INT, etc. here, but the debugger is likely to see many jsvals, and
+# doing several inferior calls for each one seems like a bad idea.
+class Punbox(Box):
+
+ 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)
+
+# Cache information about the jsval type for this objfile.
+class jsvalTypeCache(object):
+ def __init__(self, cache):
+ # Capture the tag values.
+ d = gdb.types.make_enum_dict(gdb.lookup_type('JSValueType'))
+ self.DOUBLE = d['JSVAL_TYPE_DOUBLE']
+ self.INT32 = d['JSVAL_TYPE_INT32']
+ self.UNDEFINED = d['JSVAL_TYPE_UNDEFINED']
+ self.BOOLEAN = d['JSVAL_TYPE_BOOLEAN']
+ self.MAGIC = d['JSVAL_TYPE_MAGIC']
+ self.STRING = d['JSVAL_TYPE_STRING']
+ self.SYMBOL = d['JSVAL_TYPE_SYMBOL']
+ self.NULL = d['JSVAL_TYPE_NULL']
+ self.OBJECT = d['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('jsval_layout')
+class jsval_layout(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_jsval:
+ cache.mod_jsval = jsvalTypeCache(cache)
+ self.jtc = cache.mod_jsval
+
+ self.value = value
+ self.box = self.jtc.boxer(value['asBits'], self.jtc)
+
+ def to_string(self):
+ tag = self.box.tag()
+ if tag == self.jtc.INT32:
+ value = self.box.as_uint32()
+ signbit = 1 << 31
+ value = (value ^ signbit) - signbit
+ elif tag == self.jtc.UNDEFINED:
+ return 'JSVAL_VOID'
+ elif tag == self.jtc.BOOLEAN:
+ return 'JSVAL_TRUE' if self.box.as_uint32() else 'JSVAL_FALSE'
+ elif tag == self.jtc.MAGIC:
+ value = self.box.as_uint32()
+ if 0 <= value and value < len(self.jtc.magic_names):
+ return '$jsmagic(%s)' % (self.jtc.magic_names[value],)
+ else:
+ return '$jsmagic(%d)' % (value,)
+ elif tag == self.jtc.STRING:
+ value = self.box.as_address().cast(self.cache.JSString_ptr_t)
+ elif tag == self.jtc.SYMBOL:
+ value = self.box.as_address().cast(self.cache.JSSymbol_ptr_t)
+ elif tag == self.jtc.NULL:
+ return 'JSVAL_NULL'
+ elif tag == self.jtc.OBJECT:
+ value = self.box.as_address().cast(self.cache.JSObject_ptr_t)
+ elif tag == self.jtc.DOUBLE:
+ value = self.value['asDouble']
+ else:
+ return '$jsval(unrecognized!)'
+ return '$jsval(%s)' % (value,)
+
+@pretty_printer('JS::Value')
+class JSValue(object):
+ def __new__(cls, value, cache):
+ return jsval_layout(value['data'], cache)
diff --git a/js/src/gdb/mozilla/prettyprinters.py b/js/src/gdb/mozilla/prettyprinters.py
new file mode 100644
index 000000000..87035d0ef
--- /dev/null
+++ b/js/src/gdb/mozilla/prettyprinters.py
@@ -0,0 +1,368 @@
+# mozilla/prettyprinters.py --- infrastructure for SpiderMonkey's auto-loaded pretty-printers.
+
+import gdb
+import re
+
+# Decorators for declaring pretty-printers.
+#
+# In each case, the decoratee should be a SpiderMonkey-style pretty-printer
+# factory, taking both a gdb.Value instance and a TypeCache instance as
+# arguments; see TypeCache, below.
+
+# Check that |fn| hasn't been registered as a pretty-printer under some
+# other name already. (The 'enabled' flags used by GDB's
+# 'enable/disable/info pretty-printer' commands are simply stored as
+# properties of the function objects themselves, so a single function
+# object can't carry the 'enabled' flags for two different printers.)
+def check_for_reused_pretty_printer(fn):
+ if hasattr(fn, 'enabled'):
+ raise RuntimeError("pretty-printer function %r registered more than once" % fn)
+
+# a dictionary mapping gdb.Type tags to pretty-printer functions.
+printers_by_tag = {}
+
+# A decorator: add the decoratee as a pretty-printer lookup function for types
+# named |type_name|.
+def pretty_printer(type_name):
+ def add(fn):
+ check_for_reused_pretty_printer(fn)
+ add_to_subprinter_list(fn, type_name)
+ printers_by_tag[type_name] = fn
+ return fn
+ return add
+
+# a dictionary mapping gdb.Type tags to pretty-printer functions for pointers to
+# that type.
+ptr_printers_by_tag = {}
+
+# A decorator: add the decoratee as a pretty-printer lookup function for
+# pointers to types named |type_name|.
+def ptr_pretty_printer(type_name):
+ def add(fn):
+ check_for_reused_pretty_printer(fn)
+ add_to_subprinter_list(fn, "ptr-to-" + type_name)
+ ptr_printers_by_tag[type_name] = fn
+ return fn
+ return add
+
+# a dictionary mapping gdb.Type tags to pretty-printer functions for
+# references to that type.
+ref_printers_by_tag = {}
+
+# A decorator: add the decoratee as a pretty-printer lookup function for
+# references to instances of types named |type_name|.
+def ref_pretty_printer(type_name):
+ def add(fn):
+ check_for_reused_pretty_printer(fn)
+ add_to_subprinter_list(fn, "ref-to-" + type_name)
+ ref_printers_by_tag[type_name] = fn
+ return fn
+ return add
+
+# a dictionary mapping the template name portion of gdb.Type tags to
+# pretty-printer functions for instantiations of that template.
+template_printers_by_tag = {}
+
+# A decorator: add the decoratee as a pretty-printer lookup function for
+# instantiations of templates named |template_name|.
+def template_pretty_printer(template_name):
+ def add(fn):
+ check_for_reused_pretty_printer(fn)
+ add_to_subprinter_list(fn, 'instantiations-of-' + template_name)
+ template_printers_by_tag[template_name] = fn
+ return fn
+ return add
+
+# A list of (REGEXP, PRINTER) pairs, such that if REGEXP (a RegexObject)
+# matches the result of converting a gdb.Value's type to a string, then
+# PRINTER is a pretty-printer lookup function that will probably like that
+# value.
+printers_by_regexp = []
+
+# A decorator: add the decoratee as a pretty-printer factory for types
+# that, when converted to a string, match |pattern|. Use |name| as the
+# pretty-printer's name, when listing, enabling and disabling.
+def pretty_printer_for_regexp(pattern, name):
+ compiled = re.compile(pattern)
+ def add(fn):
+ check_for_reused_pretty_printer(fn)
+ add_to_subprinter_list(fn, name)
+ printers_by_regexp.append((compiled, fn))
+ return fn
+ return add
+
+# Forget all pretty-printer lookup functions defined in the module name
+# |module_name|, if any exist. Use this at the top of each pretty-printer
+# module like this:
+#
+# clear_module_printers(__name__)
+def clear_module_printers(module_name):
+ global printers_by_tag, ptr_printers_by_tag, ref_printers_by_tag
+ global template_printers_by_tag, printers_by_regexp
+
+ # Remove all pretty-printers defined in the module named |module_name|
+ # from d.
+ def clear_dictionary(d):
+ # Walk the dictionary, building a list of keys whose entries we
+ # should remove. (It's not safe to delete entries from a dictionary
+ # while we're iterating over it.)
+ to_delete = []
+ for (k, v) in d.items():
+ if v.__module__ == module_name:
+ to_delete.append(k)
+ remove_from_subprinter_list(v)
+ for k in to_delete:
+ del d[k]
+
+ clear_dictionary(printers_by_tag)
+ clear_dictionary(ptr_printers_by_tag)
+ clear_dictionary(ref_printers_by_tag)
+ clear_dictionary(template_printers_by_tag)
+
+ # Iterate over printers_by_regexp, deleting entries from the given module.
+ new_list = []
+ for p in printers_by_regexp:
+ if p.__module__ == module_name:
+ remove_from_subprinter_list(p)
+ else:
+ new_list.append(p)
+ printers_by_regexp = new_list
+
+# Our subprinters array. The 'subprinters' attributes of all lookup
+# functions returned by lookup_for_objfile point to this array instance,
+# which we mutate as subprinters are added and removed.
+subprinters = []
+
+# Set up the 'name' and 'enabled' attributes on |subprinter|, and add it to our
+# list of all SpiderMonkey subprinters.
+def add_to_subprinter_list(subprinter, name):
+ subprinter.name = name
+ subprinter.enabled = True
+ subprinters.append(subprinter)
+
+# Remove |subprinter| from our list of all SpiderMonkey subprinters.
+def remove_from_subprinter_list(subprinter):
+ subprinters.remove(subprinter)
+
+# An exception class meaning, "This objfile has no SpiderMonkey in it."
+class NotSpiderMonkeyObjfileError(TypeError):
+ pass
+
+# TypeCache: a cache for frequently used information about an objfile.
+#
+# When a new SpiderMonkey objfile is loaded, we construct an instance of
+# this class for it. Then, whenever we construct a pretty-printer for some
+# gdb.Value, we also pass, as a second argument, the TypeCache for the
+# objfile to which that value's type belongs.
+#
+# if objfile doesn't seem to have SpiderMonkey code in it, the constructor
+# raises NotSpiderMonkeyObjfileError.
+#
+# Pretty-printer modules may add attributes to this to hold their own
+# cached values. Such attributes should be named mod_NAME, where the module
+# is named mozilla.NAME; for example, mozilla.JSString should store its
+# metadata in the TypeCache's mod_JSString attribute.
+class TypeCache(object):
+ def __init__(self, objfile):
+ self.objfile = objfile
+
+ # Unfortunately, the Python interface doesn't allow us to specify
+ # the objfile in whose scope lookups should occur. But simply
+ # knowing that we need to lookup the types afresh is probably
+ # enough.
+ self.void_t = gdb.lookup_type('void')
+ self.void_ptr_t = self.void_t.pointer()
+ try:
+ self.JSString_ptr_t = gdb.lookup_type('JSString').pointer()
+ self.JSSymbol_ptr_t = gdb.lookup_type('JS::Symbol').pointer()
+ self.JSObject_ptr_t = gdb.lookup_type('JSObject').pointer()
+ except gdb.error:
+ raise NotSpiderMonkeyObjfileError
+
+ self.mod_GCCellPtr = None
+ self.mod_Interpreter = None
+ self.mod_JSObject = None
+ self.mod_JSString = None
+ self.mod_jsval = None
+ self.mod_ExecutableAllocator = None
+ self.mod_IonGraph = None
+
+# Yield a series of all the types that |t| implements, by following typedefs
+# and iterating over base classes. Specifically:
+# - |t| itself is the first value yielded.
+# - If we yield a typedef, we later yield its definition.
+# - If we yield a type with base classes, we later yield those base classes.
+# - If we yield a type with some base classes that are typedefs,
+# we yield all the type's base classes before following the typedefs.
+# (Actually, this never happens, because G++ doesn't preserve the typedefs in
+# the DWARF.)
+#
+# This is a hokey attempt to order the implemented types by meaningfulness when
+# pretty-printed. Perhaps it is entirely misguided, and we should actually
+# collect all applicable pretty-printers, and then use some ordering on the
+# pretty-printers themselves.
+#
+# We may yield a type more than once (say, if it appears more than once in the
+# class hierarchy).
+def implemented_types(t):
+
+ # Yield all types that follow |t|.
+ def followers(t):
+ if t.code == gdb.TYPE_CODE_TYPEDEF:
+ yield t.target()
+ for t2 in followers(t.target()): yield t2
+ elif t.code == gdb.TYPE_CODE_STRUCT:
+ base_classes = []
+ for f in t.fields():
+ if f.is_base_class:
+ yield f.type
+ base_classes.append(f.type)
+ for b in base_classes:
+ for t2 in followers(b): yield t2
+
+ yield t
+ for t2 in followers(t): yield t2
+
+template_regexp = re.compile("([\w_:]+)<")
+
+# Construct and return a pretty-printer lookup function for objfile, or
+# return None if the objfile doesn't contain SpiderMonkey code
+# (specifically, definitions for SpiderMonkey types).
+def lookup_for_objfile(objfile):
+ # Create a type cache for this objfile.
+ try:
+ cache = TypeCache(objfile)
+ except NotSpiderMonkeyObjfileError:
+ if gdb.parameter("verbose"):
+ gdb.write("objfile '%s' has no SpiderMonkey code; not registering pretty-printers\n"
+ % (objfile.filename,))
+ return None
+
+ # Return a pretty-printer for |value|, if we have one. This is the lookup
+ # function object we place in each gdb.Objfile's pretty-printers list, so it
+ # carries |name|, |enabled|, and |subprinters| attributes.
+ def lookup(value):
+ # If |table| has a pretty-printer for |tag|, apply it to |value|.
+ def check_table(table, tag):
+ if tag in table:
+ f = table[tag]
+ if f.enabled:
+ return f(value, cache)
+ return None
+
+ def check_table_by_type_name(table, t):
+ if t.code == gdb.TYPE_CODE_TYPEDEF:
+ return check_table(table, str(t))
+ elif t.code == gdb.TYPE_CODE_STRUCT and t.tag:
+ return check_table(table, t.tag)
+ else:
+ return None
+
+ for t in implemented_types(value.type):
+ if t.code == gdb.TYPE_CODE_PTR:
+ for t2 in implemented_types(t.target()):
+ p = check_table_by_type_name(ptr_printers_by_tag, t2)
+ if p: return p
+ elif t.code == gdb.TYPE_CODE_REF:
+ for t2 in implemented_types(t.target()):
+ p = check_table_by_type_name(ref_printers_by_tag, t2)
+ if p: return p
+ else:
+ p = check_table_by_type_name(printers_by_tag, t)
+ if p: return p
+ if t.code == gdb.TYPE_CODE_STRUCT and t.tag:
+ m = template_regexp.match(t.tag)
+ if m:
+ p = check_table(template_printers_by_tag, m.group(1))
+ if p: return p
+
+ # Failing that, look for a printer in printers_by_regexp. We have
+ # to scan the whole list, so regexp printers should be used
+ # sparingly.
+ s = str(value.type)
+ for (r, f) in printers_by_regexp:
+ if f.enabled:
+ m = r.match(s)
+ if m:
+ p = f(value, cache)
+ if p: return p
+
+ # No luck.
+ return None
+
+ # Give |lookup| the attributes expected of a pretty-printer with
+ # subprinters, for enabling and disabling.
+ lookup.name = "SpiderMonkey"
+ lookup.enabled = True
+ lookup.subprinters = subprinters
+
+ return lookup
+
+# A base class for pretty-printers for pointer values that handles null
+# pointers, by declining to construct a pretty-printer for them at all.
+# Derived classes may simply assume that self.value is non-null.
+#
+# To help share code, this class can also be used with reference types.
+#
+# This class provides the following methods, which subclasses are free to
+# override:
+#
+# __init__(self, value, cache): Save value and cache as properties by those names
+# on the instance.
+#
+# to_string(self): format the type's name and address, as GDB would, and then
+# call a 'summary' method (which the subclass must define) to produce a
+# description of the referent.
+#
+# Note that pretty-printers returning a 'string' display hint must not use
+# this default 'to_string' method, as GDB will take everything it returns,
+# including the type name and address, as string contents.
+class Pointer(object):
+ def __new__(cls, value, cache):
+ # Don't try to provide pretty-printers for NULL pointers.
+ if value.type.strip_typedefs().code == gdb.TYPE_CODE_PTR and value == 0:
+ return None
+ return super(Pointer, cls).__new__(cls)
+
+ def __init__(self, value, cache):
+ self.value = value
+ self.cache = cache
+
+ def to_string(self):
+ # See comment above.
+ assert not hasattr(self, 'display_hint') or self.display_hint() != 'string'
+ concrete_type = self.value.type.strip_typedefs()
+ if concrete_type.code == gdb.TYPE_CODE_PTR:
+ address = self.value.cast(self.cache.void_ptr_t)
+ elif concrete_type.code == gdb.TYPE_CODE_REF:
+ address = '@' + str(self.value.address.cast(self.cache.void_ptr_t))
+ else:
+ assert not "mozilla.prettyprinters.Pointer applied to bad value type"
+ try:
+ summary = self.summary()
+ except gdb.MemoryError as r:
+ summary = str(r)
+ v = '(%s) %s %s' % (self.value.type, address, summary)
+ return v
+
+ def summary(self):
+ raise NotImplementedError
+
+field_enum_value = None
+
+# Given |t|, a gdb.Type instance representing an enum type, return the
+# numeric value of the enum value named |name|.
+#
+# Pre-2012-4-18 versions of GDB store the value of an enum member on the
+# gdb.Field's 'bitpos' attribute; later versions store it on the 'enumval'
+# attribute. This function retrieves the value from either.
+def enum_value(t, name):
+ global field_enum_value
+ f = t[name]
+ # Monkey-patching is a-okay in polyfills! Just because.
+ if not field_enum_value:
+ if hasattr(f, 'enumval'):
+ field_enum_value = lambda f: f.enumval
+ else:
+ field_enum_value = lambda f: f.bitpos
+ return field_enum_value(f)
diff --git a/js/src/gdb/mozilla/unwind.py b/js/src/gdb/mozilla/unwind.py
new file mode 100644
index 000000000..adea63ea6
--- /dev/null
+++ b/js/src/gdb/mozilla/unwind.py
@@ -0,0 +1,591 @@
+# mozilla/unwind.py --- unwinder and frame filter for SpiderMonkey
+
+import gdb
+import gdb.types
+from gdb.FrameDecorator import FrameDecorator
+import re
+import platform
+from mozilla.ExecutableAllocator import jsjitExecutableAllocatorCache, jsjitExecutableAllocator
+
+# For ease of use in Python 2, we use "long" instead of "int"
+# everywhere.
+try:
+ long
+except NameError:
+ long = int
+
+# The Python 3 |map| built-in works lazily, but in Python 2 we need
+# itertools.imap to get this.
+try:
+ from itertools import imap
+except ImportError:
+ imap = map
+
+_have_unwinder = True
+try:
+ from gdb.unwinder import Unwinder
+except ImportError:
+ _have_unwinder = False
+ # We need something here; it doesn't matter what as no unwinder
+ # will ever be instantiated.
+ Unwinder = object
+
+def debug(something):
+ # print("@@ " + something)
+ pass
+
+# Maps frametype enum base names to corresponding class.
+SizeOfFramePrefix = {
+ 'JitFrame_IonJS': 'ExitFrameLayout',
+ 'JitFrame_BaselineJS': 'JitFrameLayout',
+ 'JitFrame_BaselineStub': 'BaselineStubFrameLayout',
+ 'JitFrame_IonStub': 'JitStubFrameLayout',
+ # Technically EntryFrameLayout, but that doesn't wind up in the
+ # debuginfo because there are no uses of it.
+ 'JitFrame_Entry': 'JitFrameLayout',
+ 'JitFrame_Rectifier': 'RectifierFrameLayout',
+ 'JitFrame_IonAccessorIC': 'IonAccessorICFrameLayout',
+ 'JitFrame_Exit': 'ExitFrameLayout',
+ 'JitFrame_Bailout': 'JitFrameLayout',
+}
+
+# All types and symbols that we need are attached to an object that we
+# can dispose of as needed.
+class UnwinderTypeCache(object):
+ def __init__(self):
+ self.d = None
+ self.frame_enum_names = {}
+ self.frame_class_types = {}
+
+ # We take this bizarre approach to defer trying to look up any
+ # symbols until absolutely needed. Without this, the loading
+ # approach taken by the gdb-tests would cause spurious exceptions.
+ def __getattr__(self, name):
+ if self.d is None:
+ self.initialize()
+ return self.d[name]
+
+ def value(self, name):
+ return long(gdb.parse_and_eval('js::jit::' + name))
+
+ def initialize(self):
+ self.d = {}
+ self.d['FRAMETYPE_MASK'] = (1 << self.value('FRAMETYPE_BITS')) - 1
+ self.d['FRAMESIZE_SHIFT'] = self.value('FRAMESIZE_SHIFT')
+ self.d['FRAME_HEADER_SIZE_SHIFT'] = self.value('FRAME_HEADER_SIZE_SHIFT')
+ self.d['FRAME_HEADER_SIZE_MASK'] = self.value('FRAME_HEADER_SIZE_MASK')
+
+ self.compute_frame_info()
+ commonFrameLayout = gdb.lookup_type('js::jit::CommonFrameLayout')
+ self.d['typeCommonFrameLayout'] = commonFrameLayout
+ self.d['typeCommonFrameLayoutPointer'] = commonFrameLayout.pointer()
+ self.d['per_tls_data'] = gdb.lookup_global_symbol('js::TlsPerThreadData')
+
+ self.d['void_starstar'] = gdb.lookup_type('void').pointer().pointer()
+ self.d['mod_ExecutableAllocator'] = jsjitExecutableAllocatorCache()
+
+ jitframe = gdb.lookup_type("js::jit::JitFrameLayout")
+ self.d['jitFrameLayoutPointer'] = jitframe.pointer()
+
+ self.d['CalleeToken_Function'] = self.value("CalleeToken_Function")
+ self.d['CalleeToken_FunctionConstructing'] = self.value("CalleeToken_FunctionConstructing")
+ self.d['CalleeToken_Script'] = self.value("CalleeToken_Script")
+ self.d['JSFunction'] = gdb.lookup_type("JSFunction").pointer()
+ self.d['JSScript'] = gdb.lookup_type("JSScript").pointer()
+ self.d['Value'] = gdb.lookup_type("JS::Value")
+
+ self.d['SOURCE_SLOT'] = long(gdb.parse_and_eval('js::ScriptSourceObject::SOURCE_SLOT'))
+ self.d['NativeObject'] = gdb.lookup_type("js::NativeObject").pointer()
+ self.d['HeapSlot'] = gdb.lookup_type("js::HeapSlot").pointer()
+ self.d['ScriptSource'] = gdb.lookup_type("js::ScriptSource").pointer()
+
+ # Compute maps related to jit frames.
+ def compute_frame_info(self):
+ t = gdb.lookup_type('enum js::jit::FrameType')
+ for field in t.fields():
+ # Strip off "js::jit::".
+ name = field.name[9:]
+ enumval = long(field.enumval)
+ self.d[name] = enumval
+ self.frame_enum_names[enumval] = name
+ class_type = gdb.lookup_type('js::jit::' + SizeOfFramePrefix[name])
+ self.frame_class_types[enumval] = class_type.pointer()
+
+# gdb doesn't have a direct way to tell us if a given address is
+# claimed by some shared library or the executable. See
+# https://sourceware.org/bugzilla/show_bug.cgi?id=19288
+# In the interest of not requiring a patched gdb, instead we read
+# /proc/.../maps. This only works locally, but maybe could work
+# remotely using "remote get". FIXME.
+def parse_proc_maps():
+ mapfile = '/proc/' + str(gdb.selected_inferior().pid) + '/maps'
+ # Note we only examine executable mappings here.
+ matcher = re.compile("^([a-fA-F0-9]+)-([a-fA-F0-9]+)\s+..x.\s+\S+\s+\S+\s+\S*(.*)$")
+ mappings = []
+ with open(mapfile, "r") as inp:
+ for line in inp:
+ match = matcher.match(line)
+ if not match:
+ # Header lines and such.
+ continue
+ start = match.group(1)
+ end = match.group(2)
+ name = match.group(3).strip()
+ if name is '' or (name.startswith('[') and name is not '[vdso]'):
+ # Skip entries not corresponding to a file.
+ continue
+ mappings.append((long(start, 16), long(end, 16)))
+ return mappings
+
+# A symbol/value pair as expected from gdb frame decorators.
+class FrameSymbol(object):
+ def __init__(self, sym, val):
+ self.sym = sym
+ self.val = val
+
+ def symbol(self):
+ return self.sym
+
+ def value(self):
+ return self.val
+
+# This represents a single JIT frame for the purposes of display.
+# That is, the frame filter creates instances of this when it sees a
+# JIT frame in the stack.
+class JitFrameDecorator(FrameDecorator):
+ def __init__(self, base, info, cache):
+ super(JitFrameDecorator, self).__init__(base)
+ self.info = info
+ self.cache = cache
+
+ def _decode_jitframe(self, this_frame):
+ calleetoken = long(this_frame['calleeToken_'])
+ tag = calleetoken & 3
+ calleetoken = calleetoken ^ tag
+ function = None
+ script = None
+ if tag == self.cache.CalleeToken_Function or tag == self.cache.CalleeToken_FunctionConstructing:
+ fptr = gdb.Value(calleetoken).cast(self.cache.JSFunction)
+ try:
+ atom = fptr['atom_']
+ if atom:
+ function = str(atom)
+ except gdb.MemoryError:
+ function = "(could not read function name)"
+ script = fptr['u']['i']['s']['script_']
+ elif tag == self.cache.CalleeToken_Script:
+ script = gdb.Value(calleetoken).cast(self.cache.JSScript)
+ return {"function": function, "script": script}
+
+ def function(self):
+ if self.info["name"] is None:
+ return FrameDecorator.function(self)
+ name = self.info["name"]
+ result = "<<" + name
+ # If we have a frame, we can extract the callee information
+ # from it for display here.
+ this_frame = self.info["this_frame"]
+ if this_frame is not None:
+ if gdb.types.has_field(this_frame.type.target(), "calleeToken_"):
+ function = self._decode_jitframe(this_frame)["function"]
+ if function is not None:
+ result = result + " " + function
+ return result + ">>"
+
+ def filename(self):
+ this_frame = self.info["this_frame"]
+ if this_frame is not None:
+ if gdb.types.has_field(this_frame.type.target(), "calleeToken_"):
+ script = self._decode_jitframe(this_frame)["script"]
+ if script is not None:
+ obj = script['sourceObject_']['value']
+ # Verify that this is a ScriptSource object.
+ # FIXME should also deal with wrappers here.
+ nativeobj = obj.cast(self.cache.NativeObject)
+ # See bug 987069 and despair. At least this
+ # approach won't give exceptions.
+ class_name = nativeobj['group_']['value']['clasp_']['name'].string("ISO-8859-1")
+ if class_name != "ScriptSource":
+ return FrameDecorator.filename(self)
+ scriptsourceobj = (nativeobj + 1).cast(self.cache.HeapSlot)[self.cache.SOURCE_SLOT]
+ scriptsource = scriptsourceobj['value']['data']['asBits'] << 1
+ scriptsource = scriptsource.cast(self.cache.ScriptSource)
+ return scriptsource['filename_']['mTuple']['mFirstA'].string()
+ return FrameDecorator.filename(self)
+
+ def frame_args(self):
+ this_frame = self.info["this_frame"]
+ if this_frame is None:
+ return FrameDecorator.frame_args(self)
+ if not gdb.types.has_field(this_frame.type.target(), "numActualArgs_"):
+ return FrameDecorator.frame_args(self)
+ # See if this is a function call.
+ if self._decode_jitframe(this_frame)["function"] is None:
+ return FrameDecorator.frame_args(self)
+ # Construct and return an iterable of all the arguments.
+ result = []
+ num_args = long(this_frame["numActualArgs_"])
+ # Sometimes we see very large values here, so truncate it to
+ # bypass the damage.
+ if num_args > 10:
+ num_args = 10
+ args_ptr = (this_frame + 1).cast(self.cache.Value.pointer())
+ for i in range(num_args + 1):
+ # Synthesize names, since there doesn't seem to be
+ # anything better to do.
+ if i == 0:
+ name = 'this'
+ else:
+ name = 'arg%d' % i
+ result.append(FrameSymbol(name, args_ptr[i]))
+ return result
+
+# A frame filter for SpiderMonkey.
+class SpiderMonkeyFrameFilter(object):
+ # |state_holder| is either None, or an instance of
+ # SpiderMonkeyUnwinder. If the latter, then this class will
+ # reference the |unwinder_state| attribute to find the current
+ # unwinder state.
+ def __init__(self, cache, state_holder):
+ self.name = "SpiderMonkey"
+ self.enabled = True
+ self.priority = 100
+ self.state_holder = state_holder
+ self.cache = cache
+
+ def maybe_wrap_frame(self, frame):
+ if self.state_holder is None or self.state_holder.unwinder_state is None:
+ return frame
+ base = frame.inferior_frame()
+ info = self.state_holder.unwinder_state.get_frame(base)
+ if info is None:
+ return frame
+ return JitFrameDecorator(frame, info, self.cache)
+
+ def filter(self, frame_iter):
+ return imap(self.maybe_wrap_frame, frame_iter)
+
+# A frame id class, as specified by the gdb unwinder API.
+class SpiderMonkeyFrameId(object):
+ def __init__(self, sp, pc):
+ self.sp = sp
+ self.pc = pc
+
+# This holds all the state needed during a given unwind. Each time a
+# new unwind is done, a new instance of this class is created. It
+# keeps track of all the state needed to unwind JIT frames. Note that
+# this class is not directly instantiated.
+#
+# This is a base class, and must be specialized for each target
+# architecture, both because we need to use arch-specific register
+# names, and because entry frame unwinding is arch-specific.
+# See https://sourceware.org/bugzilla/show_bug.cgi?id=19286 for info
+# about the register name issue.
+#
+# Each subclass must define SP_REGISTER, PC_REGISTER, and
+# SENTINEL_REGISTER (see x64UnwinderState for info); and implement
+# unwind_entry_frame_registers.
+class UnwinderState(object):
+ def __init__(self, typecache):
+ self.next_sp = None
+ self.next_type = None
+ self.activation = None
+ # An unwinder instance is specific to a thread. Record the
+ # selected thread for later verification.
+ self.thread = gdb.selected_thread()
+ self.frame_map = {}
+ self.proc_mappings = None
+ try:
+ self.proc_mappings = parse_proc_maps()
+ except IOError:
+ pass
+ self.typecache = typecache
+
+ # If the given gdb.Frame was created by this unwinder, return the
+ # corresponding informational dictionary for the frame.
+ # Otherwise, return None. This is used by the frame filter to
+ # display extra information about the frame.
+ def get_frame(self, frame):
+ sp = long(frame.read_register(self.SP_REGISTER))
+ if sp in self.frame_map:
+ return self.frame_map[sp]
+ return None
+
+ # Add information about a frame to the frame map. This map is
+ # queried by |self.get_frame|. |sp| is the frame's stack pointer,
+ # and |name| the frame's type as a string, e.g. "JitFrame_Exit".
+ def add_frame(self, sp, name = None, this_frame = None):
+ self.frame_map[long(sp)] = { "name": name, "this_frame": this_frame }
+
+ # See whether |pc| is claimed by some text mapping. See
+ # |parse_proc_maps| for details on how the decision is made.
+ def text_address_claimed(self, pc):
+ for (start, end) in self.proc_mappings:
+ if (pc >= start and pc <= end):
+ return True
+ return False
+
+ # See whether |pc| is claimed by the Jit.
+ def is_jit_address(self, pc):
+ if self.proc_mappings != None:
+ return not self.text_address_claimed(pc)
+
+ ptd = self.get_tls_per_thread_data()
+ runtime = ptd['runtime_']
+ if runtime == 0:
+ return False
+
+ jitRuntime = runtime['jitRuntime_']
+ if jitRuntime == 0:
+ return False
+
+ execAllocators = [jitRuntime['execAlloc_'], jitRuntime['backedgeExecAlloc_']]
+ for execAlloc in execAllocators:
+ for pool in jsjitExecutableAllocator(execAlloc, self.typecache):
+ pages = pool['m_allocation']['pages']
+ size = pool['m_allocation']['size']
+ if pages <= pc and pc < pages + size:
+ return True
+ return False
+
+ # Check whether |self| is valid for the selected thread.
+ def check(self):
+ return gdb.selected_thread() is self.thread
+
+ # Essentially js::TlsPerThreadData.get().
+ def get_tls_per_thread_data(self):
+ return self.typecache.per_tls_data.value()['mValue']
+
+ # |common| is a pointer to a CommonFrameLayout object. Return a
+ # tuple (local_size, header_size, frame_type), where |size| is the
+ # integer size of the previous frame's locals; |header_size| is
+ # the size of this frame's header; and |frame_type| is an integer
+ # representing the previous frame's type.
+ def unpack_descriptor(self, common):
+ value = long(common['descriptor_'])
+ local_size = value >> self.typecache.FRAMESIZE_SHIFT
+ header_size = ((value >> self.typecache.FRAME_HEADER_SIZE_SHIFT) &
+ self.typecache.FRAME_HEADER_SIZE_MASK)
+ header_size = header_size * self.typecache.void_starstar.sizeof
+ frame_type = long(value & self.typecache.FRAMETYPE_MASK)
+ if frame_type == self.typecache.JitFrame_Entry:
+ # Trampoline-x64.cpp pushes a JitFrameLayout object, but
+ # the stack pointer is actually adjusted as if a
+ # CommonFrameLayout object was pushed.
+ header_size = self.typecache.typeCommonFrameLayout.sizeof
+ return (local_size, header_size, frame_type)
+
+ # Create a new frame for gdb. This makes a new unwind info object
+ # and fills it in, then returns it. It also registers any
+ # pertinent information with the frame filter for later display.
+ #
+ # |pc| is the PC from the pending frame
+ # |sp| is the stack pointer to use
+ # |frame| points to the CommonFrameLayout object
+ # |frame_type| is a integer, one of the |enum FrameType| values,
+ # describing the current frame.
+ # |pending_frame| is the pending frame (see the gdb unwinder
+ # documentation).
+ def create_frame(self, pc, sp, frame, frame_type, pending_frame):
+ # Make a frame_id that claims that |frame| is sort of like a
+ # frame pointer for this frame.
+ frame_id = SpiderMonkeyFrameId(frame, pc)
+
+ # Read the frame layout object to find the next such object.
+ # This lets us unwind the necessary registers for the next
+ # frame, and also update our internal state to match.
+ common = frame.cast(self.typecache.typeCommonFrameLayoutPointer)
+ next_pc = common['returnAddress_']
+ (local_size, header_size, next_type) = self.unpack_descriptor(common)
+ next_sp = frame + header_size + local_size
+
+ # Compute the type of the next oldest frame's descriptor.
+ this_class_type = self.typecache.frame_class_types[frame_type]
+ this_frame = frame.cast(this_class_type)
+
+ # Register this frame so the frame filter can find it. This
+ # is registered using SP because we don't have any other good
+ # approach -- you can't get the frame id from a gdb.Frame.
+ # https://sourceware.org/bugzilla/show_bug.cgi?id=19800
+ frame_name = self.typecache.frame_enum_names[frame_type]
+ self.add_frame(sp, name = frame_name, this_frame = this_frame)
+
+ # Update internal state for the next unwind.
+ self.next_sp = next_sp
+ self.next_type = next_type
+
+ unwind_info = pending_frame.create_unwind_info(frame_id)
+ unwind_info.add_saved_register(self.PC_REGISTER, next_pc)
+ unwind_info.add_saved_register(self.SP_REGISTER, next_sp)
+ # FIXME it would be great to unwind any other registers here.
+ return unwind_info
+
+ # Unwind an "ordinary" JIT frame. This is used for JIT frames
+ # other than enter and exit frames. Returns the newly-created
+ # unwind info for gdb.
+ def unwind_ordinary(self, pc, pending_frame):
+ return self.create_frame(pc, self.next_sp, self.next_sp,
+ self.next_type, pending_frame)
+
+ # Unwind an exit frame. Returns None if this cannot be done;
+ # otherwise returns the newly-created unwind info for gdb.
+ def unwind_exit_frame(self, pc, pending_frame):
+ if self.activation == 0:
+ # Reached the end of the list.
+ return None
+ elif self.activation is None:
+ ptd = self.get_tls_per_thread_data()
+ self.activation = ptd['runtime_']['jitActivation']
+ jittop = ptd['runtime_']['jitTop']
+ else:
+ jittop = self.activation['prevJitTop_']
+ self.activation = self.activation['prevJitActivation_']
+
+ if jittop == 0:
+ return None
+
+ exit_sp = pending_frame.read_register(self.SP_REGISTER)
+ frame_type = self.typecache.JitFrame_Exit
+ return self.create_frame(pc, exit_sp, jittop, frame_type, pending_frame)
+
+ # A wrapper for unwind_entry_frame_registers that handles
+ # architecture-independent boilerplate.
+ def unwind_entry_frame(self, pc, pending_frame):
+ sp = self.next_sp
+ # Notify the frame filter.
+ self.add_frame(sp, name = 'JitFrame_Entry')
+ # Make an unwind_info for the per-architecture code to fill in.
+ frame_id = SpiderMonkeyFrameId(sp, pc)
+ unwind_info = pending_frame.create_unwind_info(frame_id)
+ self.unwind_entry_frame_registers(sp, unwind_info)
+ self.next_sp = None
+ self.next_type = None
+ return unwind_info
+
+ # The main entry point that is called to try to unwind a JIT frame
+ # of any type. Returns None if this cannot be done; otherwise
+ # returns the newly-created unwind info for gdb.
+ def unwind(self, pending_frame):
+ pc = pending_frame.read_register(self.PC_REGISTER)
+
+ # If the jit does not claim this address, bail. GDB defers to our
+ # unwinder by default, but we don't really want that kind of power.
+ if not self.is_jit_address(long(pc)):
+ return None
+
+ if self.next_sp is not None:
+ if self.next_type == self.typecache.JitFrame_Entry:
+ return self.unwind_entry_frame(pc, pending_frame)
+ return self.unwind_ordinary(pc, pending_frame)
+ # Maybe we've found an exit frame. FIXME I currently don't
+ # know how to identify these precisely, so we'll just hope for
+ # the time being.
+ return self.unwind_exit_frame(pc, pending_frame)
+
+# The UnwinderState subclass for x86-64.
+class x64UnwinderState(UnwinderState):
+ SP_REGISTER = 'rsp'
+ PC_REGISTER = 'rip'
+
+ # A register unique to this architecture, that is also likely to
+ # have been saved in any frame. The best thing to use here is
+ # some arch-specific name for PC or SP.
+ SENTINEL_REGISTER = 'rip'
+
+ # Must be in sync with Trampoline-x64.cpp:generateEnterJIT. Note
+ # that rip isn't pushed there explicitly, but rather by the
+ # previous function's call.
+ PUSHED_REGS = ["r15", "r14", "r13", "r12", "rbx", "rbp", "rip"]
+
+ # Fill in the unwound registers for an entry frame.
+ def unwind_entry_frame_registers(self, sp, unwind_info):
+ sp = sp.cast(self.typecache.void_starstar)
+ # Skip the "result" push.
+ sp = sp + 1
+ for reg in self.PUSHED_REGS:
+ data = sp.dereference()
+ sp = sp + 1
+ unwind_info.add_saved_register(reg, data)
+ if reg is "rbp":
+ unwind_info.add_saved_register(self.SP_REGISTER, sp)
+
+# The unwinder object. This provides the "user interface" to the JIT
+# unwinder, and also handles constructing or destroying UnwinderState
+# objects as needed.
+class SpiderMonkeyUnwinder(Unwinder):
+ # A list of all the possible unwinders. See |self.make_unwinder|.
+ UNWINDERS = [x64UnwinderState]
+
+ def __init__(self, typecache):
+ super(SpiderMonkeyUnwinder, self).__init__("SpiderMonkey")
+ self.typecache = typecache
+ self.unwinder_state = None
+
+ # Disabled by default until we figure out issues in gdb.
+ self.enabled = False
+ gdb.write("SpiderMonkey unwinder is disabled by default, to enable it type:\n" +
+ "\tenable unwinder .* SpiderMonkey\n")
+ # Some versions of gdb did not flush the internal frame cache
+ # when enabling or disabling an unwinder. This was fixed in
+ # the same release of gdb that added the breakpoint_created
+ # event.
+ if not hasattr(gdb.events, "breakpoint_created"):
+ gdb.write("\tflushregs\n")
+
+ # We need to invalidate the unwinder state whenever the
+ # inferior starts executing. This avoids having a stale
+ # cache.
+ gdb.events.cont.connect(self.invalidate_unwinder_state)
+ assert self.test_sentinels()
+
+ def test_sentinels(self):
+ # Self-check.
+ regs = {}
+ for unwinder in self.UNWINDERS:
+ if unwinder.SENTINEL_REGISTER in regs:
+ return False
+ regs[unwinder.SENTINEL_REGISTER] = 1
+ return True
+
+ def make_unwinder(self, pending_frame):
+ # gdb doesn't provide a good way to find the architecture.
+ # See https://sourceware.org/bugzilla/show_bug.cgi?id=19399
+ # So, we look at each known architecture and see if the
+ # corresponding "unique register" is known.
+ for unwinder in self.UNWINDERS:
+ try:
+ pending_frame.read_register(unwinder.SENTINEL_REGISTER)
+ except:
+ # Failed to read the register, so let's keep going.
+ # This is more fragile than it might seem, because it
+ # fails if the sentinel register wasn't saved in the
+ # previous frame.
+ continue
+ return unwinder(self.typecache)
+ return None
+
+ def __call__(self, pending_frame):
+ if self.unwinder_state is None or not self.unwinder_state.check():
+ self.unwinder_state = self.make_unwinder(pending_frame)
+ if not self.unwinder_state:
+ return None
+ return self.unwinder_state.unwind(pending_frame)
+
+ def invalidate_unwinder_state(self, *args, **kwargs):
+ self.unwinder_state = None
+
+# Register the unwinder and frame filter with |objfile|. If |objfile|
+# is None, register them globally.
+def register_unwinder(objfile):
+ type_cache = UnwinderTypeCache()
+ unwinder = None
+ # This currently only works on Linux, due to parse_proc_maps.
+ if _have_unwinder and platform.system() == "Linux":
+ unwinder = SpiderMonkeyUnwinder(type_cache)
+ gdb.unwinder.register_unwinder(objfile, unwinder, replace=True)
+ # We unconditionally register the frame filter, because at some
+ # point we'll add interpreter frame filtering.
+ filt = SpiderMonkeyFrameFilter(type_cache, unwinder)
+ if objfile is None:
+ objfile = gdb
+ objfile.frame_filters[filt.name] = filt