summaryrefslogtreecommitdiffstats
path: root/js/src/gdb
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/gdb
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/gdb')
-rw-r--r--js/src/gdb/README198
-rw-r--r--js/src/gdb/TODO22
-rw-r--r--js/src/gdb/gdb-tests-gdb.py.in11
-rw-r--r--js/src/gdb/gdb-tests.cpp95
-rw-r--r--js/src/gdb/gdb-tests.h87
-rw-r--r--js/src/gdb/lib-for-tests/catcher.py35
-rw-r--r--js/src/gdb/lib-for-tests/prologue.py129
-rw-r--r--js/src/gdb/moz.build66
-rw-r--r--js/src/gdb/mozilla/CellHeader.py25
-rw-r--r--js/src/gdb/mozilla/ExecutableAllocator.py97
-rw-r--r--js/src/gdb/mozilla/GCCellPtr.py125
-rw-r--r--js/src/gdb/mozilla/Interpreter.py94
-rw-r--r--js/src/gdb/mozilla/IonGraph.py283
-rw-r--r--js/src/gdb/mozilla/JSObject.py119
-rw-r--r--js/src/gdb/mozilla/JSString.py105
-rw-r--r--js/src/gdb/mozilla/JSSymbol.py40
-rw-r--r--js/src/gdb/mozilla/PropertyKey.py62
-rw-r--r--js/src/gdb/mozilla/Root.py104
-rw-r--r--js/src/gdb/mozilla/__init__.py5
-rw-r--r--js/src/gdb/mozilla/asmjs.py51
-rw-r--r--js/src/gdb/mozilla/autoload.py41
-rw-r--r--js/src/gdb/mozilla/jitsrc.py161
-rw-r--r--js/src/gdb/mozilla/jsop.py60
-rw-r--r--js/src/gdb/mozilla/jsval.py188
-rw-r--r--js/src/gdb/mozilla/prettyprinters.py447
-rw-r--r--js/src/gdb/mozilla/unwind.py596
-rw-r--r--js/src/gdb/progressbar.py54
-rw-r--r--js/src/gdb/run-tests.py469
-rw-r--r--js/src/gdb/taskpool.py240
-rw-r--r--js/src/gdb/tests/enum-printers.cpp25
-rw-r--r--js/src/gdb/tests/enum-printers.py47
-rw-r--r--js/src/gdb/tests/test-ExecutableAllocator.cpp49
-rw-r--r--js/src/gdb/tests/test-ExecutableAllocator.py22
-rw-r--r--js/src/gdb/tests/test-GCCellPtr.cpp62
-rw-r--r--js/src/gdb/tests/test-GCCellPtr.py17
-rw-r--r--js/src/gdb/tests/test-Interpreter.cpp88
-rw-r--r--js/src/gdb/tests/test-Interpreter.py23
-rw-r--r--js/src/gdb/tests/test-JSObject-null.py8
-rw-r--r--js/src/gdb/tests/test-JSObject.cpp54
-rw-r--r--js/src/gdb/tests/test-JSObject.py24
-rw-r--r--js/src/gdb/tests/test-JSString-null.py8
-rw-r--r--js/src/gdb/tests/test-JSString-subclasses.py7
-rw-r--r--js/src/gdb/tests/test-JSString.cpp65
-rw-r--r--js/src/gdb/tests/test-JSString.py24
-rw-r--r--js/src/gdb/tests/test-JSSymbol.cpp22
-rw-r--r--js/src/gdb/tests/test-JSSymbol.py11
-rw-r--r--js/src/gdb/tests/test-Root-null.py21
-rw-r--r--js/src/gdb/tests/test-Root.cpp67
-rw-r--r--js/src/gdb/tests/test-Root.py30
-rw-r--r--js/src/gdb/tests/test-asmjs.cpp44
-rw-r--r--js/src/gdb/tests/test-asmjs.py16
-rw-r--r--js/src/gdb/tests/test-jsbytecode.cpp32
-rw-r--r--js/src/gdb/tests/test-jsbytecode.py9
-rw-r--r--js/src/gdb/tests/test-jsid.cpp44
-rw-r--r--js/src/gdb/tests/test-jsid.py19
-rw-r--r--js/src/gdb/tests/test-jsop.cpp13
-rw-r--r--js/src/gdb/tests/test-jsop.py9
-rw-r--r--js/src/gdb/tests/test-jsval.cpp49
-rw-r--r--js/src/gdb/tests/test-jsval.py23
-rw-r--r--js/src/gdb/tests/test-prettyprinters.cpp38
-rw-r--r--js/src/gdb/tests/test-prettyprinters.py40
-rw-r--r--js/src/gdb/tests/test-unwind.cpp76
-rw-r--r--js/src/gdb/tests/test-unwind.py61
-rw-r--r--js/src/gdb/tests/typedef-printers.cpp11
-rw-r--r--js/src/gdb/tests/typedef-printers.py18
65 files changed, 5185 insertions, 0 deletions
diff --git a/js/src/gdb/README b/js/src/gdb/README
new file mode 100644
index 0000000000..9c98e192b5
--- /dev/null
+++ b/js/src/gdb/README
@@ -0,0 +1,198 @@
+This directory holds Python code to support debugging SpiderMonkey with
+GDB. It includes pretty-printers for common SpiderMonkey types like JS::Value,
+jsid, and JSObject, and makes GDB "see through" the SpiderMonkey rooting
+types like js::Rooted and JS::Handle. For example:
+
+ (gdb) frame
+ #0 js::baseops::SetPropertyHelper (cx=0xbf3460,
+ obj=(JSObject * const) 0x7ffff150b060 [object global] delegate,
+ receiver=(JSObject * const) 0x7ffff150b060 [object global] delegate,
+ id=$jsid("x"), defineHow=4, vp=$JS::Int32Value(1), strict=0)
+ at /home/jimb/moz/archer/js/src/jsobj.cpp:4495
+ 4495 MOZ_ASSERT((defineHow & ~(DNP_CACHE_RESULT | DNP_UNQUALIFIED)) == 0);
+ (gdb)
+
+Things to note here:
+
+- obj, a JS::HandleObject, prints as:
+ obj=(JSObject * const) 0x7ffff150b060 [object global] delegate,
+ This immediately shows the handle's referent, along with a JavaScript-like summary
+ of the object.
+
+- id, a JS::HandleId, prints as:
+ id=$jsid("x"),
+ We show the handle's referent, and print the identifier as a string.
+
+- vp, a JS::MutableHandleValue, prints as:
+ vp=$JS::Int32Value(1)
+ We show the handle's referent, using the JS::Value's tag to print it noting
+ its particular internal type and value.
+
+You can still see the raw form of a value with 'print/r':
+
+ (gdb) p/r obj
+ $1 = {<js::HandleBase<JSObject*>> = {<No data fields>}, ptr = 0x7fffffffca60}
+ (gdb)
+
+You can also use GDB's 'disable pretty-printer' command to turn off
+individual pretty-printers; try 'info pretty-printer' first.
+
+GDB should pick these extensions up automatically when you debug the shell or
+the browser, by auto-loading the 'js-gdb.py' file that the build system
+installs alongside the 'js' executable (or 'libxul.so-gdb.py' for the browser).
+You may need to add a command like the following to your '$HOME/.gdbinit' file:
+
+ # Tell GDB to trust auto-load files found under ~/moz.
+ add-auto-load-safe-path ~/moz
+
+If you do need this, GDB will tell you.
+
+In general, pretty-printers for pointer types include a summary of the
+pointer's referent:
+
+ (gdb) b math_atan2
+ Breakpoint 1 at 0x542e0a: file /home/jimb/moz/archer/js/src/jsmath.cpp, line 214.
+ (gdb) run
+ js> Math.atan2('Spleen', 42)
+ Breakpoint 1, math_atan2 (cx=0xbf3440, argc=2, vp=0x7ffff172f0a0)
+ (gdb) print vp[0]
+ $1 = $JS::Value((JSObject *) 0x7ffff151c0c0 [object Function "atan2"])
+ (gdb) print vp[1]
+ $2 = $JS::Value((JSObject *) 0x7ffff150d0a0 [object Math])
+ (gdb) print vp[2]
+ $3 = $JS::Value("Spleen")
+ (gdb) print vp[3]
+ $4 = $JS::Int32Value(42)
+ (gdb)
+
+We used to also have pretty-printers for the actual contents of a JSString
+struct, that knew which union branches were live and which were dead. These were
+more fragile than the summary pretty-printers, and harder to test, so I've
+removed them until we can see how to do better.
+
+There are unit tests; see 'Running the unit tests', below.
+
+I'd love for others to pitch in. GDB's Python API is documented in the GDB
+manual.
+
+I've recently rewritten the printers. The new code is simpler, and more
+robust; unit tests are easier to write; and the new test harness can run
+the tests in parallel. If a printer you'd contributed to in the past was
+dropped in the process, I apologize; I felt we should have good test
+coverage for any printer landed in-tree. You may also be interested in
+'Personal pretty-printers', below.
+
+Directory layout
+----------------
+
+- js/src/gdb/mozilla: The actual SpiderMonkey support code. GDB auto-loads this
+ when you debug an executable or shared library that contains SpiderMonkey.
+- js/src/gdb/tests: Unit tests for the above.
+ - Each '.py' file is a unit test, to be run by js/src/gdb/run-tests.py.
+ - Each '.cpp' file contains C++ code fragments for some unit test to use.
+- js/src/gdb/lib-for-tests: Python modules used by the unit tests.
+
+In js/src/gdb:
+
+- run-tests.py: test harness for GDB SpiderMonkey support unit tests. See
+ 'Running the unit tests', below.
+- taskpool.py, progressbar.py: Python modules used by run-tests.py.
+- gdb-tests.cpp, gdb-tests.h: Driver program for C++ code fragments.
+- gdb-tests-gdb.py.in: Template for GDB autoload file for gdb-tests.
+
+Personal pretty-printers
+------------------------
+
+If you'd like to write your own pretty-printers, you can put them in a
+module named 'my_mozilla_printers' in a directory somewhere on your Python
+module search path. Our autoload code tries to import 'my_mozilla_printers'
+after importing our other SpiderMonkey support modules. For example:
+
+ $ echo $PYTHONPATH
+ /home/jimb/python
+ $ cat ~/python/my_mozilla_printers.py
+ import gdb
+ from mozilla.prettyprinters import ptr_pretty_printer
+
+ # Simple char16_t * printer. Doesn't show address; chases null pointers.
+ @ptr_pretty_printer('char16_t')
+ class char16Ptr(object):
+ def __init__(self, value, cache): self.value = value
+ def display_hint(self): return 'string'
+ def to_string(self):
+ c = u''
+ for i in range(50):
+ if self.value[i] == 0: break
+ c += unichr(self.value[i])
+ return c
+ $
+ ...
+ (gdb) whatis sample
+ type = char16_t [4]
+ (gdb) print &sample[0]
+ $1 = "Hi!"
+
+Running the unit tests
+----------------------
+
+These extensions have unit tests, invoked as follows:
+
+$ python run-tests.py [OPTIONS] OBJDIR [TESTS...]
+
+where OBJDIR is a directory containing a standalone SpiderMonkey build; TESTS
+are names of selected tests to run (if omitted, we run them all); and OPTIONS
+are drawn from the list below.
+
+--gdb=EXECUTABLE
+ Instead of running whatever 'gdb' we find in our search path, use
+ EXECUTABLE to run the tests.
+
+--srcdir=SRCDIR
+ Find the sources corresponding to OBJDIR/dist/bin/libmozjs.so in SRCDIR.
+ Without this option, we use the parent of the directory containing
+ 'run-tests.py'. Note that SRCDIR must be a complete SpiderMonkey source
+ directory, as our tests #include internal SpiderMonkey header files (to
+ test pretty-printers for internal types, like parse nodes.)
+
+--testdir=TESTDIR
+ Search for Python scripts and any accompanying C++ source code in
+ TESTDIR. If omitted, we use the 'tests' directory in the directory
+ containing 'run-tests.py'.
+
+--builddir=BUILDDIR
+ Build the C++ executable that GDB debugs to run the tests in BUILDDIR.
+ If omitted, create a 'gdb-tests' subdirectory of OBJDIR/js/src.
+
+(It is safe to use relative paths for OBJDIR, SRCDIR, and so on. They are
+always interpreted relative to the directory that was current when
+run-tests.py was started.)
+
+For example, since I build in a subdirectory 'obj~' of the 'js/src'
+directory, I use this command from 'js/src' to run the pretty-printer unit
+tests:
+
+ $ python gdb/run-tests.py obj~
+
+Writing new unit tests
+----------------------
+
+Each unit test consists of a Python script, possibly with some accompanying
+C++ code. Running tests works like this:
+
+- The run-tests.py script calls 'make' in 'BUILDDIR/gdb' to build
+ 'gdb-tests'.
+
+- Then, for each '.py' test script in js/src/gdb/tests, the harness starts
+ GDB on the 'gdb-tests' executable, and then has GDB run
+ js/src/gdb/lib-for-tests/prologue.py, passing it the test script's path as
+ its first command-line argument.
+
+Thanks To:
+----------
+
+- David Anderson
+- Steve Fink
+- Chris Leary
+- Josh Matthews
+- Jason Orendorff
+- Andrew Sutherland
diff --git a/js/src/gdb/TODO b/js/src/gdb/TODO
new file mode 100644
index 0000000000..07186aa108
--- /dev/null
+++ b/js/src/gdb/TODO
@@ -0,0 +1,22 @@
+* Ideas:
+- char16_t *
+- js::Shape, js::Baseshape
+- printers for structures with horrible unions (JSString, JSParseNode)
+- bring back parse_node.py
+- New 'js show' command for showing full trees, property lists, hash table
+ contents, and so on --- JSParseNode * should not show the whole tree.
+ Possibly clean up some "pointer-only" stuff in parse_node.py.
+ - 'js show <defn>' lists a JSDefinition's uses
+ - 'js show <parsenode>' shows entire tree
+ - 'js show <scope>' lists all properties (parents)
+ - 'js tree <scope>' shows property tree
+- avoid dead union branches in js::Shape; print attrs nicely
+- Print JSScope with identifier.
+- Print JSAtomSets, and thus PN_NAMESET.
+- JSParseNode PN_NAMESET
+- 'JSClass *' pretty-printer
+
+
+Local variables:
+mode: org
+End:
diff --git a/js/src/gdb/gdb-tests-gdb.py.in b/js/src/gdb/gdb-tests-gdb.py.in
new file mode 100644
index 0000000000..c6e4009816
--- /dev/null
+++ b/js/src/gdb/gdb-tests-gdb.py.in
@@ -0,0 +1,11 @@
+"""GDB Python customization auto-loader for GDB test executable"""
+#filter substitution
+
+import os.path
+sys.path[0:0] = [os.path.join('@topsrcdir@', 'gdb')]
+
+import mozilla.autoload
+mozilla.autoload.register(gdb.current_objfile())
+
+import mozilla.asmjs
+mozilla.asmjs.install()
diff --git a/js/src/gdb/gdb-tests.cpp b/js/src/gdb/gdb-tests.cpp
new file mode 100644
index 0000000000..6000c90f79
--- /dev/null
+++ b/js/src/gdb/gdb-tests.cpp
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gdb-tests.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/Context.h"
+#include "js/GlobalObject.h"
+#include "js/Initialization.h"
+#include "js/RealmOptions.h"
+#include "js/Warnings.h" // JS::SetWarningReporter
+
+using namespace JS;
+
+/* The class of the global object. */
+static const JSClass global_class = {"global", JSCLASS_GLOBAL_FLAGS,
+ &DefaultGlobalClassOps};
+
+static volatile int dontOptimizeMeAway = 0;
+
+void usePointer(const void* ptr) { dontOptimizeMeAway = 1; }
+
+template <typename T>
+static inline T* checkPtr(T* ptr) {
+ if (!ptr) {
+ abort();
+ }
+ return ptr;
+}
+
+static void checkBool(bool success) {
+ if (!success) {
+ abort();
+ }
+}
+
+/* The warning reporter callback. */
+void reportWarning(JSContext* cx, JSErrorReport* report) {
+ fprintf(stderr, "%s:%u: %s\n",
+ report->filename ? report->filename : "<no filename>",
+ (unsigned int)report->lineno, report->message().c_str());
+}
+
+// prologue.py sets a breakpoint on this function; test functions can call it
+// to easily return control to GDB where desired.
+void breakpoint() {
+ // If we leave this function empty, the linker will unify it with other
+ // empty functions throughout SpiderMonkey. If we then set a GDB
+ // breakpoint on it, that breakpoint will hit at all sorts of random
+ // times. So make it perform a distinctive side effect.
+ fprintf(stderr, "Called " __FILE__ ":breakpoint\n");
+}
+
+GDBFragment* GDBFragment::allFragments = nullptr;
+
+int main(int argc, const char** argv) {
+ if (!JS_Init()) return 1;
+ JSContext* cx = checkPtr(JS_NewContext(1024 * 1024));
+
+ JS_SetGCParameter(cx, JSGC_MAX_BYTES, 0xffffffff);
+
+ checkBool(JS::InitSelfHostedCode(cx));
+ JS::SetWarningReporter(cx, reportWarning);
+
+ /* Create the global object. */
+ JS::RealmOptions options;
+ RootedObject global(
+ cx, checkPtr(JS_NewGlobalObject(cx, &global_class, nullptr,
+ JS::FireOnNewGlobalHook, options)));
+ JSAutoRealm ar(cx, global);
+
+ argv++;
+ while (*argv) {
+ const char* name = *argv++;
+ GDBFragment* fragment;
+ for (fragment = GDBFragment::allFragments; fragment;
+ fragment = fragment->next) {
+ if (strcmp(fragment->name(), name) == 0) {
+ fragment->run(cx, argv);
+ break;
+ }
+ }
+ if (!fragment) {
+ fprintf(stderr, "Unrecognized fragment name: %s\n", name);
+ exit(1);
+ }
+ }
+
+ return 0;
+}
diff --git a/js/src/gdb/gdb-tests.h b/js/src/gdb/gdb-tests.h
new file mode 100644
index 0000000000..ecaa3d7969
--- /dev/null
+++ b/js/src/gdb/gdb-tests.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef gdb_gdb_tests_h
+#define gdb_gdb_tests_h
+
+// Support for C++ fragments to be used by Python unit tests for SpiderMonkey's
+// GDB support.
+//
+// That is:
+// - js/src/gdb/mozilla holds the actual GDB SpiderMonkey support code.
+// - Each '.py' file in js/src/gdb/tests is a unit test for the above.
+// - Each '.cpp' file in js/src/gdb/tests is C++ code for one of the unit tests
+// to run.
+//
+// (So the .cpp files are two steps removed from being anything one would
+// actually run.)
+
+#include "NamespaceImports.h"
+
+#include "js/GCAnnotations.h"
+
+void breakpoint();
+
+extern void usePointer(const void* ptr);
+
+template <typename T>
+void use(const T& thing) {
+ usePointer(&thing);
+}
+
+struct GDBFragment {
+ GDBFragment() {
+ next = allFragments;
+ allFragments = this;
+ }
+
+ // The name of this fragment. gdb-tests.cpp runs the fragments whose names
+ // are passed to it on the command line.
+ virtual const char* name() = 0;
+
+ // Run the fragment code. |argv| is a reference to the pointer into the
+ // command-line argument vector, referring to the argument immediately
+ // following this fragment's name. The fragment can consume arguments and
+ // advance argv if it wishes.
+ virtual void run(JSContext* cx, const char**& argv) = 0;
+
+ // We declare one instance of this type for each fragment to run. The
+ // constructor adds each instance to a linked list, of which this is
+ // the head.
+ static GDBFragment* allFragments;
+
+ // The link in the list of all instances.
+ GDBFragment* next;
+};
+
+// Macro for declaring a C++ fragment for some Python unit test to call. Usage:
+//
+// FRAGMENT(<category>, <name>) { <body of fragment function> }
+//
+// where <category> and <name> are identifiers. The gdb-tests executable
+// takes a series of fragment names as command-line arguments and runs them in
+// turn; each fragment is named <category>.<name> on the command line.
+//
+// The body runs in a scope where 'cx' is a usable JSContext*.
+
+#define FRAGMENT(category, subname) \
+ class FRAGMENT_CLASS_NAME(category, subname) : public GDBFragment { \
+ void run(JSContext* cx, const char**& argv) override; \
+ const char* name() override { \
+ return FRAGMENT_STRING_NAME(category, subname); \
+ } \
+ static FRAGMENT_CLASS_NAME(category, subname) singleton; \
+ }; \
+ FRAGMENT_CLASS_NAME(category, subname) \
+ FRAGMENT_CLASS_NAME(category, subname)::singleton; \
+ void FRAGMENT_CLASS_NAME(category, subname)::run(JSContext* cx, \
+ const char**& argv)
+
+#define FRAGMENT_STRING_NAME(category, subname) (#category "." #subname)
+#define FRAGMENT_CLASS_NAME(category, subname) Fragment_##category##_##subname
+
+#endif /* gdb_gdb_tests_h */
diff --git a/js/src/gdb/lib-for-tests/catcher.py b/js/src/gdb/lib-for-tests/catcher.py
new file mode 100644
index 0000000000..8ef3529255
--- /dev/null
+++ b/js/src/gdb/lib-for-tests/catcher.py
@@ -0,0 +1,35 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Apparently, there's simply no way to ask GDB to exit with a non-zero
+# status when the script run with the --eval-command option fails. Thus, if
+# we have --eval-command run prologue.py directly, syntax errors there will
+# lead GDB to exit with no indication anything went wrong.
+#
+# To avert that, we use this very small launcher script to run prologue.py
+# and catch errors.
+#
+# Remember, errors in this file will cause spurious passes, so keep this as
+# simple as possible!
+# flake8: noqa: F821
+
+import os
+import sys
+import traceback
+
+
+def execfile(filename, globs, locs):
+ with open(filename) as f:
+ code = compile(f.read(), filename, "exec")
+ exec(code, globs, locs)
+
+
+try:
+ # testlibdir is set on the GDB command line, via:
+ # --eval-command python testlibdir=...
+ execfile(os.path.join(testlibdir, "prologue.py"), globals(), locals())
+except Exception as err:
+ sys.stderr.write("Error running GDB prologue:\n")
+ traceback.print_exc()
+ sys.exit(1)
diff --git a/js/src/gdb/lib-for-tests/prologue.py b/js/src/gdb/lib-for-tests/prologue.py
new file mode 100644
index 0000000000..e94ce480d6
--- /dev/null
+++ b/js/src/gdb/lib-for-tests/prologue.py
@@ -0,0 +1,129 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# flake8: noqa: F821
+
+import re
+import sys
+import traceback
+
+import gdb
+
+# testlibdir is set on the GDB command line, via --eval-command python testlibdir=...
+sys.path[0:0] = [testlibdir]
+
+active_fragment = None
+
+# Run the C++ fragment named |fragment|, stopping on entry to |function|
+# ('breakpoint', by default) and then select the calling frame.
+
+
+def run_fragment(fragment, function="gdb-tests.cpp:breakpoint"):
+ # Arrange to stop at a reasonable place in the test program.
+ bp = gdb.Breakpoint(function)
+ try:
+ gdb.execute("run %s" % (fragment,))
+ # Check that we did indeed stop by hitting the breakpoint we set.
+ assert bp.hit_count == 1
+ finally:
+ bp.delete()
+ gdb.execute("frame 1")
+
+ global active_fragment
+ active_fragment = fragment
+
+
+# Assert that |actual| is equal to |expected|; if not, complain in a helpful way.
+
+
+def assert_eq(actual, expected):
+ if actual != expected:
+ raise AssertionError(
+ """Unexpected result:
+expected: %r
+actual: %r"""
+ % (expected, actual)
+ )
+
+
+# Assert that |expected| regex matches |actual| result; if not, complain in a helpful way.
+
+
+def assert_match(actual, expected):
+ if re.match(expected, actual, re.MULTILINE) is None:
+ raise AssertionError(
+ """Unexpected result:
+expected pattern: %r
+actual: %r"""
+ % (expected, actual)
+ )
+
+
+# Assert that |value|'s pretty-printed form is |form|. If |value| is a
+# string, then evaluate it with gdb.parse_and_eval to produce a value.
+
+
+def assert_pretty(value, form):
+ if isinstance(value, str):
+ value = gdb.parse_and_eval(value)
+ assert_eq(str(value), form)
+
+
+# Assert that |value|'s pretty-printed form match the pattern |pattern|. If
+# |value| is a string, then evaluate it with gdb.parse_and_eval to produce a
+# value.
+
+
+def assert_regexp_pretty(value, form):
+ if isinstance(value, str):
+ value = gdb.parse_and_eval(value)
+ assert_match(str(value), form)
+
+
+# Check that the list of registered pretty-printers includes one named
+# |printer|, with a subprinter named |subprinter|.
+
+
+def assert_subprinter_registered(printer, subprinter):
+ # Match a line containing |printer| followed by a colon, and then a
+ # series of more-indented lines containing |subprinter|.
+
+ names = {"printer": re.escape(printer), "subprinter": re.escape(subprinter)}
+ pat = r"^( +)%(printer)s *\n(\1 +.*\n)*\1 +%(subprinter)s *\n" % names
+ output = gdb.execute("info pretty-printer", to_string=True)
+ if not re.search(pat, output, re.MULTILINE):
+ raise AssertionError(
+ "assert_subprinter_registered failed to find pretty-printer:\n"
+ " %s:%s\n"
+ "'info pretty-printer' says:\n"
+ "%s" % (printer, subprinter, output)
+ )
+
+
+# Request full stack traces for Python errors.
+gdb.execute("set python print-stack full")
+
+# Tell GDB not to ask the user about the things we tell it to do.
+gdb.execute("set confirm off", False)
+
+# Some print settings that make testing easier.
+gdb.execute("set print static-members off")
+gdb.execute("set print address off")
+gdb.execute("set print pretty off")
+gdb.execute("set width 0")
+
+try:
+ # testscript is set on the GDB command line, via:
+ # --eval-command python testscript=...
+ execfile(testscript, globals(), locals())
+except AssertionError as err:
+ header = "\nAssertion traceback"
+ if active_fragment:
+ header += " for " + active_fragment
+ sys.stderr.write(header + ":\n")
+ (t, v, tb) = sys.exc_info()
+ traceback.print_tb(tb)
+ sys.stderr.write("\nTest assertion failed:\n")
+ sys.stderr.write(str(err))
+ sys.exit(1)
diff --git a/js/src/gdb/moz.build b/js/src/gdb/moz.build
new file mode 100644
index 0000000000..550dc93bec
--- /dev/null
+++ b/js/src/gdb/moz.build
@@ -0,0 +1,66 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+GeckoProgram("gdb-tests", linkage=None)
+
+include("../js-cxxflags.mozbuild")
+include("../js-standalone.mozbuild")
+
+SOURCES += [
+ # This file must not be unified with any of the test files, or the use()
+ # and breakpoint() functions might get optimized out.
+ "gdb-tests.cpp"
+]
+
+UNIFIED_SOURCES += [
+ "tests/enum-printers.cpp",
+ "tests/test-asmjs.cpp",
+ "tests/test-ExecutableAllocator.cpp",
+ "tests/test-GCCellPtr.cpp",
+ "tests/test-Interpreter.cpp",
+ "tests/test-jsbytecode.cpp",
+ "tests/test-jsid.cpp",
+ "tests/test-JSObject.cpp",
+ "tests/test-jsop.cpp",
+ "tests/test-JSString.cpp",
+ "tests/test-JSSymbol.cpp",
+ "tests/test-jsval.cpp",
+ "tests/test-Root.cpp",
+ "tests/test-unwind.cpp",
+ "tests/typedef-printers.cpp",
+]
+
+SOURCES += ["tests/test-prettyprinters.cpp"]
+
+if CONFIG["CC_TYPE"] != "clang-cl":
+ # Test expects to see pre-typedef names of base classes, but the compiler will
+ # normally omit those from the debuginfo. The current clang-cl does not support
+ # this option.
+ SOURCES["tests/test-prettyprinters.cpp"].flags += [
+ "-fno-eliminate-unused-debug-types",
+ ]
+
+if CONFIG["CC_TYPE"] == "clang":
+ # Clang may complain that -fno-eliminate-unused-debug-types is unused when used
+ # in conjunction with other flags such as -gline-tables-only.
+ SOURCES["tests/test-prettyprinters.cpp"].flags += [
+ "-Qunused-arguments",
+ ]
+
+DEFINES["EXPORT_JS_API"] = True
+
+LOCAL_INCLUDES += [
+ "!..",
+ "..",
+]
+
+USE_LIBS += [
+ "static:js",
+]
+
+DEFINES["topsrcdir"] = "%s/js/src" % TOPSRCDIR
+FINAL_TARGET_PP_FILES += ["gdb-tests-gdb.py.in"]
+OBJDIR_FILES.js.src.gdb += ["!/dist/bin/gdb-tests-gdb.py"]
diff --git a/js/src/gdb/mozilla/CellHeader.py b/js/src/gdb/mozilla/CellHeader.py
new file mode 100644
index 0000000000..cb667b3ad0
--- /dev/null
+++ b/js/src/gdb/mozilla/CellHeader.py
@@ -0,0 +1,25 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import gdb
+
+
+def get_header_ptr(value, ptr_t):
+ # Return the pointer stored in Cell::header_ for subclasses of
+ # TenuredCellWithNonGCPointer and CellWithTenuredGCPointer.
+ return value["header_"]["value_"].cast(ptr_t)
+
+
+def get_header_length_and_flags(value, cache):
+ # Return the length and flags values for subclasses of
+ # CellWithLengthAndFlags.
+ flags = value["header_"]["value_"].cast(cache.uintptr_t)
+ try:
+ length = value["length_"]
+ except gdb.error:
+ # If we couldn't fetch the length directly, it must be stored
+ # within `flags`.
+ length = flags >> 32
+ flags = flags % 2 ** 32
+ return length, flags
diff --git a/js/src/gdb/mozilla/ExecutableAllocator.py b/js/src/gdb/mozilla/ExecutableAllocator.py
new file mode 100644
index 0000000000..5187e2d1f1
--- /dev/null
+++ b/js/src/gdb/mozilla/ExecutableAllocator.py
@@ -0,0 +1,97 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+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__)
+
+
+class jsjitExecutableAllocatorCache(object):
+ """Cache information about the ExecutableAllocator type for this objfile."""
+
+ 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")
+ self.d["HashNumber"] = gdb.lookup_type("mozilla::HashNumber")
+
+
+@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()
+ self.hashNumType = allocator.cache.HashNumber
+ # Emulate the HashSet::Range
+ self.table = allocator.value["m_pools"]["mImpl"]["mTable"]
+ self.index = 0
+ kHashNumberBits = 32
+ hashShift = allocator.value["m_pools"]["mImpl"]["mHashShift"]
+ self.capacity = 1 << (kHashNumberBits - hashShift)
+ if self.table == 0:
+ self.capacity = 0
+ # auto hashes = reinterpret_cast<HashNumber*>(mTable);
+ self.hashes = self.table.cast(self.hashNumType.pointer())
+ # auto entries = reinterpret_cast<Entry*>(&hashes[capacity()]);
+ self.entries = (self.hashes + self.capacity).cast(self.entryType.pointer())
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ return self.__next__()
+
+ def __next__(self):
+ cur = self.index
+ if cur >= self.capacity:
+ raise StopIteration()
+ self.index = self.index + 1
+ if self.hashes[cur] > 1: # table[i]->isLive()
+ return self.entries[cur]
+ 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 0000000000..e42ecfa893
--- /dev/null
+++ b/js/src/gdb/mozilla/GCCellPtr.py
@@ -0,0 +1,125 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Pretty-printers for 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 types for this objfile.
+
+
+class GCCellPtrTypeCache(object):
+ def __init__(self, cache):
+ self.TraceKind_t = gdb.lookup_type("JS::TraceKind")
+ self.AllocKind_t = gdb.lookup_type("js::gc::AllocKind")
+ self.Arena_t = gdb.lookup_type("js::gc::Arena")
+ self.Cell_t = gdb.lookup_type("js::gc::Cell")
+ self.TenuredCell_t = gdb.lookup_type("js::gc::TenuredCell")
+
+ trace_kinds = gdb.types.make_enum_dict(self.TraceKind_t)
+ alloc_kinds = gdb.types.make_enum_dict(self.AllocKind_t)
+
+ def trace_kind(k):
+ return trace_kinds["JS::TraceKind::" + k]
+
+ def alloc_kind(k):
+ return alloc_kinds["js::gc::AllocKind::" + k]
+
+ # Build a mapping from TraceKind enum values to the types they denote.
+ trace_map = {
+ # Inline types.
+ "Object": "JSObject",
+ "BigInt": "JS::BigInt",
+ "String": "JSString",
+ "Symbol": "JS::Symbol",
+ "Shape": "js::Shape",
+ "BaseShape": "js::BaseShape",
+ "Null": "std::nullptr_t",
+ # Out-of-line types.
+ "JitCode": "js::jit::JitCode",
+ "Script": "js::BaseScript",
+ "Scope": "js::Scope",
+ "RegExpShared": "js::RegExpShared",
+ "GetterSetter": "js::GetterSetter",
+ "PropMap": "js::PropMap",
+ }
+
+ # Map from AllocKind to TraceKind for out-of-line types.
+ alloc_map = {
+ "JITCODE": "JitCode",
+ "SCRIPT": "Script",
+ "SCOPE": "Scope",
+ "REGEXP_SHARED": "RegExpShared",
+ "GETTER_SETTER": "GetterSetter",
+ "COMPACT_PROP_MAP": "PropMap",
+ "NORMAL_PROP_MAP": "PropMap",
+ "DICT_PROP_MAP": "PropMap",
+ }
+
+ self.trace_kind_to_type = {
+ trace_kind(k): gdb.lookup_type(v) for k, v in trace_map.items()
+ }
+ self.alloc_kind_to_trace_kind = {
+ alloc_kind(k): trace_kind(v) for k, v in alloc_map.items()
+ }
+
+ self.Null = trace_kind("Null")
+ self.tracekind_mask = gdb.parse_and_eval("JS::OutOfLineTraceKindMask")
+ self.arena_mask = gdb.parse_and_eval("js::gc::ArenaMask")
+
+
+@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.tracekind_mask
+ if kind == self.cache.mod_GCCellPtr.Null:
+ return "JS::GCCellPtr(nullptr)"
+ if kind == self.cache.mod_GCCellPtr.tracekind_mask:
+ # Out-of-line trace kinds.
+ #
+ # Compute the underlying type for out-of-line kinds by
+ # reimplementing the GCCellPtr::outOfLineKind() method.
+ #
+ # The extra casts below are only present to make it easier to
+ # compare this code against the C++ implementation.
+
+ # GCCellPtr::asCell()
+ cell_ptr = ptr & ~self.cache.mod_GCCellPtr.tracekind_mask
+ cell = cell_ptr.reinterpret_cast(self.cache.mod_GCCellPtr.Cell_t.pointer())
+
+ # Cell::asTenured()
+ tenured = cell.cast(self.cache.mod_GCCellPtr.TenuredCell_t.pointer())
+
+ # TenuredCell::arena()
+ addr = int(tenured)
+ arena_ptr = addr & ~self.cache.mod_GCCellPtr.arena_mask
+ arena = arena_ptr.reinterpret_cast(
+ self.cache.mod_GCCellPtr.Arena_t.pointer()
+ )
+
+ # Arena::getAllocKind()
+ alloc_kind = arena["allocKind"].cast(self.cache.mod_GCCellPtr.AllocKind_t)
+ alloc_idx = int(
+ alloc_kind.cast(self.cache.mod_GCCellPtr.AllocKind_t.target())
+ )
+
+ # Map the AllocKind to a TraceKind.
+ kind = self.cache.mod_GCCellPtr.alloc_kind_to_trace_kind[alloc_idx]
+ type_name = self.cache.mod_GCCellPtr.trace_kind_to_type[int(kind)]
+ return "JS::GCCellPtr(({}*) {})".format(
+ type_name, 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 0000000000..cc7cd73726
--- /dev/null
+++ b/js/src/gdb/mozilla/Interpreter.py
@@ -0,0 +1,94 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Pretty-printers for InterpreterRegs.
+
+import gdb
+
+import mozilla.prettyprinters as prettyprinters
+
+prettyprinters.clear_module_printers(__name__)
+
+from mozilla.prettyprinters import pretty_printer
+
+
+class InterpreterTypeCache(object):
+ # Cache information about the Interpreter types for this objfile.
+ def __init__(self):
+ self.tValue = gdb.lookup_type("JS::Value")
+ self.tJSOp = gdb.lookup_type("JSOp")
+ try:
+ self.tScriptFrameIterData = gdb.lookup_type("js::ScriptFrameIter::Data")
+ except gdb.error:
+ # Work around problem with gcc optimized debuginfo where it doesn't
+ # seem to be able to see that ScriptFrameIter inherits the
+ # FrameIter::Data type.
+ self.tScriptFrameIterData = gdb.lookup_type("js::FrameIter::Data")
+ self.tInterpreterFrame = gdb.lookup_type("js::InterpreterFrame")
+ self.tBaselineFrame = gdb.lookup_type("js::jit::BaselineFrame")
+ self.tRematerializedFrame = gdb.lookup_type("js::jit::RematerializedFrame")
+ self.tDebugFrame = gdb.lookup_type("js::wasm::DebugFrame")
+
+
+@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 = "pc = {}".format(self.value["pc"])
+ return "{{ {}, {}, {} }}".format(fp_, sp, pc)
+
+
+@pretty_printer("js::AbstractFramePtr")
+class AbstractFramePtr(object):
+ Tag_ScriptFrameIterData = 0x0
+ Tag_InterpreterFrame = 0x1
+ Tag_BaselineFrame = 0x2
+ Tag_RematerializedFrame = 0x3
+ Tag_WasmDebugFrame = 0x4
+ TagMask = 0x7
+
+ 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())
+ if tag == AbstractFramePtr.Tag_WasmDebugFrame:
+ label = "js::wasm::DebugFrame"
+ ptr = ptr.cast(self.itc.tDebugFrame.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 0000000000..639baf3ace
--- /dev/null
+++ b/js/src/gdb/mozilla/IonGraph.py
@@ -0,0 +1,283 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+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 os
+import subprocess
+import tempfile
+import time
+
+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 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" % self.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.encode("utf8"))
+ 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()
+ 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.
+ 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 0000000000..95accc93b9
--- /dev/null
+++ b/js/src/gdb/mozilla/JSObject.py
@@ -0,0 +1,119 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Pretty-printers for SpiderMonkey JSObjects.
+
+import re
+
+import gdb
+
+import mozilla.prettyprinters as prettyprinters
+from mozilla.CellHeader import get_header_ptr
+from mozilla.jsval import JSValue
+from mozilla.prettyprinters import ptr_pretty_printer, ref_pretty_printer
+
+prettyprinters.clear_module_printers(__name__)
+
+
+class JSObjectTypeCache(object):
+ def __init__(self):
+ object_flag = gdb.lookup_type("js::ObjectFlag")
+ self.objectflag_IsUsedAsPrototype = prettyprinters.enum_value(
+ object_flag, "js::ObjectFlag::IsUsedAsPrototype"
+ )
+ self.value_ptr_t = gdb.lookup_type("JS::Value").pointer()
+ self.func_ptr_t = gdb.lookup_type("JSFunction").pointer()
+ self.class_NON_NATIVE = gdb.parse_and_eval("JSClass::NON_NATIVE")
+ self.BaseShape_ptr_t = gdb.lookup_type("js::BaseShape").pointer()
+ self.Shape_ptr_t = gdb.lookup_type("js::Shape").pointer()
+ self.JSClass_ptr_t = gdb.lookup_type("JSClass").pointer()
+ self.JSScript_ptr_t = gdb.lookup_type("JSScript").pointer()
+ self.JSFunction_AtomSlot = gdb.parse_and_eval("JSFunction::AtomSlot")
+ self.JSFunction_NativeJitInfoOrInterpretedScriptSlot = gdb.parse_and_eval(
+ "JSFunction::NativeJitInfoOrInterpretedScriptSlot"
+ )
+
+
+# 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()
+ self.otc = cache.mod_JSObject
+
+ def summary(self):
+ shape = get_header_ptr(self.value, self.otc.Shape_ptr_t)
+ baseshape = get_header_ptr(shape, self.otc.BaseShape_ptr_t)
+ classp = get_header_ptr(baseshape, self.otc.JSClass_ptr_t)
+ 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:
+ flags = shape["objectFlags_"]["flags_"]
+ used_as_prototype = bool(flags & self.otc.objectflag_IsUsedAsPrototype)
+ 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
+ name = get_function_name(function, self.cache)
+ return "[object {}{}]{}".format(
+ class_name,
+ " " + name if name else "",
+ " used_as_prototype" if used_as_prototype else "",
+ )
+
+
+def get_function_name(function, cache):
+ if not cache.mod_JSObject:
+ cache.mod_JSObject = JSObjectTypeCache()
+ otc = cache.mod_JSObject
+
+ function = function.cast(otc.func_ptr_t)
+ fixed_slots = (function + 1).cast(otc.value_ptr_t)
+ atom_value = JSValue(fixed_slots[otc.JSFunction_AtomSlot], cache)
+
+ if atom_value.is_undefined():
+ return "<unnamed>"
+
+ return str(atom_value.get_string())
+
+
+def get_function_script(function, cache):
+ if not cache.mod_JSObject:
+ cache.mod_JSObject = JSObjectTypeCache()
+ otc = cache.mod_JSObject
+
+ function = function.cast(otc.func_ptr_t)
+ fixed_slots = (function + 1).cast(otc.value_ptr_t)
+ slot = otc.JSFunction_NativeJitInfoOrInterpretedScriptSlot
+ script_value = JSValue(fixed_slots[slot], cache)
+
+ if script_value.is_undefined():
+ return 0
+
+ return script_value.get_private()
+
+
+@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 0000000000..3c758e0352
--- /dev/null
+++ b/js/src/gdb/mozilla/JSString.py
@@ -0,0 +1,105 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Pretty-printers for SpiderMonkey strings.
+
+import gdb
+
+import mozilla.prettyprinters
+from mozilla.CellHeader import get_header_length_and_flags
+from mozilla.prettyprinters import ptr_pretty_printer
+
+try:
+ chr(10000) # UPPER RIGHT PENCIL
+except ValueError: # 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__)
+
+
+class JSStringTypeCache(object):
+ # Cache information about the JSString type for this objfile.
+ def __init__(self, cache):
+ dummy = gdb.Value(0).cast(cache.JSString_ptr_t)
+ self.ATOM_BIT = dummy["ATOM_BIT"]
+ self.LINEAR_BIT = dummy["LINEAR_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, flags = get_header_length_and_flags(self.value, self.cache)
+
+ corrupt = {
+ 0x2F2F2F2F: "JS_FRESH_NURSERY_PATTERN",
+ 0x2B2B2B2B: "JS_SWEPT_NURSERY_PATTERN",
+ 0xE5E5E5E5: "jemalloc freed memory",
+ }.get(flags & 0xFFFFFFFF)
+ if corrupt:
+ for ch in "<CORRUPT:%s>" % corrupt:
+ yield ch
+ return
+ is_rope = (flags & self.stc.LINEAR_BIT) == 0
+ 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, maxlen=200):
+ s = ""
+ invalid_chars_allowed = 2
+ for c in self.chars():
+ if len(s) >= maxlen:
+ s += "..."
+ break
+
+ try:
+ # Convert from gdb.Value to string.
+ s += chr(c)
+ except ValueError:
+ if invalid_chars_allowed == 0:
+ s += "<TOO_MANY_INVALID_CHARS>"
+ break
+ else:
+ invalid_chars_allowed -= 1
+ s += "\\x%04x" % (c & 0xFFFF)
+ 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 0000000000..db96898c91
--- /dev/null
+++ b/js/src/gdb/mozilla/JSSymbol.py
@@ -0,0 +1,40 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Pretty-printer for SpiderMonkey symbols.
+
+import mozilla.prettyprinters
+from mozilla.CellHeader import get_header_ptr
+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
+PrivateNameSymbol = 0xFFFFFFFD
+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(get_header_ptr(self.value, self.cache.JSString_ptr_t))
+ if code == InSymbolRegistry:
+ return "Symbol.for({})".format(desc)
+ elif code == UniqueSymbol:
+ return "Symbol({})".format(desc)
+ elif code == PrivateNameSymbol:
+ return "#{}".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/PropertyKey.py b/js/src/gdb/mozilla/PropertyKey.py
new file mode 100644
index 0000000000..517976a6da
--- /dev/null
+++ b/js/src/gdb/mozilla/PropertyKey.py
@@ -0,0 +1,62 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Pretty-printers for JSID values.
+
+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("JS::PropertyKey")
+class PropertyKey(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).
+ StringTypeTag = 0x0
+ IntTagBit = 0x1
+ VoidTypeTag = 0x2
+ SymbolTypeTag = 0x4
+ TypeMask = 0x7
+
+ def __init__(self, value, cache):
+ self.value = value
+ self.cache = cache
+ self.concrete_type = self.value.type.strip_typedefs()
+
+ def to_string(self):
+ bits = self.value["asBits_"]
+ tag = bits & PropertyKey.TypeMask
+ if tag == PropertyKey.StringTypeTag:
+ body = bits.cast(self.cache.JSString_ptr_t)
+ elif tag & PropertyKey.IntTagBit:
+ body = bits >> 1
+ elif tag == PropertyKey.VoidTypeTag:
+ return "JS::VoidPropertyKey"
+ elif tag == PropertyKey.SymbolTypeTag:
+ body = (bits & ~PropertyKey.TypeMask).cast(self.cache.JSSymbol_ptr_t)
+ else:
+ body = "<unrecognized>"
+ return "$jsid(%s)" % (body,)
+
+
+@pretty_printer("JS::Rooted<long>")
+def RootedPropertyKey(value, cache):
+ # Hard-code the referent type pretty-printer for PropertyKey roots and
+ # handles. See the comment for mozilla.Root.Common.__init__.
+ return mozilla.Root.Rooted(value, cache, PropertyKey)
+
+
+@pretty_printer("JS::Handle<long>")
+def HandlePropertyKey(value, cache):
+ return mozilla.Root.Handle(value, cache, PropertyKey)
+
+
+@pretty_printer("JS::MutableHandle<long>")
+def MutableHandlePropertyKey(value, cache):
+ return mozilla.Root.MutableHandle(value, cache, PropertyKey)
diff --git a/js/src/gdb/mozilla/Root.py b/js/src/gdb/mozilla/Root.py
new file mode 100644
index 0000000000..fa80f3fec6
--- /dev/null
+++ b/js/src/gdb/mozilla/Root.py
@@ -0,0 +1,104 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Pretty-printers and utilities for SpiderMonkey rooting templates:
+# Rooted, Handle, MutableHandle, etc.
+
+import mozilla.prettyprinters
+from mozilla.prettyprinters import template_pretty_printer
+
+# Forget any printers from previous loads of this module.
+mozilla.prettyprinters.clear_module_printers(__name__)
+
+
+class Common(object):
+ # 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.
+
+ # 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"
+
+
+def deref(root):
+ # Return the referent of a HeapPtr, Rooted, or Handle.
+ 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 0000000000..7084d5052e
--- /dev/null
+++ b/js/src/gdb/mozilla/__init__.py
@@ -0,0 +1,5 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# 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 0000000000..85a19a5cfc
--- /dev/null
+++ b/js/src/gdb/mozilla/asmjs.py
@@ -0,0 +1,51 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+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 WasmFaultHandler is installed as the SIGSEGV signal action.
+ sigaction_fn = gdb.parse_and_eval(
+ "(void(*)(int,void*,void*))__sigaction"
+ ).dereference()
+ sigaction_fn(SIGSEGV, 0, buf)
+ WasmTrapHandler = gdb.parse_and_eval("WasmTrapHandler")
+ if buf["__sigaction_handler"]["sa_handler"] == WasmTrapHandler:
+ # Advise the user that magic is happening.
+ print("js/src/gdb/mozilla/asmjs.py: Allowing WasmTrapHandler to run.")
+
+ # If WasmTrapHandler 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 0000000000..bc62e04555
--- /dev/null
+++ b/js/src/gdb/mozilla/autoload.py
@@ -0,0 +1,41 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# 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.ExecutableAllocator
+
+# 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.jsop
+import mozilla.JSString
+import mozilla.JSSymbol
+import mozilla.jsval
+import mozilla.prettyprinters
+import mozilla.PropertyKey
+import mozilla.Root
+import mozilla.unwind
+
+# The user may have personal pretty-printers. Get those, too, if they exist.
+try:
+ import my_mozilla_printers # NOQA: F401
+except ImportError:
+ pass
+
+
+def register(objfile):
+ # Register our pretty-printers with |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/jitsrc.py b/js/src/gdb/mozilla/jitsrc.py
new file mode 100644
index 0000000000..9a9ffff28b
--- /dev/null
+++ b/js/src/gdb/mozilla/jitsrc.py
@@ -0,0 +1,161 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This is a gdb extension to automate the process of tracing backwards in rr
+# from a jit instruction to the code that generated that instruction.
+#
+# Usage:
+# (rr) x/i $pc
+# => 0x240e954ac13a: pushq (%rbx)
+# (rr) jitsrc 0x240e954ac13a
+
+import re
+
+import gdb
+
+# (base_name, hops, func_name, source_var, dest_var) tuples, such that :
+# - `base_name`: a regex matching the name of the function that implements
+# the actual write
+# - `hops`: the number of stack frames between `base_name` and `func_name`
+# - `func_name`: a regex matching the name of the function that calls memcpy
+# - `source_var`: an expression that can be evaluated in the frame
+# corresponding to `func_name` to get the source of the memcpy
+# - `dest_var`: an expression that can be evaluated in the frame
+# corresponding to `func_name` to get the destination of the memcpy
+#
+# If an invocation of `jitsrc` stops in the middle of a memcpy, the solution
+# is normally to add a new pattern here.
+patterns = [
+ (
+ "__memmove_(avx|evex)_unaligned_erms",
+ 1,
+ "js::jit::X86Encoding::BaseAssembler::executableCopy",
+ "src",
+ "dst",
+ ),
+ (
+ "__memcpy_(avx|evex)_unaligned",
+ 1,
+ "js::jit::X86Encoding::BaseAssembler::executableCopy",
+ "src",
+ "dst",
+ ),
+ (
+ "__memmove_(avx|evex)_unaligned_erms",
+ 1,
+ "arena_t::RallocSmallOrLarge",
+ "aPtr",
+ "ret",
+ ),
+ ("__memcpy_(avx|evex)_unaligned", 1, "arena_t::RallocSmallOrLarge", "aPtr", "ret"),
+ (
+ "mozilla::detail::VectorImpl<.*>::new_<.*>",
+ 3,
+ "mozilla::Vector<.*>::convertToHeapStorage",
+ "beginNoCheck()",
+ "newBuf",
+ ),
+ (
+ "__memmove_(avx|evex)_unaligned_erms",
+ 1,
+ "js::jit::AssemblerBufferWithConstantPools",
+ "&cur->instructions[0]",
+ "dest",
+ ),
+ (
+ "__memcpy_sse2_unaligned",
+ 1,
+ "js::jit::AssemblerBufferWithConstantPools",
+ "&cur->instructions[0]",
+ "dest",
+ ),
+ (
+ "__memcpy_sse2_unaligned",
+ 2,
+ "js::jit::AssemblerX86Shared::executableCopy",
+ "masm.m_formatter.m_buffer.m_buffer.mBegin",
+ "buffer",
+ ),
+ ("__memcpy_sse2_unaligned", 1, "arena_t::RallocSmallOrLarge", "aPtr", "ret"),
+ ("js::jit::X86Encoding::SetInt32", 0, "js::jit::X86Encoding::SetInt32", "0", "0"),
+ (
+ "js::jit::X86Encoding::SetPointer",
+ 0,
+ "js::jit::X86Encoding::SetPointer",
+ "0",
+ "0",
+ ),
+ (
+ "<unnamed>",
+ 1,
+ "js::jit::AssemblerBufferWithConstantPools<.*>::executableCopy",
+ "&cur->instructions[0]",
+ "dest",
+ ),
+ ("std::__copy_move", 4, "CopySpan", "source.data()", "target.data()"),
+ (
+ "__memmove_(avx|evex)_unaligned_erms",
+ 1,
+ "mozilla::detail::EndianUtils::copyAndSwapTo<.*0,.*0",
+ "aSrc",
+ "(size_t) aDest",
+ ),
+]
+
+
+class JitSource(gdb.Command):
+ def __init__(self):
+ super(JitSource, self).__init__("jitsrc", gdb.COMMAND_RUNNING)
+ self.dont_repeat()
+
+ def disable_breakpoints(self):
+ self.disabled_breakpoints = [b for b in gdb.breakpoints() if b.enabled]
+ for b in self.disabled_breakpoints:
+ b.enabled = False
+
+ def enable_breakpoints(self):
+ for b in self.disabled_breakpoints:
+ b.enabled = True
+
+ def search_stack(self, base_name, hops, name, src, dst, address):
+ current_frame_name = gdb.newest_frame().name() or "<unnamed>"
+ if not re.match(base_name, current_frame_name):
+ return None
+ f = gdb.newest_frame()
+ for _ in range(hops):
+ f = f.older()
+ if not re.match(name, f.name()):
+ return None
+ f.select()
+ src_val = gdb.parse_and_eval(src)
+ dst_val = gdb.parse_and_eval(dst)
+ return hex(src_val + int(address, 16) - dst_val)
+
+ def next_address(self, old):
+ for pattern in patterns:
+ found = self.search_stack(*pattern, old)
+ if found:
+ return found
+ return None
+
+ def runback(self, address):
+ b = gdb.Breakpoint(
+ "*" + address, type=gdb.BP_WATCHPOINT, wp_class=gdb.WP_WRITE, internal=True
+ )
+ while b.hit_count == 0:
+ gdb.execute("rc", to_string=True)
+ b.delete()
+
+ def invoke(self, arg, from_tty):
+ args = gdb.string_to_argv(arg)
+ address = args[0]
+ self.disable_breakpoints()
+ while address:
+ self.runback(address)
+ address = self.next_address(address)
+ self.enable_breakpoints()
+
+
+# Register to the gdb runtime
+JitSource()
diff --git a/js/src/gdb/mozilla/jsop.py b/js/src/gdb/mozilla/jsop.py
new file mode 100644
index 0000000000..635acb7528
--- /dev/null
+++ b/js/src/gdb/mozilla/jsop.py
@@ -0,0 +1,60 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Pretty-printers for JSOp and jsbytecode values.
+
+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__)
+
+
+class JSOpTypeCache(object):
+ # Cache information about the JSOp type for this objfile.
+ def __init__(self, cache):
+ self.tJSOp = gdb.lookup_type("JSOp")
+
+ @classmethod
+ def get_or_create(cls, cache):
+ if not cache.mod_JSOp:
+ cache.mod_JSOp = cls(cache)
+ return cache.mod_JSOp
+
+
+@pretty_printer("JSOp")
+class JSOp(object):
+ def __init__(self, value, cache):
+ self.value = value
+ self.cache = cache
+ self.jotc = JSOpTypeCache.get_or_create(cache)
+
+ def to_string(self):
+ # JSOp's storage type is |uint8_t|, but gdb uses a signed value.
+ # Manually convert it to an unsigned value.
+ #
+ # https://sourceware.org/bugzilla/show_bug.cgi?id=25325
+ idx = int(self.value.cast(self.jotc.tJSOp.target()))
+ assert 0 <= idx and idx <= 255
+ fields = self.jotc.tJSOp.fields()
+ if idx < len(fields):
+ return fields[idx].name
+ return "(JSOp) {:d}".format(idx)
+
+
+@ptr_pretty_printer("jsbytecode")
+class JSBytecodePtr(mozilla.prettyprinters.Pointer):
+ def __init__(self, value, cache):
+ super(JSBytecodePtr, self).__init__(value, cache)
+ self.jotc = JSOpTypeCache.get_or_create(cache)
+
+ def to_string(self):
+ try:
+ opcode = str(self.value.dereference().cast(self.jotc.tJSOp))
+ except Exception:
+ opcode = "bad pc"
+ return "{} ({})".format(self.value.cast(self.cache.void_ptr_t), opcode)
diff --git a/js/src/gdb/mozilla/jsval.py b/js/src/gdb/mozilla/jsval.py
new file mode 100644
index 0000000000..d01a5ceacc
--- /dev/null
+++ b/js/src/gdb/mozilla/jsval.py
@@ -0,0 +1,188 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Pretty-printers for SpiderMonkey's JS::Value.
+
+import struct
+
+import gdb
+import gdb.types
+
+import mozilla.prettyprinters
+from mozilla.prettyprinters import pretty_printer
+
+# Forget any printers from previous loads of this module.
+mozilla.prettyprinters.clear_module_printers(__name__)
+
+# See Value.h [SMDOC] JS::Value Boxing Formats for details on the JS::Value boxing
+# formats handled below.
+
+
+class Box(object):
+ def __init__(self, asBits, jtc):
+ self.asBits = asBits
+ self.jtc = jtc
+ # Value::asBits is uint64_t, but somehow the sign bit can be botched
+ # here, even though Python integers are arbitrary precision.
+ if self.asBits < 0:
+ self.asBits = self.asBits + (1 << 64)
+
+ # Return this value's type tag.
+ def tag(self):
+ raise NotImplementedError
+
+ # Return this value as a 32-bit integer, double, or address.
+ def as_uint32(self):
+ raise NotImplementedError
+
+ def as_double(self):
+ packed = struct.pack("q", self.asBits)
+ (unpacked,) = struct.unpack("d", packed)
+ return unpacked
+
+ def as_address(self):
+ raise NotImplementedError
+
+
+class Punbox(Box):
+ # Packed non-number boxing --- the format used on x86_64. It would be nice to
+ # simply call Value::toInt32, etc. here, but the debugger is likely to see many
+ # Values, and doing several inferior calls for each one seems like a bad idea.
+
+ FULL_WIDTH = 64
+ TAG_SHIFT = 47
+ PAYLOAD_MASK = (1 << TAG_SHIFT) - 1
+ TAG_MASK = (1 << (FULL_WIDTH - TAG_SHIFT)) - 1
+ TAG_MAX_DOUBLE = 0x1FFF0
+ TAG_TYPE_MASK = 0x0000F
+
+ def tag(self):
+ tag = self.asBits >> Punbox.TAG_SHIFT
+ if tag <= Punbox.TAG_MAX_DOUBLE:
+ return self.jtc.DOUBLE
+ else:
+ return tag & Punbox.TAG_TYPE_MASK
+
+ def as_uint32(self):
+ return int(self.asBits & ((1 << 32) - 1))
+
+ def as_address(self):
+ return gdb.Value(self.asBits & Punbox.PAYLOAD_MASK)
+
+
+class Nunbox(Box):
+ TAG_SHIFT = 32
+ TAG_CLEAR = 0xFFFF0000
+ PAYLOAD_MASK = 0xFFFFFFFF
+ TAG_TYPE_MASK = 0x0000000F
+
+ def tag(self):
+ tag = self.asBits >> Nunbox.TAG_SHIFT
+ if tag < Nunbox.TAG_CLEAR:
+ return self.jtc.DOUBLE
+ return tag & Nunbox.TAG_TYPE_MASK
+
+ def as_uint32(self):
+ return int(self.asBits & Nunbox.PAYLOAD_MASK)
+
+ def as_address(self):
+ return gdb.Value(self.asBits & Nunbox.PAYLOAD_MASK)
+
+
+class JSValueTypeCache(object):
+ # Cache information about the Value type for this objfile.
+
+ def __init__(self, cache):
+ # Capture the tag values.
+ d = gdb.types.make_enum_dict(gdb.lookup_type("JSValueType"))
+
+ # The enum keys are prefixed when building with some compilers (clang at
+ # a minimum), so use a helper function to handle either key format.
+ def get(key):
+ val = d.get(key)
+ if val is not None:
+ return val
+ return d["JSValueType::" + key]
+
+ self.DOUBLE = get("JSVAL_TYPE_DOUBLE")
+ self.INT32 = get("JSVAL_TYPE_INT32")
+ self.UNDEFINED = get("JSVAL_TYPE_UNDEFINED")
+ self.BOOLEAN = get("JSVAL_TYPE_BOOLEAN")
+ self.MAGIC = get("JSVAL_TYPE_MAGIC")
+ self.STRING = get("JSVAL_TYPE_STRING")
+ self.SYMBOL = get("JSVAL_TYPE_SYMBOL")
+ self.BIGINT = get("JSVAL_TYPE_BIGINT")
+ self.NULL = get("JSVAL_TYPE_NULL")
+ self.OBJECT = get("JSVAL_TYPE_OBJECT")
+
+ # Let self.magic_names be an array whose i'th element is the name of
+ # the i'th magic value.
+ d = gdb.types.make_enum_dict(gdb.lookup_type("JSWhyMagic"))
+ self.magic_names = list(range(max(d.values()) + 1))
+ for (k, v) in d.items():
+ self.magic_names[v] = k
+
+ # Choose an unboxing scheme for this architecture.
+ self.boxer = Punbox if cache.void_ptr_t.sizeof == 8 else Nunbox
+
+
+@pretty_printer("JS::Value")
+class JSValue(object):
+ def __init__(self, value, cache):
+ # Save the generic typecache, and create our own, if we haven't already.
+ self.cache = cache
+ if not cache.mod_JS_Value:
+ cache.mod_JS_Value = JSValueTypeCache(cache)
+ self.jtc = cache.mod_JS_Value
+
+ self.value = value
+ self.box = self.jtc.boxer(value["asBits_"], self.jtc)
+
+ def to_string(self):
+ tag = self.box.tag()
+
+ if tag == self.jtc.UNDEFINED:
+ return "$JS::UndefinedValue()"
+ if tag == self.jtc.NULL:
+ return "$JS::NullValue()"
+ if tag == self.jtc.BOOLEAN:
+ return "$JS::BooleanValue(%s)" % str(self.box.as_uint32() != 0).lower()
+ if tag == self.jtc.MAGIC:
+ value = self.box.as_uint32()
+ if 0 <= value and value < len(self.jtc.magic_names):
+ return "$JS::MagicValue(%s)" % (self.jtc.magic_names[value],)
+ else:
+ return "$JS::MagicValue(%d)" % (value,)
+
+ if tag == self.jtc.INT32:
+ value = self.box.as_uint32()
+ signbit = 1 << 31
+ value = (value ^ signbit) - signbit
+ return "$JS::Int32Value(%s)" % value
+
+ if tag == self.jtc.DOUBLE:
+ return "$JS::DoubleValue(%s)" % self.box.as_double()
+
+ if tag == self.jtc.STRING:
+ value = self.box.as_address().cast(self.cache.JSString_ptr_t)
+ elif tag == self.jtc.OBJECT:
+ value = self.box.as_address().cast(self.cache.JSObject_ptr_t)
+ elif tag == self.jtc.SYMBOL:
+ value = self.box.as_address().cast(self.cache.JSSymbol_ptr_t)
+ elif tag == self.jtc.BIGINT:
+ return "$JS::BigIntValue()"
+ else:
+ value = "unrecognized!"
+ return "$JS::Value(%s)" % (value,)
+
+ def is_undefined(self):
+ return self.box.tag() == self.jtc.UNDEFINED
+
+ def get_string(self):
+ assert self.box.tag() == self.jtc.STRING
+ return self.box.as_address().cast(self.cache.JSString_ptr_t)
+
+ def get_private(self):
+ assert self.box.tag() == self.jtc.DOUBLE
+ return self.box.asBits
diff --git a/js/src/gdb/mozilla/prettyprinters.py b/js/src/gdb/mozilla/prettyprinters.py
new file mode 100644
index 0000000000..130e7d847c
--- /dev/null
+++ b/js/src/gdb/mozilla/prettyprinters.py
@@ -0,0 +1,447 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# mozilla/prettyprinters.py --- infrastructure for SpiderMonkey's auto-loaded pretty-printers.
+
+import re
+
+import gdb
+
+# 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.
+
+
+def check_for_reused_pretty_printer(fn):
+ # 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.)
+ 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()
+ self.uintptr_t = gdb.lookup_type("uintptr_t")
+ 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_JSOp = None
+ self.mod_JSString = None
+ self.mod_JS_Value = 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.
+#
+# 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 is_struct_or_union(t):
+ 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_:]+)<")
+
+
+def is_struct_or_union(t):
+ return t.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION)
+
+
+def is_struct_or_union_or_enum(t):
+ return t.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION, gdb.TYPE_CODE_ENUM)
+
+
+# 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 is_struct_or_union_or_enum(t) 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 is_struct_or_union(t) 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"):
+
+ def field_enum_value(f):
+ return f.enumval
+
+ else:
+
+ def field_enum_value(f):
+ return 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 0000000000..8ad4393b7a
--- /dev/null
+++ b/js/src/gdb/mozilla/unwind.py
@@ -0,0 +1,596 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# mozilla/unwind.py --- unwinder and frame filter for SpiderMonkey
+
+import platform
+
+import gdb
+import gdb.types
+from gdb.FrameDecorator import FrameDecorator
+
+from mozilla.JSObject import get_function_name, get_function_script
+from mozilla.prettyprinters import TypeCache
+
+# 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 = {
+ "FrameType::IonJS": "ExitFrameLayout",
+ "FrameType::BaselineJS": "JitFrameLayout",
+ "FrameType::BaselineStub": "BaselineStubFrameLayout",
+ "FrameType::CppToJSJit": "JitFrameLayout",
+ "FrameType::WasmToJSJit": "JitFrameLayout",
+ "FrameType::JSJitToWasm": "JitFrameLayout",
+ "FrameType::Rectifier": "RectifierFrameLayout",
+ "FrameType::IonAccessorIC": "IonAccessorICFrameLayout",
+ "FrameType::IonICCall": "IonICCallFrameLayout",
+ "FrameType::Exit": "ExitFrameLayout",
+ "FrameType::Bailout": "JitFrameLayout",
+}
+
+
+# We cannot have semi-colon as identifier names, so use a colon instead,
+# and forward the name resolution to the type cache class.
+class UnwinderTypeCacheFrameType(object):
+ def __init__(self, tc):
+ self.tc = tc
+
+ def __getattr__(self, name):
+ return self.tc.__getattr__("FrameType::" + name)
+
+
+class UnwinderTypeCache(TypeCache):
+ # All types and symbols that we need are attached to an object that we
+ # can dispose of as needed.
+
+ def __init__(self):
+ self.d = None
+ self.frame_enum_names = {}
+ self.frame_class_types = {}
+ super(UnwinderTypeCache, self).__init__(None)
+
+ # 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()
+ if name == "frame_type":
+ return UnwinderTypeCacheFrameType(self)
+ if name not in self.d:
+ return None
+ return self.d[name]
+
+ def value(self, name):
+ return long(gdb.lookup_symbol(name)[0].value())
+
+ def jit_value(self, name):
+ return self.value("js::jit::" + name)
+
+ def initialize(self):
+ self.d = {}
+ self.d["FRAMETYPE_MASK"] = (1 << self.jit_value("FRAMETYPE_BITS")) - 1
+ self.d["FRAMESIZE_SHIFT"] = self.jit_value("FRAMESIZE_SHIFT")
+ self.d["FRAME_HEADER_SIZE_SHIFT"] = self.jit_value("FRAME_HEADER_SIZE_SHIFT")
+ self.d["FRAME_HEADER_SIZE_MASK"] = self.jit_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_context"] = gdb.lookup_global_symbol("js::TlsContext")
+
+ self.d["void_starstar"] = gdb.lookup_type("void").pointer().pointer()
+
+ jitframe = gdb.lookup_type("js::jit::JitFrameLayout")
+ self.d["jitFrameLayoutPointer"] = jitframe.pointer()
+
+ self.d["CalleeToken_Function"] = self.jit_value("CalleeToken_Function")
+ self.d["CalleeToken_FunctionConstructing"] = self.jit_value(
+ "CalleeToken_FunctionConstructing"
+ )
+ self.d["CalleeToken_Script"] = self.jit_value("CalleeToken_Script")
+ self.d["JSScript"] = gdb.lookup_type("JSScript").pointer()
+ self.d["Value"] = gdb.lookup_type("JS::Value")
+
+ self.d["SOURCE_SLOT"] = self.value("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()
+
+ # ProcessExecutableMemory, used to identify if a pc is in the section
+ # pre-allocated by the JIT.
+ self.d["MaxCodeBytesPerProcess"] = self.jit_value("MaxCodeBytesPerProcess")
+ self.d["execMemory"] = gdb.lookup_symbol("::execMemory")[0].value()
+
+ # 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::", remains: "FrameType::*".
+ 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()
+
+
+class FrameSymbol(object):
+ "A symbol/value pair as expected from gdb frame decorators."
+
+ def __init__(self, sym, val):
+ self.sym = sym
+ self.val = val
+
+ def symbol(self):
+ return self.sym
+
+ def value(self):
+ return self.val
+
+
+class JitFrameDecorator(FrameDecorator):
+ """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."""
+
+ 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
+ ):
+ value = gdb.Value(calleetoken)
+ function = get_function_name(value, self.cache)
+ script = get_function_script(value, self.cache)
+ 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"]["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
+
+
+class SpiderMonkeyFrameFilter(object):
+ "A frame filter for SpiderMonkey."
+
+ # |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)
+
+
+class SpiderMonkeyFrameId(object):
+ "A frame id class, as specified by the gdb unwinder API."
+
+ def __init__(self, sp, pc):
+ self.sp = sp
+ self.pc = pc
+
+
+class UnwinderState(object):
+ """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."""
+
+ 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.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. "FrameType::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 the Jit.
+ def is_jit_address(self, pc):
+ execMem = self.typecache.execMemory
+ base = long(execMem["base_"])
+ length = self.typecache.MaxCodeBytesPerProcess
+
+ # If the base pointer is null, then no memory got allocated yet.
+ if long(base) == 0:
+ return False
+
+ # If allocated, then we allocated MaxCodeBytesPerProcess.
+ return base <= pc and pc < base + length
+
+ # Check whether |self| is valid for the selected thread.
+ def check(self):
+ return gdb.selected_thread() is self.thread
+
+ # Essentially js::TlsContext.get().
+ def get_tls_context(self):
+ return self.typecache.per_tls_context.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.frame_type.CppToJSJit:
+ # 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:
+ cx = self.get_tls_context()
+ self.activation = cx["jitActivation"]["value"]
+ else:
+ self.activation = self.activation["prevJitActivation_"]
+
+ packedExitFP = self.activation["packedExitFP_"]
+ if packedExitFP == 0:
+ return None
+
+ exit_sp = pending_frame.read_register(self.SP_REGISTER)
+ frame_type = self.typecache.frame_type.Exit
+ return self.create_frame(pc, exit_sp, packedExitFP, 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="FrameType::CppToJSJit")
+ # 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.frame_type.CppToJSJit:
+ 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)
+
+
+class x64UnwinderState(UnwinderState):
+ "The UnwinderState subclass for x86-64."
+
+ 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 == "rbp":
+ unwind_info.add_saved_register(self.SP_REGISTER, sp)
+
+
+class SpiderMonkeyUnwinder(Unwinder):
+ """The unwinder object. This provides the "user interface" to the JIT
+ unwinder, and also handles constructing or destroying UnwinderState
+ objects as needed."""
+
+ # 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 Exception:
+ # 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
+
+
+def register_unwinder(objfile):
+ """Register the unwinder and frame filter with |objfile|. If |objfile|
+ is None, register them globally."""
+
+ 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
diff --git a/js/src/gdb/progressbar.py b/js/src/gdb/progressbar.py
new file mode 100644
index 0000000000..04be825c95
--- /dev/null
+++ b/js/src/gdb/progressbar.py
@@ -0,0 +1,54 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Text progress bar library, like curl or scp.
+
+import datetime
+import sys
+import time
+
+
+class ProgressBar(object):
+ def __init__(self, label, limit, label_width=12):
+ self.label = label
+ self.limit = limit
+ self.label_width = label_width
+ self.cur = 0
+ self.t0 = datetime.datetime.now()
+ self.fullwidth = None
+
+ self.barlen = 64 - self.label_width
+ self.fmt = (
+ "\r%-" + str(label_width) + "s %3d%% %-" + str(self.barlen) + "s| %6.1fs"
+ )
+
+ def update(self, value):
+ self.cur = value
+ pct = int(100.0 * self.cur / self.limit)
+ barlen = int(1.0 * self.barlen * self.cur / self.limit) - 1
+ bar = "=" * barlen + ">"
+ dt = datetime.datetime.now() - self.t0
+ dt = dt.seconds + dt.microseconds * 1e-6
+ line = self.fmt % (self.label[: self.label_width], pct, bar, dt)
+ self.fullwidth = len(line)
+ sys.stdout.write(line)
+ sys.stdout.flush()
+
+ # Clear the current bar and leave the cursor at the start of the line.
+ def clear(self):
+ if self.fullwidth:
+ sys.stdout.write("\r" + " " * self.fullwidth + "\r")
+ self.fullwidth = None
+
+ def finish(self):
+ self.update(self.limit)
+ sys.stdout.write("\n")
+
+
+if __name__ == "__main__":
+ pb = ProgressBar("test", 12)
+ for i in range(12):
+ pb.update(i)
+ time.sleep(0.5)
+ pb.finish()
diff --git a/js/src/gdb/run-tests.py b/js/src/gdb/run-tests.py
new file mode 100644
index 0000000000..e1d18dc710
--- /dev/null
+++ b/js/src/gdb/run-tests.py
@@ -0,0 +1,469 @@
+#!/usr/bin/env python
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# run-tests.py -- Python harness for GDB SpiderMonkey support
+
+import os
+import re
+import subprocess
+import sys
+
+# From this directory:
+import progressbar
+from taskpool import TaskPool, get_cpu_count
+
+
+def _relpath(path, start=None):
+ # Backported from Python 3.1 posixpath.py
+ """Return a relative version of a path"""
+
+ if not path:
+ raise ValueError("no path specified")
+
+ if start is None:
+ start = os.curdir
+
+ start_list = os.path.abspath(start).split(os.sep)
+ path_list = os.path.abspath(path).split(os.sep)
+
+ # Work out how much of the filepath is shared by start and path.
+ i = len(os.path.commonprefix([start_list, path_list]))
+
+ rel_list = [os.pardir] * (len(start_list) - i) + path_list[i:]
+ if not rel_list:
+ return os.curdir
+ return os.path.join(*rel_list)
+
+
+os.path.relpath = _relpath
+
+# Characters that need to be escaped when used in shell words.
+shell_need_escapes = re.compile("[^\w\d%+,-./:=@'\"]", re.DOTALL)
+# Characters that need to be escaped within double-quoted strings.
+shell_dquote_escapes = re.compile('[^\w\d%+,-./:=@"]', re.DOTALL)
+
+
+def make_shell_cmd(l):
+ def quote(s):
+ if shell_need_escapes.search(s):
+ if s.find("'") < 0:
+ return "'" + s + "'"
+ return '"' + shell_dquote_escapes.sub("\\g<0>", s) + '"'
+ return s
+
+ return " ".join([quote(_) for _ in l])
+
+
+# An instance of this class collects the lists of passing, failing, and
+# timing-out tests, runs the progress bar, and prints a summary at the end.
+class Summary(object):
+ class SummaryBar(progressbar.ProgressBar):
+ def __init__(self, limit):
+ super(Summary.SummaryBar, self).__init__("", limit, 24)
+
+ def start(self):
+ self.label = "[starting ]"
+ self.update(0)
+
+ def counts(self, run, failures, timeouts):
+ self.label = "[%4d|%4d|%4d|%4d]" % (run - failures, failures, timeouts, run)
+ self.update(run)
+
+ def __init__(self, num_tests):
+ self.run = 0
+ self.failures = [] # kind of judgemental; "unexpecteds"?
+ self.timeouts = []
+ if not OPTIONS.hide_progress:
+ self.bar = Summary.SummaryBar(num_tests)
+
+ # Progress bar control.
+ def start(self):
+ if not OPTIONS.hide_progress:
+ self.bar.start()
+
+ def update(self):
+ if not OPTIONS.hide_progress:
+ self.bar.counts(self.run, len(self.failures), len(self.timeouts))
+
+ # Call 'thunk' to show some output, while getting the progress bar out of the way.
+
+ def interleave_output(self, thunk):
+ if not OPTIONS.hide_progress:
+ self.bar.clear()
+ thunk()
+ self.update()
+
+ def passed(self, test):
+ self.run += 1
+ self.update()
+
+ def failed(self, test):
+ self.run += 1
+ self.failures.append(test)
+ self.update()
+
+ def timeout(self, test):
+ self.run += 1
+ self.timeouts.append(test)
+ self.update()
+
+ def finish(self):
+ if not OPTIONS.hide_progress:
+ self.bar.finish()
+
+ if self.failures:
+
+ print("tests failed:")
+ for test in self.failures:
+ test.show(sys.stdout)
+
+ if OPTIONS.worklist:
+ try:
+ with open(OPTIONS.worklist) as out:
+ for test in self.failures:
+ out.write(test.name + "\n")
+ except IOError as err:
+ sys.stderr.write(
+ "Error writing worklist file '%s': %s" % (OPTIONS.worklist, err)
+ )
+ sys.exit(1)
+
+ if OPTIONS.write_failures:
+ try:
+ with open(OPTIONS.write_failures, "w") as out:
+ for test in self.failures:
+ test.show(out)
+ except IOError as err:
+ sys.stderr.write(
+ "Error writing worklist file '%s': %s"
+ % (OPTIONS.write_failures, err)
+ )
+ sys.exit(1)
+
+ if self.timeouts:
+ print("tests timed out:")
+ for test in self.timeouts:
+ test.show(sys.stdout)
+
+ if self.failures or self.timeouts:
+ sys.exit(2)
+
+
+class Test(TaskPool.Task):
+ def __init__(self, path, summary):
+ super(Test, self).__init__()
+ self.test_path = path # path to .py test file
+ self.summary = summary
+
+ # test.name is the name of the test relative to the top of the test
+ # directory. This is what we use to report failures and timeouts,
+ # and when writing test lists.
+ self.name = os.path.relpath(self.test_path, OPTIONS.testdir)
+
+ self.stdout = ""
+ self.stderr = ""
+ self.returncode = None
+
+ def cmd(self):
+ testlibdir = os.path.normpath(
+ os.path.join(OPTIONS.testdir, "..", "lib-for-tests")
+ )
+ return [
+ OPTIONS.gdb_executable,
+ "-nw", # Don't create a window (unnecessary?)
+ "-nx", # Don't read .gdbinit.
+ "--ex",
+ "add-auto-load-safe-path %s" % (OPTIONS.bindir,),
+ "--ex",
+ "set env LD_LIBRARY_PATH %s" % (OPTIONS.bindir,),
+ "--ex",
+ "file %s" % (os.path.join(OPTIONS.bindir, "gdb-tests"),),
+ "--eval-command",
+ "python testlibdir=%r" % (testlibdir,),
+ "--eval-command",
+ "python testscript=%r" % (self.test_path,),
+ "--eval-command",
+ "python exec(open(%r).read())" % os.path.join(testlibdir, "catcher.py"),
+ ]
+
+ def start(self, pipe, deadline):
+ super(Test, self).start(pipe, deadline)
+ if OPTIONS.show_cmd:
+ self.summary.interleave_output(lambda: self.show_cmd(sys.stdout))
+
+ def onStdout(self, text):
+ self.stdout += text
+
+ def onStderr(self, text):
+ self.stderr += text
+
+ def onFinished(self, returncode):
+ self.returncode = returncode
+ if OPTIONS.show_output:
+ self.summary.interleave_output(lambda: self.show_output(sys.stdout))
+ if returncode != 0:
+ self.summary.failed(self)
+ else:
+ self.summary.passed(self)
+
+ def onTimeout(self):
+ self.summary.timeout(self)
+
+ def show_cmd(self, out):
+ out.write("Command: %s\n" % (make_shell_cmd(self.cmd()),))
+
+ def show_output(self, out):
+ if self.stdout:
+ out.write("Standard output:")
+ out.write("\n" + self.stdout + "\n")
+ if self.stderr:
+ out.write("Standard error:")
+ out.write("\n" + self.stderr + "\n")
+
+ def show(self, out):
+ out.write(self.name + "\n")
+ if OPTIONS.write_failure_output:
+ self.show_cmd(out)
+ self.show_output(out)
+ out.write("GDB exit code: %r\n" % (self.returncode,))
+
+
+def find_tests(dir, substring=None):
+ ans = []
+ for dirpath, _, filenames in os.walk(dir):
+ if dirpath == ".":
+ continue
+ for filename in filenames:
+ if not filename.endswith(".py"):
+ continue
+ test = os.path.join(dirpath, filename)
+ if substring is None or substring in os.path.relpath(test, dir):
+ ans.append(test)
+ return ans
+
+
+def build_test_exec(builddir):
+ subprocess.check_call(["make"], cwd=builddir)
+
+
+def run_tests(tests, summary):
+ jobs = OPTIONS.workercount
+ # python 3.3 fixed a bug with concurrently writing .pyc files.
+ # https://bugs.python.org/issue13146
+ embedded_version = (
+ subprocess.check_output(
+ [
+ OPTIONS.gdb_executable,
+ "--batch",
+ "--ex",
+ "python import sys; print(sys.hexversion)",
+ ]
+ )
+ .decode("ascii")
+ .strip()
+ )
+ if hex(int(embedded_version)) < "0x3030000":
+ jobs = 1
+
+ pool = TaskPool(tests, job_limit=jobs, timeout=OPTIONS.timeout)
+ pool.run_all()
+
+
+OPTIONS = None
+
+
+def main(argv):
+ global OPTIONS
+ script_path = os.path.abspath(__file__)
+ script_dir = os.path.dirname(script_path)
+
+ # OBJDIR is a standalone SpiderMonkey build directory. This is where we
+ # find the SpiderMonkey shared library to link against.
+ #
+ # The [TESTS] optional arguments are paths of test files relative
+ # to the jit-test/tests directory.
+ from optparse import OptionParser
+
+ op = OptionParser(usage="%prog [options] OBJDIR [TESTS...]")
+ op.add_option(
+ "-s",
+ "--show-cmd",
+ dest="show_cmd",
+ action="store_true",
+ help="show GDB shell command run",
+ )
+ op.add_option(
+ "-o",
+ "--show-output",
+ dest="show_output",
+ action="store_true",
+ help="show output from GDB",
+ )
+ op.add_option(
+ "-x",
+ "--exclude",
+ dest="exclude",
+ action="append",
+ help="exclude given test dir or path",
+ )
+ op.add_option(
+ "-t",
+ "--timeout",
+ dest="timeout",
+ type=float,
+ default=150.0,
+ help="set test timeout in seconds",
+ )
+ op.add_option(
+ "-j",
+ "--worker-count",
+ dest="workercount",
+ type=int,
+ help="Run [WORKERCOUNT] tests at a time",
+ )
+ op.add_option(
+ "--no-progress",
+ dest="hide_progress",
+ action="store_true",
+ help="hide progress bar",
+ )
+ op.add_option(
+ "--worklist",
+ dest="worklist",
+ metavar="FILE",
+ help="Read tests to run from [FILE] (or run all if [FILE] not found);\n"
+ "write failures back to [FILE]",
+ )
+ op.add_option(
+ "-r",
+ "--read-tests",
+ dest="read_tests",
+ metavar="FILE",
+ help="Run test files listed in [FILE]",
+ )
+ op.add_option(
+ "-w",
+ "--write-failures",
+ dest="write_failures",
+ metavar="FILE",
+ help="Write failing tests to [FILE]",
+ )
+ op.add_option(
+ "--write-failure-output",
+ dest="write_failure_output",
+ action="store_true",
+ help="With --write-failures=FILE, additionally write the output of failed "
+ "tests to [FILE]",
+ )
+ op.add_option(
+ "--gdb",
+ dest="gdb_executable",
+ metavar="EXECUTABLE",
+ default="gdb",
+ help="Run tests with [EXECUTABLE], rather than plain 'gdb'.",
+ )
+ op.add_option(
+ "--srcdir",
+ dest="srcdir",
+ default=os.path.abspath(os.path.join(script_dir, "..")),
+ help="Use SpiderMonkey sources in [SRCDIR].",
+ )
+ op.add_option(
+ "--testdir",
+ dest="testdir",
+ default=os.path.join(script_dir, "tests"),
+ help="Find tests in [TESTDIR].",
+ )
+ op.add_option(
+ "--builddir", dest="builddir", help="Build test executable from [BUILDDIR]."
+ )
+ op.add_option("--bindir", dest="bindir", help="Run test executable from [BINDIR].")
+ (OPTIONS, args) = op.parse_args(argv)
+ if len(args) < 1:
+ op.error("missing OBJDIR argument")
+ OPTIONS.objdir = os.path.abspath(args[0])
+
+ test_args = args[1:]
+
+ if not OPTIONS.workercount:
+ OPTIONS.workercount = get_cpu_count()
+
+ # Compute defaults for OPTIONS.builddir and OPTIONS.bindir now, since we've
+ # computed OPTIONS.objdir.
+ if not OPTIONS.builddir:
+ OPTIONS.builddir = os.path.join(OPTIONS.objdir, "js", "src", "gdb")
+ if not OPTIONS.bindir:
+ OPTIONS.bindir = os.path.join(OPTIONS.objdir, "dist", "bin")
+
+ test_set = set()
+
+ # All the various sources of test names accumulate.
+ if test_args:
+ for arg in test_args:
+ test_set.update(find_tests(OPTIONS.testdir, arg))
+ if OPTIONS.worklist:
+ try:
+ with open(OPTIONS.worklist) as f:
+ for line in f:
+ test_set.update(os.path.join(OPTIONS.testdir, line.strip("\n")))
+ except IOError:
+ # With worklist, a missing file means to start the process with
+ # the complete list of tests.
+ sys.stderr.write(
+ "Couldn't read worklist file '%s'; running all tests\n"
+ % (OPTIONS.worklist,)
+ )
+ test_set = set(find_tests(OPTIONS.testdir))
+ if OPTIONS.read_tests:
+ try:
+ with open(OPTIONS.read_tests) as f:
+ for line in f:
+ test_set.update(os.path.join(OPTIONS.testdir, line.strip("\n")))
+ except IOError as err:
+ sys.stderr.write(
+ "Error trying to read test file '%s': %s\n" % (OPTIONS.read_tests, err)
+ )
+ sys.exit(1)
+
+ # If none of the above options were passed, and no tests were listed
+ # explicitly, use the complete set.
+ if not test_args and not OPTIONS.worklist and not OPTIONS.read_tests:
+ test_set = set(find_tests(OPTIONS.testdir))
+
+ if OPTIONS.exclude:
+ exclude_set = set()
+ for exclude in OPTIONS.exclude:
+ exclude_set.update(find_tests(OPTIONS.testdir, exclude))
+ test_set -= exclude_set
+
+ if not test_set:
+ sys.stderr.write("No tests found matching command line arguments.\n")
+ sys.exit(1)
+
+ summary = Summary(len(test_set))
+ test_list = [Test(_, summary) for _ in sorted(test_set)]
+
+ # Build the test executable from all the .cpp files found in the test
+ # directory tree.
+ try:
+ build_test_exec(OPTIONS.builddir)
+ except subprocess.CalledProcessError as err:
+ sys.stderr.write("Error building test executable: %s\n" % (err,))
+ sys.exit(1)
+
+ # Run the tests.
+ try:
+ summary.start()
+ run_tests(test_list, summary)
+ summary.finish()
+ except OSError as err:
+ sys.stderr.write("Error running tests: %s\n" % (err,))
+ sys.exit(1)
+
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/js/src/gdb/taskpool.py b/js/src/gdb/taskpool.py
new file mode 100644
index 0000000000..38254af6b3
--- /dev/null
+++ b/js/src/gdb/taskpool.py
@@ -0,0 +1,240 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# flake8: noqa: F821
+
+import fcntl
+import os
+import select
+import time
+from subprocess import PIPE, Popen
+
+
+class TaskPool(object):
+ # Run a series of subprocesses. Try to keep up to a certain number going in
+ # parallel at any given time. Enforce time limits.
+ #
+ # This is implemented using non-blocking I/O, and so is Unix-specific.
+ #
+ # We assume that, if a task closes its standard error, then it's safe to
+ # wait for it to terminate. So an ill-behaved task that closes its standard
+ # output and then hangs will hang us, as well. However, as it takes special
+ # effort to close one's standard output, this seems unlikely to be a
+ # problem in practice.
+
+ # A task we should run in a subprocess. Users should subclass this and
+ # fill in the methods as given.
+ class Task(object):
+ def __init__(self):
+ self.pipe = None
+ self.start_time = None
+
+ # Record that this task is running, with |pipe| as its Popen object,
+ # and should time out at |deadline|.
+ def start(self, pipe, deadline):
+ self.pipe = pipe
+ self.deadline = deadline
+
+ # Return a shell command (a string or sequence of arguments) to be
+ # passed to Popen to run the task. The command will be given
+ # /dev/null as its standard input, and pipes as its standard output
+ # and error.
+ def cmd(self):
+ raise NotImplementedError
+
+ # TaskPool calls this method to report that the process wrote
+ # |string| to its standard output.
+ def onStdout(self, string):
+ raise NotImplementedError
+
+ # TaskPool calls this method to report that the process wrote
+ # |string| to its standard error.
+ def onStderr(self, string):
+ raise NotImplementedError
+
+ # TaskPool calls this method to report that the process terminated,
+ # yielding |returncode|.
+ def onFinished(self, returncode):
+ raise NotImplementedError
+
+ # TaskPool calls this method to report that the process timed out and
+ # was killed.
+ def onTimeout(self):
+ raise NotImplementedError
+
+ # If a task output handler (onStdout, onStderr) throws this, we terminate
+ # the task.
+ class TerminateTask(Exception):
+ pass
+
+ def __init__(self, tasks, cwd=".", job_limit=4, timeout=150):
+ self.pending = iter(tasks)
+ self.cwd = cwd
+ self.job_limit = job_limit
+ self.timeout = timeout
+ self.next_pending = next(self.pending, None)
+
+ def run_all(self):
+ # The currently running tasks: a set of Task instances.
+ running = set()
+ with open(os.devnull, "r") as devnull:
+ while True:
+ while len(running) < self.job_limit and self.next_pending:
+ task = self.next_pending
+ p = Popen(
+ task.cmd(),
+ bufsize=16384,
+ stdin=devnull,
+ stdout=PIPE,
+ stderr=PIPE,
+ cwd=self.cwd,
+ )
+
+ # Put the stdout and stderr pipes in non-blocking mode. See
+ # the post-'select' code below for details.
+ flags = fcntl.fcntl(p.stdout, fcntl.F_GETFL)
+ fcntl.fcntl(p.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+ flags = fcntl.fcntl(p.stderr, fcntl.F_GETFL)
+ fcntl.fcntl(p.stderr, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+
+ task.start(p, time.time() + self.timeout)
+ running.add(task)
+ self.next_pending = next(self.pending, None)
+
+ # If we have no tasks running, and the above wasn't able to
+ # start any new ones, then we must be done!
+ if not running:
+ break
+
+ # How many seconds do we have until the earliest deadline?
+ now = time.time()
+ secs_to_next_deadline = max(min([t.deadline for t in running]) - now, 0)
+
+ # Wait for output or a timeout.
+ stdouts_and_stderrs = [t.pipe.stdout for t in running] + [
+ t.pipe.stderr for t in running
+ ]
+ (readable, w, x) = select.select(
+ stdouts_and_stderrs, [], [], secs_to_next_deadline
+ )
+ finished = set()
+ terminate = set()
+ for t in running:
+ # Since we've placed the pipes in non-blocking mode, these
+ # 'read's will simply return as many bytes as are available,
+ # rather than blocking until they have accumulated the full
+ # amount requested (or reached EOF). The 'read's should
+ # never throw, since 'select' has told us there was
+ # something available.
+ if t.pipe.stdout in readable:
+ output = t.pipe.stdout.read(16384)
+ if len(output):
+ try:
+ t.onStdout(output.decode("utf-8"))
+ except TerminateTask:
+ terminate.add(t)
+ if t.pipe.stderr in readable:
+ output = t.pipe.stderr.read(16384)
+ if len(output):
+ try:
+ t.onStderr(output.decode("utf-8"))
+ except TerminateTask:
+ terminate.add(t)
+ else:
+ # We assume that, once a task has closed its stderr,
+ # it will soon terminate. If a task closes its
+ # stderr and then hangs, we'll hang too, here.
+ t.pipe.wait()
+ t.onFinished(t.pipe.returncode)
+ finished.add(t)
+ # Remove the finished tasks from the running set. (Do this here
+ # to avoid mutating the set while iterating over it.)
+ running -= finished
+
+ # Terminate any tasks whose handlers have asked us to do so.
+ for t in terminate:
+ t.pipe.terminate()
+ t.pipe.wait()
+ running.remove(t)
+
+ # Terminate any tasks which have missed their deadline.
+ finished = set()
+ for t in running:
+ if now >= t.deadline:
+ t.pipe.terminate()
+ t.pipe.wait()
+ t.onTimeout()
+ finished.add(t)
+ # Remove the finished tasks from the running set. (Do this here
+ # to avoid mutating the set while iterating over it.)
+ running -= finished
+ return None
+
+
+def get_cpu_count():
+ """
+ Guess at a reasonable parallelism count to set as the default for the
+ current machine and run.
+ """
+ # Python 2.6+
+ try:
+ import multiprocessing
+
+ return multiprocessing.cpu_count()
+ except (ImportError, NotImplementedError):
+ pass
+
+ # POSIX
+ try:
+ res = int(os.sysconf("SC_NPROCESSORS_ONLN"))
+ if res > 0:
+ return res
+ except (AttributeError, ValueError):
+ pass
+
+ # Windows
+ try:
+ res = int(os.environ["NUMBER_OF_PROCESSORS"])
+ if res > 0:
+ return res
+ except (KeyError, ValueError):
+ pass
+
+ return 1
+
+
+if __name__ == "__main__":
+ # Test TaskPool by using it to implement the unique 'sleep sort' algorithm.
+ def sleep_sort(ns, timeout):
+ sorted = []
+
+ class SortableTask(TaskPool.Task):
+ def __init__(self, n):
+ super(SortableTask, self).__init__()
+ self.n = n
+
+ def start(self, pipe, deadline):
+ super(SortableTask, self).start(pipe, deadline)
+
+ def cmd(self):
+ return ["sh", "-c", "echo out; sleep %d; echo err>&2" % (self.n,)]
+
+ def onStdout(self, text):
+ print("%d stdout: %r" % (self.n, text))
+
+ def onStderr(self, text):
+ print("%d stderr: %r" % (self.n, text))
+
+ def onFinished(self, returncode):
+ print("%d (rc=%d)" % (self.n, returncode))
+ sorted.append(self.n)
+
+ def onTimeout(self):
+ print("%d timed out" % (self.n,))
+
+ p = TaskPool([SortableTask(_) for _ in ns], job_limit=len(ns), timeout=timeout)
+ p.run_all()
+ return sorted
+
+ print(repr(sleep_sort([1, 1, 2, 3, 5, 8, 13, 21, 34], 15)))
diff --git a/js/src/gdb/tests/enum-printers.cpp b/js/src/gdb/tests/enum-printers.cpp
new file mode 100644
index 0000000000..dfb1cd7b46
--- /dev/null
+++ b/js/src/gdb/tests/enum-printers.cpp
@@ -0,0 +1,25 @@
+#include "gdb-tests.h"
+
+#include <stdint.h>
+
+enum unscoped_no_storage { EnumValue1 };
+
+enum unscoped_with_storage : uint8_t { EnumValue2 };
+
+enum class scoped_no_storage { EnumValue3 };
+
+enum class scoped_with_storage : uint8_t { EnumValue4 };
+
+FRAGMENT(enum_printers, one) {
+ unscoped_no_storage i1 = EnumValue1;
+ unscoped_with_storage i2 = EnumValue2;
+ scoped_no_storage i3 = scoped_no_storage::EnumValue3;
+ scoped_with_storage i4 = scoped_with_storage::EnumValue4;
+
+ breakpoint();
+
+ use(i1);
+ use(i2);
+ use(i3);
+ use(i4);
+}
diff --git a/js/src/gdb/tests/enum-printers.py b/js/src/gdb/tests/enum-printers.py
new file mode 100644
index 0000000000..ff2c088dc8
--- /dev/null
+++ b/js/src/gdb/tests/enum-printers.py
@@ -0,0 +1,47 @@
+# Test that we can find pretty-printers for enums.
+# flake8: noqa: F821
+
+import mozilla.prettyprinters
+
+
+@mozilla.prettyprinters.pretty_printer("unscoped_no_storage")
+class my_typedef(object):
+ def __init__(self, value, cache):
+ pass
+
+ def to_string(self):
+ return "unscoped_no_storage::success"
+
+
+@mozilla.prettyprinters.pretty_printer("unscoped_with_storage")
+class my_typedef(object):
+ def __init__(self, value, cache):
+ pass
+
+ def to_string(self):
+ return "unscoped_with_storage::success"
+
+
+@mozilla.prettyprinters.pretty_printer("scoped_no_storage")
+class my_typedef(object):
+ def __init__(self, value, cache):
+ pass
+
+ def to_string(self):
+ return "scoped_no_storage::success"
+
+
+@mozilla.prettyprinters.pretty_printer("scoped_with_storage")
+class my_typedef(object):
+ def __init__(self, value, cache):
+ pass
+
+ def to_string(self):
+ return "scoped_with_storage::success"
+
+
+run_fragment("enum_printers.one")
+assert_pretty("i1", "unscoped_no_storage::success")
+assert_pretty("i2", "unscoped_with_storage::success")
+assert_pretty("i3", "scoped_no_storage::success")
+assert_pretty("i4", "scoped_with_storage::success")
diff --git a/js/src/gdb/tests/test-ExecutableAllocator.cpp b/js/src/gdb/tests/test-ExecutableAllocator.cpp
new file mode 100644
index 0000000000..2a2ae1cf63
--- /dev/null
+++ b/js/src/gdb/tests/test-ExecutableAllocator.cpp
@@ -0,0 +1,49 @@
+#include "gdb-tests.h"
+
+#include "jit/ExecutableAllocator.h"
+#include "vm/JSContext.h"
+
+FRAGMENT(ExecutableAllocator, empty) {
+ using namespace js::jit;
+ ExecutableAllocator execAlloc;
+
+ breakpoint();
+
+ use(execAlloc);
+}
+
+FRAGMENT(ExecutableAllocator, onepool) {
+ using namespace js::jit;
+ ExecutablePool* pool = nullptr;
+ ExecutableAllocator execAlloc;
+ execAlloc.alloc(cx, 16 * 1024, &pool, CodeKind::Baseline);
+
+ breakpoint();
+
+ use(pool);
+ use(execAlloc);
+}
+
+FRAGMENT(ExecutableAllocator, twopools) {
+ using namespace js::jit;
+ const size_t INIT_ALLOC_SIZE = 16 * 1024;
+ const size_t ALLOC_SIZE = 32 * 1024;
+ ExecutablePool* init = nullptr;
+ ExecutablePool* pool = nullptr;
+ ExecutableAllocator execAlloc;
+ size_t allocated = 0;
+
+ execAlloc.alloc(cx, INIT_ALLOC_SIZE, &init, CodeKind::Baseline);
+
+ do { // Keep allocating until we get a second pool.
+ execAlloc.alloc(cx, ALLOC_SIZE, &pool, CodeKind::Ion);
+ allocated += ALLOC_SIZE;
+ } while (pool == init);
+
+ breakpoint();
+
+ use(execAlloc);
+ init->release(INIT_ALLOC_SIZE, CodeKind::Baseline);
+ init->release(allocated - ALLOC_SIZE, CodeKind::Ion);
+ pool->release(ALLOC_SIZE, CodeKind::Ion);
+}
diff --git a/js/src/gdb/tests/test-ExecutableAllocator.py b/js/src/gdb/tests/test-ExecutableAllocator.py
new file mode 100644
index 0000000000..bec2dda623
--- /dev/null
+++ b/js/src/gdb/tests/test-ExecutableAllocator.py
@@ -0,0 +1,22 @@
+# Tests for ExecutableAllocator pretty-printing
+# Ignore flake8 errors "undefined name 'assert_regexp_pretty'"
+# As it caused by the way we instanciate this file
+# flake8: noqa: F821
+
+assert_subprinter_registered("SpiderMonkey", "JS::GCCellPtr")
+
+run_fragment("ExecutableAllocator.empty")
+
+assert_pretty("execAlloc", "ExecutableAllocator([])")
+
+run_fragment("ExecutableAllocator.onepool")
+
+reExecPool = "ExecutablePool [a-f0-9]{8,}-[a-f0-9]{8,}"
+assert_regexp_pretty("pool", reExecPool)
+assert_regexp_pretty("execAlloc", "ExecutableAllocator\(\[" + reExecPool + "\]\)")
+
+run_fragment("ExecutableAllocator.twopools")
+
+assert_regexp_pretty(
+ "execAlloc", "ExecutableAllocator\(\[" + reExecPool + ", " + reExecPool + "\]\)"
+)
diff --git a/js/src/gdb/tests/test-GCCellPtr.cpp b/js/src/gdb/tests/test-GCCellPtr.cpp
new file mode 100644
index 0000000000..8dcf135be8
--- /dev/null
+++ b/js/src/gdb/tests/test-GCCellPtr.cpp
@@ -0,0 +1,62 @@
+#include "gdb-tests.h"
+
+#include "js/CompileOptions.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/GlobalObject.h"
+#include "js/HeapAPI.h"
+#include "js/RegExpFlags.h"
+#include "js/SourceText.h"
+#include "js/Symbol.h"
+#include "js/TypeDecls.h"
+#include "vm/BigIntType.h"
+#include "vm/JSObject.h"
+#include "vm/RegExpObject.h"
+#include "vm/Shape.h"
+
+#include "vm/JSObject-inl.h"
+
+FRAGMENT(GCCellPtr, simple) {
+ JS::Rooted<JSObject*> glob(cx, JS::CurrentGlobalOrNull(cx));
+ JS::Rooted<JSString*> empty(cx, JS_NewStringCopyN(cx, nullptr, 0));
+ JS::Rooted<JS::Symbol*> unique(cx, JS::NewSymbol(cx, nullptr));
+ JS::Rooted<JS::BigInt*> zeroBigInt(cx, JS::BigInt::zero(cx));
+ JS::Rooted<js::RegExpObject*> regExp(
+ cx, js::RegExpObject::create(cx, u"", 0, JS::RegExpFlags{},
+ js::GenericObject));
+ JS::Rooted<js::RegExpShared*> rootedRegExpShared(
+ cx, js::RegExpObject::getShared(cx, regExp));
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(__FILE__, __LINE__);
+ JS::SourceText<char16_t> srcBuf;
+ (void)srcBuf.init(cx, nullptr, 0, JS::SourceOwnership::Borrowed);
+ JS::RootedScript emptyScript(cx, JS::Compile(cx, options, srcBuf));
+
+ // Inline TraceKinds.
+ JS::GCCellPtr nulll(nullptr);
+ JS::GCCellPtr object(glob.get());
+ JS::GCCellPtr string(empty.get());
+ JS::GCCellPtr symbol(unique.get());
+ JS::GCCellPtr bigint(zeroBigInt.get());
+ JS::GCCellPtr shape(glob->shape());
+
+ // Out-of-line TraceKinds.
+ JS::GCCellPtr baseShape(glob->shape()->base());
+ // JitCode can't easily be tested here, so skip it.
+ JS::GCCellPtr script(emptyScript.get());
+ JS::GCCellPtr scope(emptyScript->bodyScope());
+ JS::GCCellPtr regExpShared(rootedRegExpShared.get());
+
+ breakpoint();
+
+ use(nulll);
+ use(object);
+ use(string);
+ use(symbol);
+ use(bigint);
+ use(shape);
+ use(baseShape);
+ use(script);
+ use(scope);
+ use(regExpShared);
+}
diff --git a/js/src/gdb/tests/test-GCCellPtr.py b/js/src/gdb/tests/test-GCCellPtr.py
new file mode 100644
index 0000000000..355ca69806
--- /dev/null
+++ b/js/src/gdb/tests/test-GCCellPtr.py
@@ -0,0 +1,17 @@
+# Tests for GCCellPtr pretty-printing
+# flake8: noqa: F821
+
+assert_subprinter_registered("SpiderMonkey", "JS::GCCellPtr")
+
+run_fragment("GCCellPtr.simple")
+
+assert_pretty("nulll", "JS::GCCellPtr(nullptr)")
+assert_pretty("object", "JS::GCCellPtr((JSObject*) )")
+assert_pretty("string", "JS::GCCellPtr((JSString*) )")
+assert_pretty("symbol", "JS::GCCellPtr((JS::Symbol*) )")
+assert_pretty("bigint", "JS::GCCellPtr((JS::BigInt*) )")
+assert_pretty("shape", "JS::GCCellPtr((js::Shape*) )")
+assert_pretty("baseShape", "JS::GCCellPtr((js::BaseShape*) )")
+assert_pretty("script", "JS::GCCellPtr((js::BaseScript*) )")
+assert_pretty("scope", "JS::GCCellPtr((js::Scope*) )")
+assert_pretty("regExpShared", "JS::GCCellPtr((js::RegExpShared*) )")
diff --git a/js/src/gdb/tests/test-Interpreter.cpp b/js/src/gdb/tests/test-Interpreter.cpp
new file mode 100644
index 0000000000..cc844d59a0
--- /dev/null
+++ b/js/src/gdb/tests/test-Interpreter.cpp
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gdb-tests.h"
+
+#include "vm/Stack.h"
+
+namespace js {
+
+void GDBTestInitInterpreterRegs(InterpreterRegs& regs,
+ js::InterpreterFrame* fp_, JS::Value* sp,
+ uint8_t* pc) {
+ regs.fp_ = fp_;
+ regs.sp = sp;
+ regs.pc = pc;
+}
+
+void GDBTestInitAbstractFramePtr(AbstractFramePtr& frame,
+ InterpreterFrame* ptr) {
+ MOZ_ASSERT((uintptr_t(ptr) & AbstractFramePtr::TagMask) == 0);
+ frame.ptr_ = uintptr_t(ptr) | AbstractFramePtr::Tag_InterpreterFrame;
+}
+
+void GDBTestInitAbstractFramePtr(AbstractFramePtr& frame,
+ jit::BaselineFrame* ptr) {
+ MOZ_ASSERT((uintptr_t(ptr) & AbstractFramePtr::TagMask) == 0);
+ frame.ptr_ = uintptr_t(ptr) | AbstractFramePtr::Tag_BaselineFrame;
+}
+
+void GDBTestInitAbstractFramePtr(AbstractFramePtr& frame,
+ jit::RematerializedFrame* ptr) {
+ MOZ_ASSERT((uintptr_t(ptr) & AbstractFramePtr::TagMask) == 0);
+ frame.ptr_ = uintptr_t(ptr) | AbstractFramePtr::Tag_RematerializedFrame;
+}
+
+void GDBTestInitAbstractFramePtr(AbstractFramePtr& frame,
+ wasm::DebugFrame* ptr) {
+ MOZ_ASSERT((uintptr_t(ptr) & AbstractFramePtr::TagMask) == 0);
+ frame.ptr_ = uintptr_t(ptr) | AbstractFramePtr::Tag_WasmDebugFrame;
+}
+
+} // namespace js
+
+FRAGMENT(Interpreter, Regs) {
+ struct FakeFrame {
+ js::InterpreterFrame frame;
+ JS::Value slot0;
+ JS::Value slot1;
+ JS::Value slot2;
+ } fakeFrame;
+ uint8_t fakeOpcode = uint8_t(JSOp::True);
+
+ js::InterpreterRegs regs;
+ js::GDBTestInitInterpreterRegs(regs, &fakeFrame.frame, &fakeFrame.slot2,
+ &fakeOpcode);
+
+ breakpoint();
+
+ use(regs);
+}
+
+FRAGMENT(Interpreter, AbstractFramePtr) {
+ js::AbstractFramePtr ifptr;
+ GDBTestInitAbstractFramePtr(ifptr,
+ (js::InterpreterFrame*)uintptr_t(0x8badf00));
+
+ js::AbstractFramePtr bfptr;
+ GDBTestInitAbstractFramePtr(bfptr,
+ (js::jit::BaselineFrame*)uintptr_t(0xbadcafe0));
+
+ js::AbstractFramePtr rfptr;
+ GDBTestInitAbstractFramePtr(
+ rfptr, (js::jit::RematerializedFrame*)uintptr_t(0xdabbad00));
+
+ js::AbstractFramePtr sfptr;
+ GDBTestInitAbstractFramePtr(sfptr,
+ (js::wasm::DebugFrame*)uintptr_t(0xcb98ad00));
+
+ breakpoint();
+
+ use(ifptr);
+ use(bfptr);
+ use(rfptr);
+ use(sfptr);
+}
diff --git a/js/src/gdb/tests/test-Interpreter.py b/js/src/gdb/tests/test-Interpreter.py
new file mode 100644
index 0000000000..7a0279dc4d
--- /dev/null
+++ b/js/src/gdb/tests/test-Interpreter.py
@@ -0,0 +1,23 @@
+# Test printing interpreter internal data structures.
+# Ignore flake8 errors "undefined name 'assert_pretty'"
+# As it caused by the way we instanciate this file
+# flake8: noqa: F821
+
+assert_subprinter_registered("SpiderMonkey", "js::InterpreterRegs")
+
+run_fragment("Interpreter.Regs")
+
+assert_pretty("regs", "{ fp_ = , sp = fp_.slots() + 2, pc = (JSOp::True) }")
+
+run_fragment("Interpreter.AbstractFramePtr")
+
+assert_pretty(
+ "ifptr", "AbstractFramePtr ((js::InterpreterFrame *) ) = {ptr_ = 146464513}"
+)
+assert_pretty(
+ "bfptr", "AbstractFramePtr ((js::jit::BaselineFrame *) ) = {ptr_ = 3135025122}"
+)
+assert_pretty(
+ "rfptr",
+ "AbstractFramePtr ((js::jit::RematerializedFrame *) ) = {ptr_ = 3669732611}",
+)
diff --git a/js/src/gdb/tests/test-JSObject-null.py b/js/src/gdb/tests/test-JSObject-null.py
new file mode 100644
index 0000000000..fcda681aea
--- /dev/null
+++ b/js/src/gdb/tests/test-JSObject-null.py
@@ -0,0 +1,8 @@
+# flake8: noqa: F821
+
+gdb.execute("set print address on")
+
+run_fragment("JSObject.null")
+
+assert_pretty("null", "0x0")
+assert_pretty("nullRaw", "0x0")
diff --git a/js/src/gdb/tests/test-JSObject.cpp b/js/src/gdb/tests/test-JSObject.cpp
new file mode 100644
index 0000000000..b1780a7332
--- /dev/null
+++ b/js/src/gdb/tests/test-JSObject.cpp
@@ -0,0 +1,54 @@
+#include "gdb-tests.h"
+#include "jsapi.h"
+#include "js/GlobalObject.h"
+#include "js/Object.h" // JS::GetClass
+
+FRAGMENT(JSObject, simple) {
+ JS::Rooted<JSObject*> glob(cx, JS::CurrentGlobalOrNull(cx));
+ JS::Rooted<JSObject*> plain(cx, JS_NewPlainObject(cx));
+ JS::Rooted<JSObject*> objectProto(cx, JS::GetRealmObjectPrototype(cx));
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+ JS::Rooted<JSObject*> func(
+ cx, (JSObject*)JS_NewFunction(cx, (JSNative)1, 0, 0, "dys"));
+ JS::Rooted<JSObject*> anon(
+ cx, (JSObject*)JS_NewFunction(cx, (JSNative)1, 0, 0, nullptr));
+ JS::Rooted<JSFunction*> funcPtr(
+ cx, JS_NewFunction(cx, (JSNative)1, 0, 0, "formFollows"));
+
+ // JS_NewObject will now assert if you feed it a bad class name, so mangle
+ // the name after construction.
+ char namebuf[20] = "goodname";
+ static JSClass cls{namebuf};
+ JS::RootedObject badClassName(cx, JS_NewObject(cx, &cls));
+ MOZ_RELEASE_ASSERT(badClassName);
+ strcpy(namebuf, "\xc7X");
+
+ JSObject& plainRef = *plain;
+ JSFunction& funcRef = *funcPtr;
+ JSObject* plainRaw = plain;
+ JSObject* funcRaw = func;
+
+ breakpoint();
+
+ use(glob);
+ use(plain);
+ use(objectProto);
+ use(func);
+ use(anon);
+ use(funcPtr);
+ use(&plainRef);
+ use(&funcRef);
+ use(JS::GetClass((JSObject*)&funcRef));
+ use(plainRaw);
+ use(funcRaw);
+}
+
+FRAGMENT(JSObject, null) {
+ JS::Rooted<JSObject*> null(cx, nullptr);
+ JSObject* nullRaw = null;
+
+ breakpoint();
+
+ use(null);
+ use(nullRaw);
+}
diff --git a/js/src/gdb/tests/test-JSObject.py b/js/src/gdb/tests/test-JSObject.py
new file mode 100644
index 0000000000..6f52317d28
--- /dev/null
+++ b/js/src/gdb/tests/test-JSObject.py
@@ -0,0 +1,24 @@
+# Printing JSObjects.
+# flake8: noqa: F821
+
+assert_subprinter_registered("SpiderMonkey", "ptr-to-JSObject")
+assert_subprinter_registered("SpiderMonkey", "ref-to-JSObject")
+
+run_fragment("JSObject.simple")
+
+# These patterns look a little strange because of prologue.py's 'set print
+# address off', which avoids putting varying addresses in the output. After
+# the '(JSObject *) ', there is a 'void *' value printing as the empty
+# string.
+
+assert_pretty("glob", "(JSObject *) [object global]")
+assert_pretty("plain", "(JSObject *) [object Object]")
+assert_pretty("objectProto", "(JSObject *) [object Object] used_as_prototype")
+assert_pretty("func", '(JSObject *) [object Function "dys"]')
+assert_pretty("anon", "(JSObject *) [object Function <unnamed>]")
+assert_pretty("funcPtr", '(JSFunction *) [object Function "formFollows"]')
+
+assert_pretty("badClassName", "(JSObject *) [object \\307X]")
+
+assert_pretty("plainRef", "(JSObject &) @ [object Object]")
+assert_pretty("funcRef", '(JSFunction &) @ [object Function "formFollows"]')
diff --git a/js/src/gdb/tests/test-JSString-null.py b/js/src/gdb/tests/test-JSString-null.py
new file mode 100644
index 0000000000..26bbfcf428
--- /dev/null
+++ b/js/src/gdb/tests/test-JSString-null.py
@@ -0,0 +1,8 @@
+# flake8: noqa: F821
+
+gdb.execute("set print address on")
+
+run_fragment("JSString.null")
+
+assert_pretty("null", "0x0")
+assert_pretty("nullRaw", "0x0")
diff --git a/js/src/gdb/tests/test-JSString-subclasses.py b/js/src/gdb/tests/test-JSString-subclasses.py
new file mode 100644
index 0000000000..b685f46aff
--- /dev/null
+++ b/js/src/gdb/tests/test-JSString-subclasses.py
@@ -0,0 +1,7 @@
+# flake8: noqa: F821
+
+# We can print pointers to subclasses of JSString.
+
+run_fragment("JSString.subclasses")
+
+assert_pretty("linear", '"Hi!"')
diff --git a/js/src/gdb/tests/test-JSString.cpp b/js/src/gdb/tests/test-JSString.cpp
new file mode 100644
index 0000000000..9e930814da
--- /dev/null
+++ b/js/src/gdb/tests/test-JSString.cpp
@@ -0,0 +1,65 @@
+#include "gdb-tests.h"
+
+#include "vm/JSContext.h"
+// When JSGC_ANALYSIS is #defined, Rooted<JSLinearString*> needs the definition
+// of JSLinearString in order to figure out its ThingRootKind
+#include "vm/StringType.h"
+
+FRAGMENT(JSString, simple) {
+ JS::Rooted<JSString*> empty(cx, JS_NewStringCopyN(cx, nullptr, 0));
+ JS::Rooted<JSString*> x(cx, JS_NewStringCopyN(cx, "x", 1));
+ JS::Rooted<JSString*> z(cx, JS_NewStringCopyZ(cx, "z"));
+
+ // I expect this will be a non-inlined string.
+ JS::Rooted<JSString*> stars(cx,
+ JS_NewStringCopyZ(cx,
+ "*************************"
+ "*************************"
+ "*************************"
+ "*************************"));
+
+ // This may well be an inlined string.
+ JS::Rooted<JSString*> xz(cx, JS_ConcatStrings(cx, x, z));
+
+ // This will probably be a rope.
+ JS::Rooted<JSString*> doubleStars(cx, JS_ConcatStrings(cx, stars, stars));
+
+ // Ensure we're not confused by typedefs for pointer types.
+ JSString* xRaw = x;
+
+ breakpoint();
+
+ use(empty);
+ use(x);
+ use(z);
+ use(stars);
+ use(xz);
+ use(doubleStars);
+ use(xRaw);
+}
+
+FRAGMENT(JSString, null) {
+ JS::Rooted<JSString*> null(cx, nullptr);
+ JSString* nullRaw = null;
+
+ breakpoint();
+
+ use(null);
+ use(nullRaw);
+}
+
+FRAGMENT(JSString, subclasses) {
+ JS::Rooted<JSLinearString*> linear(
+ cx, JS_EnsureLinearString(cx, JS_NewStringCopyZ(cx, "Hi!")));
+
+ breakpoint();
+
+ use(linear);
+}
+
+FRAGMENT(JSString, atom) {
+ JSAtom* molybdenum = js::Atomize(cx, "molybdenum", 10);
+ breakpoint();
+
+ use(molybdenum);
+}
diff --git a/js/src/gdb/tests/test-JSString.py b/js/src/gdb/tests/test-JSString.py
new file mode 100644
index 0000000000..99cebc4ab5
--- /dev/null
+++ b/js/src/gdb/tests/test-JSString.py
@@ -0,0 +1,24 @@
+# Printing JSStrings.
+# flake8: noqa: F821
+
+assert_subprinter_registered("SpiderMonkey", "ptr-to-JSString")
+run_fragment("JSString.simple")
+
+assert_pretty("empty", '""')
+assert_pretty("x", '"x"')
+assert_pretty("z", '"z"')
+assert_pretty("xz", '"xz"')
+
+stars = gdb.parse_and_eval("stars")
+assert_eq(str(stars), "'*' <repeats 100 times>")
+
+doubleStars = gdb.parse_and_eval("doubleStars")
+assert_eq(str(doubleStars), "'*' <repeats 200 times>")
+
+assert_pretty("xRaw", '"x"')
+
+# JSAtom *
+
+run_fragment("JSString.atom")
+
+assert_pretty("molybdenum", '"molybdenum"')
diff --git a/js/src/gdb/tests/test-JSSymbol.cpp b/js/src/gdb/tests/test-JSSymbol.cpp
new file mode 100644
index 0000000000..815f2d9e29
--- /dev/null
+++ b/js/src/gdb/tests/test-JSSymbol.cpp
@@ -0,0 +1,22 @@
+#include "gdb-tests.h"
+
+#include "js/String.h"
+#include "js/Symbol.h"
+
+FRAGMENT(JSSymbol, simple) {
+ using namespace JS;
+
+ RootedString hello(cx, JS_NewStringCopyZ(cx, "Hello!"));
+
+ Rooted<Symbol*> unique(cx, NewSymbol(cx, nullptr));
+ Rooted<Symbol*> unique_with_desc(cx, NewSymbol(cx, hello));
+ Rooted<Symbol*> registry(cx, GetSymbolFor(cx, hello));
+ Rooted<Symbol*> well_known(cx, GetWellKnownSymbol(cx, SymbolCode::iterator));
+
+ breakpoint();
+
+ use(unique);
+ use(unique_with_desc);
+ use(registry);
+ use(well_known);
+}
diff --git a/js/src/gdb/tests/test-JSSymbol.py b/js/src/gdb/tests/test-JSSymbol.py
new file mode 100644
index 0000000000..a0dba5c036
--- /dev/null
+++ b/js/src/gdb/tests/test-JSSymbol.py
@@ -0,0 +1,11 @@
+# Printing JS::Symbols.
+# flake8: noqa: F821
+
+assert_subprinter_registered("SpiderMonkey", "ptr-to-JS::Symbol")
+
+run_fragment("JSSymbol.simple")
+
+assert_pretty("unique", "Symbol()")
+assert_pretty("unique_with_desc", 'Symbol("Hello!")')
+assert_pretty("registry", 'Symbol.for("Hello!")')
+assert_pretty("well_known", "Symbol.iterator")
diff --git a/js/src/gdb/tests/test-Root-null.py b/js/src/gdb/tests/test-Root-null.py
new file mode 100644
index 0000000000..839fa069c5
--- /dev/null
+++ b/js/src/gdb/tests/test-Root-null.py
@@ -0,0 +1,21 @@
+# Test printing roots that refer to NULL pointers.
+
+# Since mozilla.prettyprinters.Pointer declines to create pretty-printers
+# for null pointers, GDB built-in printing code ends up handling them. But
+# as of 2012-11, GDB suppresses printing pointers in replacement values:
+# see: http://sourceware.org/ml/gdb/2012-11/msg00055.html
+#
+# Thus, if the pretty-printer for JS::Rooted simply returns the referent as
+# a replacement value (which seems reasonable enough, if you want the
+# pretty-printer to be completely transparent), and the referent is a null
+# pointer, it prints as nothing at all.
+#
+# This test ensures that the JS::Rooted pretty-printer doesn't make that
+# mistake.
+# flake8: noqa: F821
+
+gdb.execute("set print address on")
+
+run_fragment("Root.null")
+
+assert_pretty("null", "0x0")
diff --git a/js/src/gdb/tests/test-Root.cpp b/js/src/gdb/tests/test-Root.cpp
new file mode 100644
index 0000000000..f93beba88a
--- /dev/null
+++ b/js/src/gdb/tests/test-Root.cpp
@@ -0,0 +1,67 @@
+#include "gdb-tests.h"
+
+#include "jsapi.h"
+
+#include "gc/Barrier.h"
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/GlobalObject.h"
+#include "js/ValueArray.h"
+#include "vm/JSFunction.h"
+
+using namespace js;
+
+FRAGMENT(Root, null) {
+ JS::Rooted<JSObject*> null(cx, nullptr);
+
+ breakpoint();
+
+ use(null);
+}
+
+void callee(JS::Handle<JSObject*> obj,
+ JS::MutableHandle<JSObject*> mutableObj) {
+ // Prevent the linker from unifying this function with others that are
+ // equivalent in machine code but not type.
+ fprintf(stderr, "Called " __FILE__ ":callee\n");
+ breakpoint();
+}
+
+FRAGMENT(Root, handle) {
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+ callee(global, &global);
+ use(global);
+}
+
+FRAGMENT(Root, HeapSlot) {
+ JS::Rooted<JS::Value> plinth(
+ cx, JS::StringValue(JS_NewStringCopyZ(cx, "plinth")));
+ JS::Rooted<JSObject*> array(
+ cx, JS::NewArrayObject(cx, JS::HandleValueArray(plinth)));
+
+ breakpoint();
+
+ use(plinth);
+ use(array);
+}
+
+FRAGMENT(Root, barriers) {
+ JSObject* obj = JS_NewPlainObject(cx);
+ PreBarriered<JSObject*> prebarriered(obj);
+ GCPtr<JSObject*> heapptr(obj);
+ HeapPtr<JSObject*> relocatable(obj);
+
+ JS::Value val = JS::ObjectValue(*obj);
+ PreBarriered<JS::Value> prebarrieredValue(JS::ObjectValue(*obj));
+ GCPtr<JS::Value> heapValue(JS::ObjectValue(*obj));
+ HeapPtr<JS::Value> relocatableValue(JS::ObjectValue(*obj));
+
+ breakpoint();
+
+ use(prebarriered);
+ use(heapptr);
+ use(relocatable);
+ use(val);
+ use(prebarrieredValue);
+ use(heapValue);
+ use(relocatableValue);
+}
diff --git a/js/src/gdb/tests/test-Root.py b/js/src/gdb/tests/test-Root.py
new file mode 100644
index 0000000000..e593a972ab
--- /dev/null
+++ b/js/src/gdb/tests/test-Root.py
@@ -0,0 +1,30 @@
+# Test printing Handles.
+# Ignore flake8 errors "undefined name 'assert_pretty'"
+# As it caused by the way we instanciate this file
+# flake8: noqa: F821
+
+assert_subprinter_registered("SpiderMonkey", "instantiations-of-JS::Rooted")
+assert_subprinter_registered("SpiderMonkey", "instantiations-of-JS::Handle")
+assert_subprinter_registered("SpiderMonkey", "instantiations-of-JS::MutableHandle")
+assert_subprinter_registered("SpiderMonkey", "instantiations-of-js::BarrieredBase")
+
+run_fragment("Root.handle")
+
+assert_pretty("obj", "(JSObject * const) [object global]")
+assert_pretty("mutableObj", "(JSObject *) [object global]")
+
+run_fragment("Root.HeapSlot")
+
+# This depends on implementation details of arrays, but since HeapSlot is
+# not a public type, I'm not sure how to avoid doing *something* ugly.
+assert_pretty("((js::NativeObject *) array.ptr)->elements_[0]", '$JS::Value("plinth")')
+
+run_fragment("Root.barriers")
+
+assert_pretty("prebarriered", "(JSObject *) [object Object]")
+assert_pretty("heapptr", "(JSObject *) [object Object]")
+assert_pretty("relocatable", "(JSObject *) [object Object]")
+assert_pretty("val", "$JS::Value((JSObject *) [object Object])")
+assert_pretty("heapValue", "$JS::Value((JSObject *) [object Object])")
+assert_pretty("prebarrieredValue", "$JS::Value((JSObject *) [object Object])")
+assert_pretty("relocatableValue", "$JS::Value((JSObject *) [object Object])")
diff --git a/js/src/gdb/tests/test-asmjs.cpp b/js/src/gdb/tests/test-asmjs.cpp
new file mode 100644
index 0000000000..8a9fab3b73
--- /dev/null
+++ b/js/src/gdb/tests/test-asmjs.cpp
@@ -0,0 +1,44 @@
+#include "gdb-tests.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/CompileOptions.h"
+#include "js/RootingAPI.h"
+#include "js/SourceText.h"
+#include "js/Value.h"
+#include "mozilla/Utf8.h"
+#include "util/Text.h"
+
+#include <string.h>
+
+FRAGMENT(asmjs, segfault) {
+ constexpr unsigned line0 = __LINE__;
+ static const char chars[] =
+ "function f(glob, ffi, heap) {\n"
+ " \"use asm\";\n"
+ " var f32 = new glob.Float32Array(heap);\n"
+ " function g(n) {\n"
+ " n = n | 0;\n"
+ " return +f32[n>>2];\n"
+ " }\n"
+ " return g;\n"
+ "}\n"
+ "\n"
+ "var func = f(this, null, new ArrayBuffer(0x10000));\n"
+ "func(0x10000 << 2);\n"
+ "'ok'\n";
+
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine(__FILE__, line0 + 1);
+ opts.asmJSOption = JS::AsmJSOption::Enabled;
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ JS::Rooted<JS::Value> rval(cx);
+
+ bool ok =
+ srcBuf.init(cx, chars, js_strlen(chars), JS::SourceOwnership::Borrowed) &&
+ JS::Evaluate(cx, opts, srcBuf, &rval);
+
+ breakpoint();
+
+ use(ok);
+ use(rval);
+}
diff --git a/js/src/gdb/tests/test-asmjs.py b/js/src/gdb/tests/test-asmjs.py
new file mode 100644
index 0000000000..9e3ed7fc89
--- /dev/null
+++ b/js/src/gdb/tests/test-asmjs.py
@@ -0,0 +1,16 @@
+# Test for special asmjs SIGSEGV-handling.
+#
+# Expected behavior is for the asm.js code in the following fragment to trigger
+# SIGSEGV. The code in js/src/gdb/mozilla/asmjs.py should prevent GDB from
+# handling that signal.
+# flake8: noqa: F821
+
+run_fragment("asmjs.segfault")
+
+# If SIGSEGV handling is broken, GDB would have stopped at the SIGSEGV signal.
+# The breakpoint would not have hit, and run_fragment would have thrown.
+#
+# So if we get here, and the asm.js code actually ran, we win.
+
+assert_pretty("ok", "true")
+assert_pretty("rval", '$JS::Value("ok")')
diff --git a/js/src/gdb/tests/test-jsbytecode.cpp b/js/src/gdb/tests/test-jsbytecode.cpp
new file mode 100644
index 0000000000..c28c6da28a
--- /dev/null
+++ b/js/src/gdb/tests/test-jsbytecode.cpp
@@ -0,0 +1,32 @@
+#include "gdb-tests.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/CompileOptions.h"
+#include "js/SourceText.h"
+#include "util/Text.h"
+#include "vm/JSFunction.h"
+#include "vm/JSScript.h"
+#include "mozilla/Utf8.h"
+
+FRAGMENT(jsbytecode, simple) {
+ constexpr unsigned line0 = __LINE__;
+ static const char chars[] = R"(
+ debugger;
+ )";
+
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine(__FILE__, line0 + 1);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ JS::Rooted<JS::Value> rval(cx);
+
+ bool ok =
+ srcBuf.init(cx, chars, js_strlen(chars), JS::SourceOwnership::Borrowed);
+
+ JSScript* script = JS::Compile(cx, opts, srcBuf);
+ jsbytecode* code = script->code();
+
+ breakpoint();
+
+ use(ok);
+ use(code);
+}
diff --git a/js/src/gdb/tests/test-jsbytecode.py b/js/src/gdb/tests/test-jsbytecode.py
new file mode 100644
index 0000000000..e69732d4c7
--- /dev/null
+++ b/js/src/gdb/tests/test-jsbytecode.py
@@ -0,0 +1,9 @@
+# Basic unit tests for jsbytecode* pretty-printer.
+# flake8: noqa: F821
+
+assert_subprinter_registered("SpiderMonkey", "ptr-to-jsbytecode")
+
+run_fragment("jsbytecode.simple")
+
+assert_pretty("ok", "true")
+assert_pretty("code", " (JSOp::Debugger)")
diff --git a/js/src/gdb/tests/test-jsid.cpp b/js/src/gdb/tests/test-jsid.cpp
new file mode 100644
index 0000000000..8104abcccd
--- /dev/null
+++ b/js/src/gdb/tests/test-jsid.cpp
@@ -0,0 +1,44 @@
+#include "gdb-tests.h"
+
+#include "js/String.h"
+#include "js/Symbol.h"
+
+FRAGMENT(jsid, simple) {
+ const char* chars = "moon";
+ JS::Rooted<JSString*> string(cx, JS_NewStringCopyZ(cx, chars));
+ JS::Rooted<JSString*> interned(cx, JS_AtomizeAndPinString(cx, chars));
+ JS::Rooted<jsid> string_id(cx, JS::PropertyKey::fromPinnedString(interned));
+ JS::Rooted<jsid> int_id(cx, JS::PropertyKey::Int(1729));
+ JS::Rooted<jsid> unique_symbol_id(
+ cx, JS::PropertyKey::Symbol(JS::NewSymbol(cx, interned)));
+ JS::Rooted<jsid> registry_symbol_id(
+ cx, JS::PropertyKey::Symbol(JS::GetSymbolFor(cx, interned)));
+ JS::Rooted<jsid> well_known_symbol_id(
+ cx, JS::GetWellKnownSymbolKey(cx, JS::SymbolCode::iterator));
+ jsid void_id = JS::PropertyKey::Void();
+
+ breakpoint();
+
+ use(string_id);
+ use(int_id);
+ use(unique_symbol_id);
+ use(registry_symbol_id);
+ use(well_known_symbol_id);
+ use(void_id);
+}
+
+void jsid_handles(JS::Handle<jsid> jsid_handle,
+ JS::MutableHandle<jsid> mutable_jsid_handle) {
+ // Prevent the linker from unifying this function with others that are
+ // equivalent in machine code but not type.
+ fprintf(stderr, "Called " __FILE__ ":jsid_handles\n");
+ breakpoint();
+}
+
+FRAGMENT(jsid, handles) {
+ const char* chars = "shovel";
+ JS::Rooted<JSString*> string(cx, JS_NewStringCopyZ(cx, chars));
+ JS::Rooted<JSString*> interned(cx, JS_AtomizeAndPinString(cx, chars));
+ JS::Rooted<jsid> string_id(cx, JS::PropertyKey::fromPinnedString(interned));
+ jsid_handles(string_id, &string_id);
+}
diff --git a/js/src/gdb/tests/test-jsid.py b/js/src/gdb/tests/test-jsid.py
new file mode 100644
index 0000000000..ea43439f2f
--- /dev/null
+++ b/js/src/gdb/tests/test-jsid.py
@@ -0,0 +1,19 @@
+# Tests for jsid pretty-printing
+# flake8: noqa: F821
+
+assert_subprinter_registered("SpiderMonkey", "JS::PropertyKey")
+
+run_fragment("jsid.simple")
+
+assert_pretty("string_id", '$jsid("moon")')
+assert_pretty("int_id", "$jsid(1729)")
+unique_symbol_pretty = str(gdb.parse_and_eval("unique_symbol_id")).split("@")[0]
+assert_eq(unique_symbol_pretty, '$jsid(Symbol("moon"))')
+assert_pretty("registry_symbol_id", '$jsid(Symbol.for("moon"))')
+assert_pretty("well_known_symbol_id", "$jsid(Symbol.iterator)")
+assert_pretty("void_id", "JS::VoidPropertyKey")
+
+run_fragment("jsid.handles")
+
+assert_pretty("jsid_handle", '$jsid("shovel")')
+assert_pretty("mutable_jsid_handle", '$jsid("shovel")')
diff --git a/js/src/gdb/tests/test-jsop.cpp b/js/src/gdb/tests/test-jsop.cpp
new file mode 100644
index 0000000000..d92c8f0d2e
--- /dev/null
+++ b/js/src/gdb/tests/test-jsop.cpp
@@ -0,0 +1,13 @@
+#include "gdb-tests.h"
+
+#include "vm/BytecodeUtil.h"
+
+FRAGMENT(jsop, simple) {
+ JSOp undefined = JSOp::Undefined;
+ JSOp debugger = JSOp::Debugger;
+
+ breakpoint();
+
+ use(undefined);
+ use(debugger);
+}
diff --git a/js/src/gdb/tests/test-jsop.py b/js/src/gdb/tests/test-jsop.py
new file mode 100644
index 0000000000..fea0da6059
--- /dev/null
+++ b/js/src/gdb/tests/test-jsop.py
@@ -0,0 +1,9 @@
+# Basic unit tests for JSOp pretty-printer.
+# flake8: noqa: F821
+
+assert_subprinter_registered("SpiderMonkey", "JSOp")
+
+run_fragment("jsop.simple")
+
+assert_pretty("undefined", "JSOp::Undefined")
+assert_pretty("debugger", "JSOp::Debugger")
diff --git a/js/src/gdb/tests/test-jsval.cpp b/js/src/gdb/tests/test-jsval.cpp
new file mode 100644
index 0000000000..3a89a76a83
--- /dev/null
+++ b/js/src/gdb/tests/test-jsval.cpp
@@ -0,0 +1,49 @@
+#include "gdb-tests.h"
+
+#include "js/GlobalObject.h"
+#include "js/String.h"
+#include "js/Symbol.h"
+#include "vm/BigIntType.h"
+
+FRAGMENT(jsval, simple) {
+ using namespace JS;
+
+ RootedValue fortytwo(cx, Int32Value(42));
+ RootedValue fortytwoD(cx, DoubleValue(42));
+ RootedValue negone(cx, Int32Value(-1));
+ RootedValue undefined(cx, UndefinedValue());
+ RootedValue null(cx, NullValue());
+ RootedValue js_true(cx, BooleanValue(true));
+ RootedValue js_false(cx, BooleanValue(false));
+ RootedValue elements_hole(cx, js::MagicValue(JS_ELEMENTS_HOLE));
+
+ RootedValue empty_string(cx);
+ empty_string.setString(JS_NewStringCopyZ(cx, ""));
+ RootedString hello(cx, JS_NewStringCopyZ(cx, "Hello!"));
+ RootedValue friendly_string(cx, StringValue(hello));
+ RootedValue symbol(cx, SymbolValue(GetSymbolFor(cx, hello)));
+ RootedValue bi(cx, BigIntValue(BigInt::zero(cx)));
+
+ RootedValue global(cx);
+ global.setObject(*CurrentGlobalOrNull(cx));
+
+ // Some interesting value that floating-point won't munge.
+ RootedValue onehundredthirtysevenonehundredtwentyeighths(
+ cx, DoubleValue(137.0 / 128.0));
+
+ breakpoint();
+
+ use(fortytwo);
+ use(fortytwoD);
+ use(negone);
+ use(undefined);
+ use(js_true);
+ use(js_false);
+ use(null);
+ use(elements_hole);
+ use(empty_string);
+ use(friendly_string);
+ use(symbol);
+ use(bi);
+ use(global);
+}
diff --git a/js/src/gdb/tests/test-jsval.py b/js/src/gdb/tests/test-jsval.py
new file mode 100644
index 0000000000..e5a42e72f4
--- /dev/null
+++ b/js/src/gdb/tests/test-jsval.py
@@ -0,0 +1,23 @@
+# Basic unit tests for jsval pretty-printer.
+# flake8: noqa: F821
+
+assert_subprinter_registered("SpiderMonkey", "JS::Value")
+
+run_fragment("jsval.simple")
+
+assert_pretty("fortytwo", "$JS::Int32Value(42)")
+assert_pretty("fortytwoD", "$JS::DoubleValue(42.0)")
+assert_pretty("negone", "$JS::Int32Value(-1)")
+assert_pretty("undefined", "$JS::UndefinedValue()")
+assert_pretty("null", "$JS::NullValue()")
+assert_pretty("js_true", "$JS::BooleanValue(true)")
+assert_pretty("js_false", "$JS::BooleanValue(false)")
+assert_pretty("elements_hole", "$JS::MagicValue(JS_ELEMENTS_HOLE)")
+assert_pretty("empty_string", '$JS::Value("")')
+assert_pretty("friendly_string", '$JS::Value("Hello!")')
+assert_pretty("symbol", '$JS::Value(Symbol.for("Hello!"))')
+assert_pretty("bi", "$JS::BigIntValue()")
+assert_pretty("global", "$JS::Value((JSObject *) [object global])")
+assert_pretty(
+ "onehundredthirtysevenonehundredtwentyeighths", "$JS::DoubleValue(1.0703125)"
+)
diff --git a/js/src/gdb/tests/test-prettyprinters.cpp b/js/src/gdb/tests/test-prettyprinters.cpp
new file mode 100644
index 0000000000..af71c52fa3
--- /dev/null
+++ b/js/src/gdb/tests/test-prettyprinters.cpp
@@ -0,0 +1,38 @@
+#include "gdb-tests.h"
+
+typedef int A;
+typedef A B;
+
+class C {};
+class D {};
+typedef C C_;
+typedef D D_;
+class E : C, D {};
+typedef E E_;
+class F : C_, D_ {};
+class G {};
+class H : F, G {};
+
+FRAGMENT(prettyprinters, implemented_types) {
+ int i = 0;
+ A a = 0;
+ B b = 0;
+ C c;
+ C_ c_;
+ E e;
+ E_ e_;
+ F f;
+ H h;
+
+ breakpoint();
+
+ use(i);
+ use(a);
+ use(b);
+ use(c);
+ use(c_);
+ use(e);
+ use(e_);
+ use(f);
+ use(h);
+}
diff --git a/js/src/gdb/tests/test-prettyprinters.py b/js/src/gdb/tests/test-prettyprinters.py
new file mode 100644
index 0000000000..f3329c0288
--- /dev/null
+++ b/js/src/gdb/tests/test-prettyprinters.py
@@ -0,0 +1,40 @@
+# Ignore flake8 errors "undefined name 'assert_pretty'"
+# As it caused by the way we instanciate this file
+# flake8: noqa: F821
+
+import mozilla.prettyprinters
+
+run_fragment("prettyprinters.implemented_types")
+
+
+def implemented_type_names(expr):
+ v = gdb.parse_and_eval(expr)
+ it = mozilla.prettyprinters.implemented_types(v.type)
+ return [str(_) for _ in it]
+
+
+assert_eq(implemented_type_names("i"), ["int"])
+assert_eq(implemented_type_names("a"), ["A", "int"])
+assert_eq(implemented_type_names("b"), ["B", "A", "int"])
+assert_eq(implemented_type_names("c"), ["C"])
+assert_eq(implemented_type_names("c_"), ["C_", "C"])
+assert_eq(implemented_type_names("e"), ["E", "C", "D"])
+assert_eq(implemented_type_names("e_"), ["E_", "E", "C", "D"])
+
+# Some compilers strip trivial typedefs in the debuginfo from classes' base
+# classes. Sometimes this can be fixed with -fno-eliminate-unused-debug-types,
+# but not always. Allow this test to pass if the typedefs are stripped.
+#
+# It would probably be better to figure out how to make the compiler emit them,
+# since I think this test is here for a reason.
+if gdb.lookup_type("F").fields()[0].name == "C_":
+ # We have the typedef info.
+ assert_eq(implemented_type_names("f"), ["F", "C_", "D_", "C", "D"])
+ assert_eq(implemented_type_names("h"), ["H", "F", "G", "C_", "D_", "C", "D"])
+else:
+ assert_eq(implemented_type_names("f"), ["F", "C", "D"])
+ assert_eq(implemented_type_names("h"), ["H", "F", "G", "C", "D"])
+
+# Check that our pretty-printers aren't interfering with printing other types.
+assert_pretty("10", "10")
+assert_pretty("(void*) 0", "") # Because of 'set print address off'
diff --git a/js/src/gdb/tests/test-unwind.cpp b/js/src/gdb/tests/test-unwind.cpp
new file mode 100644
index 0000000000..a3dc6fe89c
--- /dev/null
+++ b/js/src/gdb/tests/test-unwind.cpp
@@ -0,0 +1,76 @@
+#include "gdb-tests.h"
+#include "jsfriendapi.h" // JSFunctionSpecWithHelp
+
+#include "jit/JitOptions.h" // js::jit::JitOptions
+#include "js/CallArgs.h" // JS::CallArgs, JS::CallArgsFromVp
+#include "js/CompilationAndEvaluation.h" // JS::Evaluate
+#include "js/CompileOptions.h" // JS::CompileOptions
+#include "js/GlobalObject.h" // JS::CurrentGlobalOrNull
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+#include "js/PropertyDescriptor.h" // JSPROP_ENUMERATE
+#include "js/RootingAPI.h" // JS::Rooted
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "js/Value.h" // JS::Value
+
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include <stdint.h> // uint32_t
+#include <string.h> // strlen
+
+static bool Something(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ args.rval().setInt32(23);
+ breakpoint();
+ return true;
+}
+
+// clang-format off
+static const JSFunctionSpecWithHelp unwind_functions[] = {
+ JS_FN_HELP("something", Something, 0, 0,
+"something()",
+" Test function for test-unwind."),
+ JS_FS_HELP_END
+};
+// clang-format on
+
+FRAGMENT(unwind, simple) {
+ using namespace JS;
+
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+ if (!JS_DefineFunctionsWithHelp(cx, global, unwind_functions)) {
+ return;
+ }
+
+ // Define an itercount property and use it to ensure Baseline compilation.
+ uint32_t threshold = js::jit::JitOptions.baselineJitWarmUpThreshold;
+ RootedValue val(cx, Int32Value(threshold + 10));
+ if (!JS_DefineProperty(cx, global, "itercount", val, 0)) {
+ return;
+ }
+
+ int line0 = __LINE__;
+ const char* bytes =
+ "\n"
+ "function unwindFunctionInner() {\n"
+ " for (var i = 0; i < itercount; i++) {}\n"
+ " return something();\n"
+ "}\n"
+ "\n"
+ "function unwindFunctionOuter() {\n"
+ " for (var i = 0; i < itercount; i++) {}\n"
+ " return unwindFunctionInner();\n"
+ "}\n"
+ "\n"
+ "unwindFunctionOuter();\n";
+
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine(__FILE__, line0 + 1);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ if (!srcBuf.init(cx, bytes, strlen(bytes), JS::SourceOwnership::Borrowed)) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> rval(cx);
+ JS::Evaluate(cx, opts, srcBuf, &rval);
+}
diff --git a/js/src/gdb/tests/test-unwind.py b/js/src/gdb/tests/test-unwind.py
new file mode 100644
index 0000000000..d12c4b0f6e
--- /dev/null
+++ b/js/src/gdb/tests/test-unwind.py
@@ -0,0 +1,61 @@
+# Test the unwinder and the frame filter.
+# flake8: NOQA: F821
+import platform
+
+
+def do_unwinder_test():
+ # The unwinder is disabled by default for the moment. Turn it on to check
+ # that the unwinder works as expected.
+ import gdb
+
+ gdb.execute("enable unwinder .* SpiderMonkey")
+
+ run_fragment("unwind.simple", "Something")
+
+ first = True
+ # The unwinder is a bit flaky still but should at least be able to
+ # recognize one set of entry and exit frames. This also tests to
+ # make sure we didn't end up solely in the interpreter.
+ found_entry = False
+ found_exit = False
+ found_main = False
+ found_inner = False
+ found_outer = False
+ frames = list(gdb.frames.execute_frame_filters(gdb.newest_frame(), 0, -1))
+ for frame in frames:
+ print("examining " + frame.function())
+ if first:
+ assert_eq(frame.function().startswith("Something"), True)
+ first = False
+ elif frame.function() == "<<FrameType::Exit>>":
+ found_exit = True
+ elif frame.function() == "<<FrameType::CppToJSJit>>":
+ found_entry = True
+ elif frame.function() == "main":
+ found_main = True
+ elif "unwindFunctionInner" in frame.function():
+ found_inner = True
+ elif "unwindFunctionOuter" in frame.function():
+ found_outer = True
+
+ # Had to have found a frame.
+ assert_eq(first, False)
+ # Had to have found main.
+ assert_eq(found_main, True)
+ # Had to have found the entry and exit frames.
+ assert_eq(found_exit, True)
+ assert_eq(found_entry, True)
+ # Had to have found the names of the two JS functions.
+ assert_eq(found_inner, True)
+ assert_eq(found_outer, True)
+
+
+# Only on the right platforms.
+if platform.machine() == "x86_64" and platform.system() == "Linux":
+ # Only test when gdb has the unwinder feature.
+ try:
+ import gdb.unwinder # NOQA: F401
+
+ do_unwinder_test()
+ except Exception:
+ pass
diff --git a/js/src/gdb/tests/typedef-printers.cpp b/js/src/gdb/tests/typedef-printers.cpp
new file mode 100644
index 0000000000..efb01f82cf
--- /dev/null
+++ b/js/src/gdb/tests/typedef-printers.cpp
@@ -0,0 +1,11 @@
+#include "gdb-tests.h"
+
+typedef int my_typedef;
+
+FRAGMENT(typedef_printers, one) {
+ my_typedef i = 0;
+
+ breakpoint();
+
+ use(i);
+}
diff --git a/js/src/gdb/tests/typedef-printers.py b/js/src/gdb/tests/typedef-printers.py
new file mode 100644
index 0000000000..888379ee63
--- /dev/null
+++ b/js/src/gdb/tests/typedef-printers.py
@@ -0,0 +1,18 @@
+# Test that we can find pretty-printers for typedef names, not just for
+# struct types and templates.
+# flake8: noqa: F821
+
+import mozilla.prettyprinters
+
+
+@mozilla.prettyprinters.pretty_printer("my_typedef")
+class my_typedef(object):
+ def __init__(self, value, cache):
+ pass
+
+ def to_string(self):
+ return "huzzah"
+
+
+run_fragment("typedef_printers.one")
+assert_pretty("i", "huzzah")