Bug 1254297 - initial work on JS interpreter frame filter draft
authorTom Tromey <tom@tromey.com>
Mon, 31 Oct 2016 09:27:47 -0600
changeset 431821 0e3a4d4e22c439e26160b228e76f83d749421643
parent 431820 46adb3e5553058edf1685f379127fd878a065993
child 535469 951b6c432b312b495fbb9cf43b8f1cdc34c6185e
push id34114
push userbmo:ttromey@mozilla.com
push dateMon, 31 Oct 2016 17:44:35 +0000
bugs1254297
milestone52.0a1
Bug 1254297 - initial work on JS interpreter frame filter MozReview-Commit-ID: Li81ym8XNTP
js/src/gdb/mozilla/unwind.py
--- a/js/src/gdb/mozilla/unwind.py
+++ b/js/src/gdb/mozilla/unwind.py
@@ -9,23 +9,16 @@ from mozilla.ExecutableAllocator import 
 
 # 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
@@ -144,16 +137,33 @@ class FrameSymbol(object):
         self.val = val
 
     def symbol(self):
         return self.sym
 
     def value(self):
         return self.val
 
+# Get the filename of a JSScript; or return None if this isn't
+# possible.
+def jsscript_filename(script, cache):
+    obj = script['sourceObject_']['value']
+    # Verify that this is a ScriptSource object.
+    # FIXME should also deal with wrappers here.
+    nativeobj = obj.cast(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 None
+    scriptsourceobj = (nativeobj + 1).cast(cache.HeapSlot)[cache.SOURCE_SLOT]
+    scriptsource = scriptsourceobj['value']['data']['asBits'] << 1
+    scriptsource = scriptsource.cast(cache.ScriptSource)
+    return scriptsource['filename_']['mTuple']['mFirstA'].string()
+
 # 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
@@ -194,28 +204,19 @@ class JitFrameDecorator(FrameDecorator):
 
     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_"):
                 [function, script] = self._decode_jitframe(this_frame)
                 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()
+                    scriptfile = jsscript_filename(script, self.cache)
+                    if scriptfile is not None:
+                        return scriptfile
         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)
@@ -237,40 +238,83 @@ class JitFrameDecorator(FrameDecorator):
             # 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 decorator representing a single interpreter frame.
+class InterpreterFrameDecorator(FrameDecorator):
+    def __init__(self, base, iframe, cache):
+        super(InterpreterFrameDecorator, self).__init__(base)
+        self.iframe = iframe
+        self.cache = cache
+
+    def function(self):
+        return "<<<<" + FrameDecorator.function(self) + ">>>>"
+
+    def filename(self):
+        scriptfile = jsscript_filename(self.iframe['script_'], self.cache)
+        if scriptfile is not None:
+            return scriptfile
+        return FrameDecorator.filename(self)
+
+    def line(self):
+        # We can't yet compute the line number.  Return None so that
+        # we don't end up sending the line number from the base frame.
+        return None
+
+    # def frame_args(self):
+    #     pass
+
+# Yield a succession of synthetic interpreter frames for the given
+# frame.  |frame| must be a FrameDecorator representing a call to the
+# Interpret function.
+def get_interpreter_frames(frame, cache):
+    try:
+        activation = frame.inferior_frame().read_var("activation")
+        iframe = activation['regs_']['fp_']
+    except gdb.error:
+        # maybe the activation wasn't initialized yet.  This can
+        # happen if we stop in its block but before its declaration.
+        yield frame
+        return
+    while long(iframe) != 0:
+        x = InterpreterFrameDecorator(frame, iframe, cache)
+        yield x
+        iframe = iframe['prev_']
+
 # 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)
+        for frame in frame_iter:
+            if self.state_holder is not None and self.state_holder.unwinder_state is not None:
+                base = frame.inferior_frame()
+                info = self.state_holder.unwinder_state.get_frame(base)
+                if info is not None:
+                    yield JitFrameDecorator(frame, info, self.cache)
+                    continue
+            if frame.function() == "Interpret(JSContext*, js::RunState&)":
+                for iframe in get_interpreter_frames(frame, self.cache):
+                    yield iframe
+            else:
+                yield frame
 
 # 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