From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- js/src/gdb/README | 198 +++++++++ js/src/gdb/TODO | 22 + js/src/gdb/gdb-tests-gdb.py.in | 11 + js/src/gdb/gdb-tests.cpp | 109 +++++ js/src/gdb/gdb-tests.h | 74 ++++ js/src/gdb/lib-for-tests/catcher.py | 22 + js/src/gdb/lib-for-tests/prologue.py | 90 ++++ js/src/gdb/moz.build | 53 +++ js/src/gdb/mozilla/ExecutableAllocator.py | 78 ++++ js/src/gdb/mozilla/GCCellPtr.py | 49 +++ js/src/gdb/mozilla/Interpreter.py | 80 ++++ js/src/gdb/mozilla/IonGraph.py | 199 +++++++++ js/src/gdb/mozilla/JSObject.py | 68 +++ js/src/gdb/mozilla/JSString.py | 72 ++++ js/src/gdb/mozilla/JSSymbol.py | 33 ++ js/src/gdb/mozilla/Root.py | 90 ++++ js/src/gdb/mozilla/__init__.py | 1 + js/src/gdb/mozilla/asmjs.py | 40 ++ js/src/gdb/mozilla/autoload.py | 33 ++ js/src/gdb/mozilla/jsid.py | 69 +++ js/src/gdb/mozilla/jsval.py | 222 ++++++++++ js/src/gdb/mozilla/prettyprinters.py | 368 ++++++++++++++++ js/src/gdb/mozilla/unwind.py | 591 ++++++++++++++++++++++++++ js/src/gdb/progressbar.py | 48 +++ js/src/gdb/run-tests.py | 349 +++++++++++++++ js/src/gdb/taskpool.py | 219 ++++++++++ js/src/gdb/tests/test-ExecutableAllocator.cpp | 44 ++ js/src/gdb/tests/test-ExecutableAllocator.py | 17 + js/src/gdb/tests/test-GCCellPtr.cpp | 23 + js/src/gdb/tests/test-GCCellPtr.py | 10 + js/src/gdb/tests/test-Interpreter.cpp | 92 ++++ js/src/gdb/tests/test-Interpreter.py | 14 + js/src/gdb/tests/test-JSObject-null.py | 6 + js/src/gdb/tests/test-JSObject.cpp | 43 ++ js/src/gdb/tests/test-JSObject.py | 22 + js/src/gdb/tests/test-JSString-null.py | 6 + js/src/gdb/tests/test-JSString-subclasses.py | 5 + js/src/gdb/tests/test-JSString.cpp | 64 +++ js/src/gdb/tests/test-JSString.py | 23 + js/src/gdb/tests/test-JSSymbol.cpp | 20 + js/src/gdb/tests/test-JSSymbol.py | 10 + js/src/gdb/tests/test-Root-null.py | 20 + js/src/gdb/tests/test-Root.cpp | 61 +++ js/src/gdb/tests/test-Root.py | 27 ++ js/src/gdb/tests/test-asmjs.cpp | 38 ++ js/src/gdb/tests/test-asmjs.py | 15 + js/src/gdb/tests/test-jsid.cpp | 44 ++ js/src/gdb/tests/test-jsid.py | 19 + js/src/gdb/tests/test-jsval.cpp | 40 ++ js/src/gdb/tests/test-jsval.py | 18 + js/src/gdb/tests/test-prettyprinters.cpp | 38 ++ js/src/gdb/tests/test-prettyprinters.py | 22 + js/src/gdb/tests/test-unwind.cpp | 52 +++ js/src/gdb/tests/test-unwind.py | 58 +++ js/src/gdb/tests/typedef-printers.cpp | 11 + js/src/gdb/tests/typedef-printers.py | 14 + 56 files changed, 4064 insertions(+) create mode 100644 js/src/gdb/README create mode 100644 js/src/gdb/TODO create mode 100644 js/src/gdb/gdb-tests-gdb.py.in create mode 100644 js/src/gdb/gdb-tests.cpp create mode 100644 js/src/gdb/gdb-tests.h create mode 100644 js/src/gdb/lib-for-tests/catcher.py create mode 100644 js/src/gdb/lib-for-tests/prologue.py create mode 100644 js/src/gdb/moz.build create mode 100644 js/src/gdb/mozilla/ExecutableAllocator.py create mode 100644 js/src/gdb/mozilla/GCCellPtr.py create mode 100644 js/src/gdb/mozilla/Interpreter.py create mode 100644 js/src/gdb/mozilla/IonGraph.py create mode 100644 js/src/gdb/mozilla/JSObject.py create mode 100644 js/src/gdb/mozilla/JSString.py create mode 100644 js/src/gdb/mozilla/JSSymbol.py create mode 100644 js/src/gdb/mozilla/Root.py create mode 100644 js/src/gdb/mozilla/__init__.py create mode 100644 js/src/gdb/mozilla/asmjs.py create mode 100644 js/src/gdb/mozilla/autoload.py create mode 100644 js/src/gdb/mozilla/jsid.py create mode 100644 js/src/gdb/mozilla/jsval.py create mode 100644 js/src/gdb/mozilla/prettyprinters.py create mode 100644 js/src/gdb/mozilla/unwind.py create mode 100644 js/src/gdb/progressbar.py create mode 100644 js/src/gdb/run-tests.py create mode 100644 js/src/gdb/taskpool.py create mode 100644 js/src/gdb/tests/test-ExecutableAllocator.cpp create mode 100644 js/src/gdb/tests/test-ExecutableAllocator.py create mode 100644 js/src/gdb/tests/test-GCCellPtr.cpp create mode 100644 js/src/gdb/tests/test-GCCellPtr.py create mode 100644 js/src/gdb/tests/test-Interpreter.cpp create mode 100644 js/src/gdb/tests/test-Interpreter.py create mode 100644 js/src/gdb/tests/test-JSObject-null.py create mode 100644 js/src/gdb/tests/test-JSObject.cpp create mode 100644 js/src/gdb/tests/test-JSObject.py create mode 100644 js/src/gdb/tests/test-JSString-null.py create mode 100644 js/src/gdb/tests/test-JSString-subclasses.py create mode 100644 js/src/gdb/tests/test-JSString.cpp create mode 100644 js/src/gdb/tests/test-JSString.py create mode 100644 js/src/gdb/tests/test-JSSymbol.cpp create mode 100644 js/src/gdb/tests/test-JSSymbol.py create mode 100644 js/src/gdb/tests/test-Root-null.py create mode 100644 js/src/gdb/tests/test-Root.cpp create mode 100644 js/src/gdb/tests/test-Root.py create mode 100644 js/src/gdb/tests/test-asmjs.cpp create mode 100644 js/src/gdb/tests/test-asmjs.py create mode 100644 js/src/gdb/tests/test-jsid.cpp create mode 100644 js/src/gdb/tests/test-jsid.py create mode 100644 js/src/gdb/tests/test-jsval.cpp create mode 100644 js/src/gdb/tests/test-jsval.py create mode 100644 js/src/gdb/tests/test-prettyprinters.cpp create mode 100644 js/src/gdb/tests/test-prettyprinters.py create mode 100644 js/src/gdb/tests/test-unwind.cpp create mode 100644 js/src/gdb/tests/test-unwind.py create mode 100644 js/src/gdb/tests/typedef-printers.cpp create mode 100644 js/src/gdb/tests/typedef-printers.py (limited to 'js/src/gdb') diff --git a/js/src/gdb/README b/js/src/gdb/README new file mode 100644 index 000000000..734a9747f --- /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 jsval, +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=$jsval(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=$jsval(1) + We show the handle's referent, using the jsval's tag to print it in its + JavaScript form. + +You can still see the raw form of a value with 'print/r': + + (gdb) p/r obj + $1 = {> = {}, 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, by +auto-loading the 'js-gdb.py' file that js/src/shell/Makefile.in places in the +same directory as the 'js' executable. 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 = $jsval((JSObject *) 0x7ffff151c0c0 [object Function "atan2"]) + (gdb) print vp[1] + $2 = $jsval((JSObject *) 0x7ffff150d0a0 [object Math]) + (gdb) print vp[2] + $3 = $jsval("Spleen") + (gdb) print vp[3] + $4 = $jsval(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 xrange(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 LIBDIR/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 LIBDIR. + +(It is safe to use relative paths for LIBDIR, 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 000000000..07186aa10 --- /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 ' lists a JSDefinition's uses + - 'js show ' shows entire tree + - 'js show ' lists all properties (parents) + - 'js tree ' 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 000000000..c6e400981 --- /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 000000000..5523222b9 --- /dev/null +++ b/js/src/gdb/gdb-tests.cpp @@ -0,0 +1,109 @@ +/* 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 +#include +#include + +#include "gdb-tests.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/Initialization.h" + +using namespace JS; + +static const JSClassOps global_classOps = { + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, + JS_GlobalObjectTraceHook +}; + +/* The class of the global object. */ +static const JSClass global_class = { + "global", JSCLASS_GLOBAL_FLAGS, + &global_classOps +}; + +template +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 : "", + (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); + JS_SetNativeStackQuota(cx, 5000000); + + checkBool(JS::InitSelfHostedCode(cx)); + JS::SetWarningReporter(cx, reportWarning); + + JSAutoRequest ar(cx); + + /* Create the global object. */ + JS::CompartmentOptions options; + options.behaviors().setVersion(JSVERSION_LATEST); + + RootedObject global(cx, checkPtr(JS_NewGlobalObject(cx, &global_class, + nullptr, JS::FireOnNewGlobalHook, options))); + JSAutoCompartment ac(cx, global); + + /* Populate the global object with the standard globals, + like Object and Array. */ + checkBool(JS_InitStandardClasses(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 000000000..ad9cd093b --- /dev/null +++ b/js/src/gdb/gdb-tests.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * + * 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" + +void breakpoint(); + +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(, ) { } +// +// where and 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 . 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); \ + const char* name() { 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 000000000..4bac2a025 --- /dev/null +++ b/js/src/gdb/lib-for-tests/catcher.py @@ -0,0 +1,22 @@ +# 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! + +import os +import sys +import traceback +try: + # testlibdir is set on the GDB command line, via: + # --eval-command python testlibdir=... + exec(open(os.path.join(testlibdir, 'prologue.py')).read()) +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 000000000..c54d2a674 --- /dev/null +++ b/js/src/gdb/lib-for-tests/prologue.py @@ -0,0 +1,90 @@ +import gdb +import os +import re +import sys +import traceback + +# testlibdir is set on the GDB command line, via --eval-command python testlibdir=... +sys.path[0:0] = [testlibdir] + +# 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='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') + +# 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) == 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=... + exec(open(testscript).read()) +except AssertionError as err: + sys.stderr.write('\nAssertion traceback:\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 000000000..07047fb74 --- /dev/null +++ b/js/src/gdb/moz.build @@ -0,0 +1,53 @@ +# -*- 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) + +UNIFIED_SOURCES += [ + 'gdb-tests.cpp', + 'tests/test-asmjs.cpp', + 'tests/test-ExecutableAllocator.cpp', + 'tests/test-GCCellPtr.cpp', + 'tests/test-Interpreter.cpp', + 'tests/test-jsid.cpp', + 'tests/test-JSObject.cpp', + 'tests/test-JSString.cpp', + 'tests/test-JSSymbol.cpp', + 'tests/test-jsval.cpp', + 'tests/test-prettyprinters.cpp', + 'tests/test-Root.cpp', + 'tests/test-unwind.cpp', + 'tests/typedef-printers.cpp', +] + +DEFINES['EXPORT_JS_API'] = True + +LOCAL_INCLUDES += [ + '!..', + '..', +] + +USE_LIBS += [ + 'static:js', +] + +if CONFIG['ENABLE_INTL_API'] and CONFIG['MOZ_ICU_DATA_ARCHIVE']: + # The ICU libraries linked into libmozjs will not include the ICU data, + # so link it directly. + USE_LIBS += ['icudata'] + +OS_LIBS += CONFIG['MOZ_ZLIB_LIBS'] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-shadow'] + +# This is intended as a temporary workaround to enable VS2015. +if CONFIG['_MSC_VER']: + CXXFLAGS += ['-wd4312'] + +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/ExecutableAllocator.py b/js/src/gdb/mozilla/ExecutableAllocator.py new file mode 100644 index 000000000..e1ff81240 --- /dev/null +++ b/js/src/gdb/mozilla/ExecutableAllocator.py @@ -0,0 +1,78 @@ +""" +All jitted code is allocated via the ExecutableAllocator class. Make GDB aware +of them, such that we can query for pages which are containing code which are +allocated by the Jits. +""" + +import gdb +import mozilla.prettyprinters +from mozilla.prettyprinters import pretty_printer, ptr_pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + +# Cache information about the JSString type for this objfile. +class jsjitExecutableAllocatorCache(object): + def __init__(self): + self.d = None + + def __getattr__(self, name): + if self.d is None: + self.initialize() + return self.d[name] + + def initialize(self): + self.d = {} + self.d['ExecutableAllocator'] = gdb.lookup_type('js::jit::ExecutableAllocator') + self.d['ExecutablePool'] = gdb.lookup_type('js::jit::ExecutablePool') + +@pretty_printer("js::jit::ExecutableAllocator") +class jsjitExecutableAllocator(object): + def __init__(self, value, cache): + if not cache.mod_ExecutableAllocator: + cache.mod_ExecutableAllocator = jsjitExecutableAllocatorCache() + self.value = value + self.cache = cache.mod_ExecutableAllocator + + def to_string(self): + return "ExecutableAllocator([%s])" % ', '.join([str(x) for x in self]) + + def __iter__(self): + return self.PoolIterator(self) + + class PoolIterator(object): + def __init__(self, allocator): + self.allocator = allocator + self.entryType = allocator.cache.ExecutablePool.pointer() + # Emulate the HashSet::Range + self.table = allocator.value['m_pools']['impl']['table'] + self.index = 0; + HASHNUMBER_BIT_SIZE = 32 + self.max = 1 << (HASHNUMBER_BIT_SIZE - allocator.value['m_pools']['impl']['hashShift']) + if self.table == 0: + self.max = 0 + + def __iter__(self): + return self; + + def next(self): + cur = self.index + if cur >= self.max: + raise StopIteration() + self.index = self.index + 1 + if self.table[cur]['keyHash'] > 1: # table[i]->isLive() + return self.table[cur]['mem']['u']['mDummy'].cast(self.entryType) + return self.next() + +@ptr_pretty_printer("js::jit::ExecutablePool") +class jsjitExecutablePool(mozilla.prettyprinters.Pointer): + def __init__(self, value, cache): + if not cache.mod_ExecutableAllocator: + cache.mod_ExecutableAllocator = jsjitExecutableAllocatorCache() + self.value = value + self.cache = cache.mod_ExecutableAllocator + + def to_string(self): + pages = self.value['m_allocation']['pages'] + size = self.value['m_allocation']['size'] + return "ExecutablePool %08x-%08x" % (pages, pages + size) diff --git a/js/src/gdb/mozilla/GCCellPtr.py b/js/src/gdb/mozilla/GCCellPtr.py new file mode 100644 index 000000000..87dcf73da --- /dev/null +++ b/js/src/gdb/mozilla/GCCellPtr.py @@ -0,0 +1,49 @@ +# Pretty-printers for GCCellPtr values. + +import gdb +import mozilla.prettyprinters + +from mozilla.prettyprinters import pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + +# Cache information about the JS::TraceKind type for this objfile. +class GCCellPtrTypeCache(object): + def __init__(self, cache): + self.TraceKind_t = gdb.lookup_type('JS::TraceKind') + + # Build a mapping from TraceKind enum values to the types they denote. + e = gdb.types.make_enum_dict(self.TraceKind_t) + kind_to_type = {} + def kind(k, t): + kind_to_type[e['JS::TraceKind::' + k]] = gdb.lookup_type(t) + kind('Object', 'JSObject') + kind('String', 'JSString') + kind('Symbol', 'JS::Symbol') + kind('Script', 'JSScript') + kind('Shape', 'js::Shape') + kind('ObjectGroup', 'js::ObjectGroup') + kind('BaseShape', 'js::BaseShape') + kind('JitCode', 'js::jit::JitCode') + kind('LazyScript', 'js::LazyScript') + self.kind_to_type = kind_to_type + + self.Null = e['JS::TraceKind::Null'] + self.mask = gdb.parse_and_eval('JS::OutOfLineTraceKindMask') + +@pretty_printer('JS::GCCellPtr') +class GCCellPtr(object): + def __init__(self, value, cache): + self.value = value + if not cache.mod_GCCellPtr: + cache.mod_GCCellPtr = GCCellPtrTypeCache(cache) + self.cache = cache + + def to_string(self): + ptr = self.value['ptr'] + kind = ptr & self.cache.mod_GCCellPtr.mask + if kind == self.cache.mod_GCCellPtr.Null: + return "JS::GCCellPtr(nullptr)" + tipe = self.cache.mod_GCCellPtr.kind_to_type[int(kind)] + return "JS::GCCellPtr(({}*) {})".format(tipe, ptr.cast(self.cache.void_ptr_t)) diff --git a/js/src/gdb/mozilla/Interpreter.py b/js/src/gdb/mozilla/Interpreter.py new file mode 100644 index 000000000..e54210e79 --- /dev/null +++ b/js/src/gdb/mozilla/Interpreter.py @@ -0,0 +1,80 @@ +# Pretty-printers for InterpreterRegs. + +import gdb +import mozilla.prettyprinters as prettyprinters + +prettyprinters.clear_module_printers(__name__) + +from mozilla.prettyprinters import pretty_printer + +# Cache information about the Interpreter types for this objfile. +class InterpreterTypeCache(object): + def __init__(self): + self.tValue = gdb.lookup_type('JS::Value') + self.tJSOp = gdb.lookup_type('JSOp') + self.tScriptFrameIterData = gdb.lookup_type('js::ScriptFrameIter::Data') + self.tInterpreterFrame = gdb.lookup_type('js::InterpreterFrame') + self.tBaselineFrame = gdb.lookup_type('js::jit::BaselineFrame') + self.tRematerializedFrame = gdb.lookup_type('js::jit::RematerializedFrame') + +@pretty_printer('js::InterpreterRegs') +class InterpreterRegs(object): + def __init__(self, value, cache): + self.value = value + self.cache = cache + if not cache.mod_Interpreter: + cache.mod_Interpreter = InterpreterTypeCache() + self.itc = cache.mod_Interpreter + + # There's basically no way to co-operate with 'set print pretty' (how would + # you get the current level of indentation?), so we don't even bother + # trying. No 'children', just 'to_string'. + def to_string(self): + fp_ = 'fp_ = {}'.format(self.value['fp_']) + slots = (self.value['fp_'] + 1).cast(self.itc.tValue.pointer()) + sp = 'sp = fp_.slots() + {}'.format(self.value['sp'] - slots) + pc = self.value['pc'] + try: + opcode = pc.dereference().cast(self.itc.tJSOp) + except: + opcode = 'bad pc' + pc = 'pc = {} ({})'.format(pc.cast(self.cache.void_ptr_t), opcode) + return '{{ {}, {}, {} }}'.format(fp_, sp, pc) + +@pretty_printer('js::AbstractFramePtr') +class AbstractFramePtr(object): + Tag_ScriptFrameIterData = 0x0 + Tag_InterpreterFrame = 0x1 + Tag_BaselineFrame = 0x2 + Tag_RematerializedFrame = 0x3 + TagMask = 0x3 + + def __init__(self, value, cache): + self.value = value + self.cache = cache + if not cache.mod_Interpreter: + cache.mod_Interpreter = InterpreterTypeCache() + self.itc = cache.mod_Interpreter + + def to_string(self): + ptr = self.value['ptr_'] + tag = ptr & AbstractFramePtr.TagMask + ptr = ptr & ~AbstractFramePtr.TagMask + if tag == AbstractFramePtr.Tag_ScriptFrameIterData: + label = 'js::ScriptFrameIter::Data' + ptr = ptr.cast(self.itc.tScriptFrameIterData.pointer()) + if tag == AbstractFramePtr.Tag_InterpreterFrame: + label = 'js::InterpreterFrame' + ptr = ptr.cast(self.itc.tInterpreterFrame.pointer()) + if tag == AbstractFramePtr.Tag_BaselineFrame: + label = 'js::jit::BaselineFrame' + ptr = ptr.cast(self.itc.tBaselineFrame.pointer()) + if tag == AbstractFramePtr.Tag_RematerializedFrame: + label = 'js::jit::RematerializedFrame' + ptr = ptr.cast(self.itc.tRematerializedFrame.pointer()) + return 'AbstractFramePtr (({} *) {})'.format(label, ptr) + + # Provide the ptr_ field as a child, so it prints after the pretty string + # provided above. + def children(self): + yield ('ptr_', self.value['ptr_']) diff --git a/js/src/gdb/mozilla/IonGraph.py b/js/src/gdb/mozilla/IonGraph.py new file mode 100644 index 000000000..edd9ef8e4 --- /dev/null +++ b/js/src/gdb/mozilla/IonGraph.py @@ -0,0 +1,199 @@ +""" +Debugging JIT Compilations can be obscure without large context. This python +script provide commands to let GDB open an image viewer to display the graph of +any compilation, as they are executed within GDB. + +This python script should be sourced within GDB after loading the python scripts +provided with SpiderMonkey. +""" + +import gdb +import os +import io +import subprocess +import tempfile +import time +import mozilla.prettyprinters +from mozilla.prettyprinters import pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + +# Cache information about the JSString type for this objfile. +class jsvmPrinterCache(object): + def __init__(self): + self.d = None + + def __getattr__(self, name): + if self.d is None: + self.initialize() + return self.d[name] + + def initialize(self): + self.d = {} + self.d['char'] = gdb.lookup_type('char') + +# Dummy class used to store the content of the type cache in the context of the +# iongraph command, which uses the jsvmLSprinter. +class ModuleCache(object): + def __init__(self): + self.mod_IonGraph = None + +@pretty_printer("js::vm::LSprinter") +class jsvmLSprinter(object): + def __init__(self, value, cache): + self.value = value + if not cache.mod_IonGraph: + cache.mod_IonGraph = jsvmPrinterCache() + self.cache = cache.mod_IonGraph + + def to_string(self): + next = self.value['head_'] + tail = self.value['tail_'] + if next == 0: + return "" + res = "" + while next != tail: + chars = (next + 1).cast(self.cache.char.pointer()) + res = res + chars.string('ascii', 'ignore', next['length']) + next = next['next'] + length = next['length'] - self.value['unused_'] + chars = (next + 1).cast(self.cache.char.pointer()) + res = res + chars.string('ascii', 'ignore', length) + return res + +def search_in_path(bin): + paths = os.getenv("PATH", "") + if paths == "": + return "" + for d in paths.split(":"): + f = os.path.join(d, bin) + if os.access(f, os.X_OK): + return f + return "" + +class IonGraphBinParameter(gdb.Parameter): + set_doc = "Set the path to iongraph binary, used by iongraph command." + show_doc = "Show the path to iongraph binary, used by iongraph command." + def get_set_string(self): + return "Path to iongraph binary changed to: %s" % self.value + def get_show_string(self, value): + return "Path to iongraph binary set to: %s" % value + def __init__(self): + super (IonGraphBinParameter, self).__init__ ("iongraph-bin", gdb.COMMAND_SUPPORT, gdb.PARAM_FILENAME) + self.value = os.getenv("GDB_IONGRAPH", "") + if self.value == "": + self.value = search_in_path("iongraph") + + +class DotBinParameter(gdb.Parameter): + set_doc = "Set the path to dot binary, used by iongraph command." + show_doc = "Show the path to dot binary, used by iongraph command." + def get_set_string(self): + return "Path to dot binary changed to: %s" % self.value + def get_show_string(self, value): + return "Path to dot binary set to: %s" % value + def __init__(self): + super (DotBinParameter, self).__init__ ("dot-bin", gdb.COMMAND_SUPPORT, gdb.PARAM_FILENAME) + self.value = os.getenv("GDB_DOT", "") + if self.value == "": + self.value = search_in_path("dot") + +class PngViewerBinParameter(gdb.Parameter): + set_doc = "Set the path to a png viewer binary, used by iongraph command." + show_doc = "Show the path to a png viewer binary, used by iongraph command." + def get_set_string(self): + return "Path to a png viewer binary changed to: %s" % self.value + def get_show_string(self): + return "Path to a png viewer binary set to: %s" % value + def __init__(self): + super (PngViewerBinParameter, self).__init__ ("pngviewer-bin", gdb.COMMAND_SUPPORT, gdb.PARAM_FILENAME) + self.value = os.getenv("GDB_PNGVIEWER", "") + if self.value == "": + self.value = search_in_path("xdg-open") + +iongraph = IonGraphBinParameter() +dot = DotBinParameter() +pngviewer = PngViewerBinParameter() + +class IonGraphCommand(gdb.Command): + """Command used to display the current state of the MIR graph in a png + viewer by providing an expression to access the MIRGenerator. + """ + + def __init__(self): + super (IonGraphCommand, self).__init__ ("iongraph", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION) + self.typeCache = ModuleCache() + + def invoke(self, mirGenExpr, from_tty): + """Call function from the graph spewer to populate the json printer with + the content generated by the jsonSpewer. Then we read the json content + from the jsonPrinter internal data, and gives that as input of iongraph + command.""" + + # From the MIRGenerator, find the graph spewer which contains both the + # jsonPrinter (containing the result of the output), and the jsonSpewer + # (continaining methods for spewing the graph). + mirGen = gdb.parse_and_eval(mirGenExpr) + jsonPrinter = mirGen['gs_']['jsonPrinter_'] + jsonSpewer = mirGen['gs_']['jsonSpewer_'] + graph = mirGen['graph_'] + + # These commands are doing side-effects which are saving the state of + # the compiled code on the LSprinter dedicated for logging. Fortunately, + # if you are using these gdb command, this probably means that other + # ways of getting this content failed you already, so making a mess in + # these logging strings should not cause much issues. + gdb.parse_and_eval('(*(%s*)(%s)).clear()' % (jsonPrinter.type, jsonPrinter.address,)) + gdb.parse_and_eval('(*(%s*)(%s)).beginFunction((JSScript*)0)' % (jsonSpewer.type, jsonSpewer.address,)) + gdb.parse_and_eval('(*(%s*)(%s)).beginPass("gdb")' % (jsonSpewer.type, jsonSpewer.address,)) + gdb.parse_and_eval('(*(%s*)(%s)).spewMIR((%s)%s)' % (jsonSpewer.type, jsonSpewer.address, graph.type, graph,)) + gdb.parse_and_eval('(*(%s*)(%s)).spewLIR((%s)%s)' % (jsonSpewer.type, jsonSpewer.address, graph.type, graph,)) + gdb.parse_and_eval('(*(%s*)(%s)).endPass()' % (jsonSpewer.type, jsonSpewer.address,)) + gdb.parse_and_eval('(*(%s*)(%s)).endFunction()' % (jsonSpewer.type, jsonSpewer.address,)) + + # Dump the content of the LSprinter containing the JSON view of the + # graph into a python string. + json = jsvmLSprinter(jsonPrinter, self.typeCache).to_string() + + # We are in the middle of the program execution and are messing up with + # the state of the logging data. As this might not be the first time we + # call beginFunction, we might have an extra comma at the beginning of + # the output, just strip it. + if json[0] == ",": + json = json[1:] + + # Usually this is added by the IonSpewer. + json = '{ "functions": [%s] }' % json + + # Display the content of the json with iongraph and other tools. + self.displayMIRGraph(json) + + def displayMIRGraph(self, jsonStr): + png = tempfile.NamedTemporaryFile() + + # start all processes in a shell-like equivalent of: + # iongraph < json | dot > tmp.png; xdg-open tmp.png + i = subprocess.Popen([iongraph.value, '--funcnum', '0', '--passnum', '0', '--out-mir', '-', '-'], stdin = subprocess.PIPE, stdout = subprocess.PIPE) + d = subprocess.Popen([dot.value, '-Tpng'], stdin = i.stdout, stdout = png) + + # Write the json file as the input of the iongraph command. + i.stdin.write(jsonStr) + i.stdin.close() + i.stdout.close() + + # Wait for iongraph and dot, such that the png file contains all the + # bits needed to by the png viewer. + i.wait() + output = d.communicate()[0] + + # Spawn & detach the png viewer, to which we give the name of the + # temporary file. Note, as we do not want to wait on the image viewer, + # there is a minor race between the removal of the temporary file, which + # would happen at the next garbage collection cycle, and the start of + # the png viewer. We could use a pipe, but unfortunately, this does not + # seems to be supported by xdg-open. + v = subprocess.Popen([pngviewer.value, png.name], stdin = None, stdout = None) + time.sleep(1) + +iongraph_cmd = IonGraphCommand() diff --git a/js/src/gdb/mozilla/JSObject.py b/js/src/gdb/mozilla/JSObject.py new file mode 100644 index 000000000..8112a9d36 --- /dev/null +++ b/js/src/gdb/mozilla/JSObject.py @@ -0,0 +1,68 @@ +# Pretty-printers for SpiderMonkey JSObjects. + +import re +import gdb +import mozilla.JSString +import mozilla.prettyprinters as prettyprinters +from mozilla.prettyprinters import ptr_pretty_printer, ref_pretty_printer +from mozilla.Root import deref + +prettyprinters.clear_module_printers(__name__) + +class JSObjectTypeCache(object): + def __init__(self, value, cache): + baseshape_flags = gdb.lookup_type('js::BaseShape::Flag') + self.flag_DELEGATE = prettyprinters.enum_value(baseshape_flags, 'js::BaseShape::DELEGATE') + self.func_ptr_type = gdb.lookup_type('JSFunction').pointer() + self.class_NON_NATIVE = gdb.parse_and_eval('js::Class::NON_NATIVE') + self.NativeObject_ptr_t = gdb.lookup_type('js::NativeObject').pointer() + +# There should be no need to register this for JSFunction as well, since we +# search for pretty-printers under the names of base classes, and +# JSFunction has JSObject as a base class. + +gdb_string_regexp = re.compile(r'(?:0x[0-9a-z]+ )?(?:<.*> )?"(.*)"', re.I) + +@ptr_pretty_printer('JSObject') +class JSObjectPtrOrRef(prettyprinters.Pointer): + def __init__(self, value, cache): + super(JSObjectPtrOrRef, self).__init__(value, cache) + if not cache.mod_JSObject: + cache.mod_JSObject = JSObjectTypeCache(value, cache) + self.otc = cache.mod_JSObject + + def summary(self): + group = deref(self.value['group_']) + classp = group['clasp_'] + non_native = classp['flags'] & self.otc.class_NON_NATIVE + + # Use GDB to format the class name, but then strip off the address + # and the quotes. + class_name = str(classp['name']) + m = gdb_string_regexp.match(class_name) + if m: + class_name = m.group(1) + + if non_native: + return '[object {}]'.format(class_name) + else: + native = self.value.cast(self.otc.NativeObject_ptr_t) + shape = deref(native['shape_']) + baseshape = deref(shape['base_']) + flags = baseshape['flags'] + is_delegate = bool(flags & self.otc.flag_DELEGATE) + name = None + if class_name == 'Function': + function = self.value + concrete_type = function.type.strip_typedefs() + if concrete_type.code == gdb.TYPE_CODE_REF: + function = function.address + function = function.cast(self.otc.func_ptr_type) + atom = deref(function['atom_']) + name = str(atom) if atom else '' + return '[object {}{}]{}'.format(class_name, + ' ' + name if name else '', + ' delegate' if is_delegate else '') + +@ref_pretty_printer('JSObject') +def JSObjectRef(value, cache): return JSObjectPtrOrRef(value, cache) diff --git a/js/src/gdb/mozilla/JSString.py b/js/src/gdb/mozilla/JSString.py new file mode 100644 index 000000000..99d5ce987 --- /dev/null +++ b/js/src/gdb/mozilla/JSString.py @@ -0,0 +1,72 @@ +# Pretty-printers for SpiderMonkey strings. + +import gdb +import mozilla.prettyprinters +from mozilla.prettyprinters import pretty_printer, ptr_pretty_printer + +try: + chr(10000) # UPPER RIGHT PENCIL +except ValueError as exc: # yuck, we are in Python 2.x, so chr() is 8-bit + chr = unichr # replace with teh unicodes + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + +# Cache information about the JSString type for this objfile. +class JSStringTypeCache(object): + def __init__(self, cache): + dummy = gdb.Value(0).cast(cache.JSString_ptr_t) + self.ROPE_FLAGS = dummy['ROPE_FLAGS'] + self.ATOM_BIT = dummy['ATOM_BIT'] + self.INLINE_CHARS_BIT = dummy['INLINE_CHARS_BIT'] + self.TYPE_FLAGS_MASK = dummy['TYPE_FLAGS_MASK'] + self.LATIN1_CHARS_BIT = dummy['LATIN1_CHARS_BIT'] + +class Common(mozilla.prettyprinters.Pointer): + def __init__(self, value, cache): + super(Common, self).__init__(value, cache) + if not cache.mod_JSString: + cache.mod_JSString = JSStringTypeCache(cache) + self.stc = cache.mod_JSString + +@ptr_pretty_printer("JSString") +class JSStringPtr(Common): + def display_hint(self): + return "string" + + def chars(self): + d = self.value['d'] + length = d['u1']['length'] + flags = d['u1']['flags'] + is_rope = ((flags & self.stc.TYPE_FLAGS_MASK) == self.stc.ROPE_FLAGS) + if is_rope: + for c in JSStringPtr(d['s']['u2']['left'], self.cache).chars(): + yield c + for c in JSStringPtr(d['s']['u3']['right'], self.cache).chars(): + yield c + else: + is_inline = (flags & self.stc.INLINE_CHARS_BIT) != 0 + is_latin1 = (flags & self.stc.LATIN1_CHARS_BIT) != 0 + if is_inline: + if is_latin1: + chars = d['inlineStorageLatin1'] + else: + chars = d['inlineStorageTwoByte'] + else: + if is_latin1: + chars = d['s']['u2']['nonInlineCharsLatin1'] + else: + chars = d['s']['u2']['nonInlineCharsTwoByte'] + for i in range(int(length)): + yield chars[i] + + def to_string(self): + s = u'' + for c in self.chars(): + s += chr(c) + return s + +@ptr_pretty_printer("JSAtom") +class JSAtomPtr(Common): + def to_string(self): + return self.value.cast(self.cache.JSString_ptr_t) diff --git a/js/src/gdb/mozilla/JSSymbol.py b/js/src/gdb/mozilla/JSSymbol.py new file mode 100644 index 000000000..5d747d107 --- /dev/null +++ b/js/src/gdb/mozilla/JSSymbol.py @@ -0,0 +1,33 @@ +# Pretty-printer for SpiderMonkey symbols. + +import gdb +import mozilla.prettyprinters +from mozilla.prettyprinters import ptr_pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + +# JS::SymbolCode enumerators +InSymbolRegistry = 0xfffffffe +UniqueSymbol = 0xffffffff + +@ptr_pretty_printer("JS::Symbol") +class JSSymbolPtr(mozilla.prettyprinters.Pointer): + def __init__(self, value, cache): + super(JSSymbolPtr, self).__init__(value, cache) + self.value = value + + def to_string(self): + code = int(self.value['code_']) & 0xffffffff + desc = str(self.value['description_']) + if code == InSymbolRegistry: + return "Symbol.for({})".format(desc) + elif code == UniqueSymbol: + return "Symbol({})".format(desc) + else: + # Well-known symbol. Strip off the quotes added by the JSString * + # pretty-printer. + assert desc[0] == '"' + assert desc[-1] == '"' + return desc[1:-1] + diff --git a/js/src/gdb/mozilla/Root.py b/js/src/gdb/mozilla/Root.py new file mode 100644 index 000000000..4656a3852 --- /dev/null +++ b/js/src/gdb/mozilla/Root.py @@ -0,0 +1,90 @@ +# Pretty-printers and utilities for SpiderMonkey rooting templates: +# Rooted, Handle, MutableHandle, etc. + +import mozilla.prettyprinters +from mozilla.prettyprinters import pretty_printer, template_pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + +# Common base class for all the rooting template pretty-printers. All these +# templates have one member holding the referent (or a pointer to it), so +# there's not much to it. +class Common(object): + # The name of the template member holding the referent. + member = 'ptr' + + # If True, this is a handle type, and should be dereferenced. If False, + # the template member holds the referent directly. + handle = False + + # If True, we should strip typedefs from our referent type. (Rooted + # 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|, for example, appears in GDB as |JS::Rooted| --- + # 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|. That constructor creates a |JS::Rooted| + # pretty-printer, and explicitly specifies the constructor for the referent, + # using this initializer's |content_printer| argument. + def __init__(self, value, cache, content_printer=None): + self.value = value + self.cache = cache + self.content_printer = content_printer + def to_string(self): + ptr = self.value[self.member] + if self.handle: + ptr = ptr.dereference() + if self.strip_typedefs: + ptr = ptr.cast(ptr.type.strip_typedefs()) + if self.content_printer: + return self.content_printer(ptr, self.cache).to_string() + else: + # As of 2012-11, GDB suppresses printing pointers in replacement + # values; see http://sourceware.org/ml/gdb/2012-11/msg00055.html + # That means that simply returning the 'ptr' member won't work. + # Instead, just invoke GDB's formatter ourselves. + return str(ptr) + +@template_pretty_printer("JS::Rooted") +class Rooted(Common): + strip_typedefs = True + +@template_pretty_printer("JS::Handle") +class Handle(Common): + handle = True + +@template_pretty_printer("JS::MutableHandle") +class MutableHandle(Common): + handle = True + +@template_pretty_printer("js::BarrieredBase") +class BarrieredBase(Common): + member = 'value' + +# Return the referent of a HeapPtr, Rooted, or Handle. +def deref(root): + tag = root.type.strip_typedefs().tag + if not tag: + raise TypeError("Can't dereference type with no structure tag: %s" % (root.type,)) + elif tag.startswith('js::HeapPtr<'): + return root['value'] + elif tag.startswith('JS::Rooted<'): + return root['ptr'] + elif tag.startswith('JS::Handle<'): + return root['ptr'] + elif tag.startswith('js::GCPtr<'): + return root['value'] + else: + raise NotImplementedError("Unrecognized tag: " + tag) diff --git a/js/src/gdb/mozilla/__init__.py b/js/src/gdb/mozilla/__init__.py new file mode 100644 index 000000000..b879ca264 --- /dev/null +++ b/js/src/gdb/mozilla/__init__.py @@ -0,0 +1 @@ +# Yes, Python, this is a package. diff --git a/js/src/gdb/mozilla/asmjs.py b/js/src/gdb/mozilla/asmjs.py new file mode 100644 index 000000000..43fac20c7 --- /dev/null +++ b/js/src/gdb/mozilla/asmjs.py @@ -0,0 +1,40 @@ +""" +In asm code, out-of-bounds heap accesses cause segfaults, which the engine +handles internally. Make GDB ignore them. +""" + +import gdb + +SIGSEGV = 11 + +# A sigaction buffer for each inferior process. +sigaction_buffers = {} + +def on_stop(event): + if isinstance(event, gdb.SignalEvent) and event.stop_signal == 'SIGSEGV': + # Allocate memory for sigaction, once per js shell process. + process = gdb.selected_inferior() + buf = sigaction_buffers.get(process) + if buf is None: + buf = gdb.parse_and_eval("(struct sigaction *) malloc(sizeof(struct sigaction))") + sigaction_buffers[process] = buf + + # See if AsmJSFaultHandler is installed as the SIGSEGV signal action. + sigaction_fn = gdb.parse_and_eval('__sigaction') + sigaction_fn(SIGSEGV, 0, buf) + AsmJSFaultHandler = gdb.parse_and_eval("AsmJSFaultHandler") + if buf['__sigaction_handler']['sa_handler'] == AsmJSFaultHandler: + # Advise the user that magic is happening. + print("js/src/gdb/mozilla/asmjs.py: Allowing AsmJSFaultHandler to run.") + + # If AsmJSFaultHandler doesn't handle this segfault, it will unhook + # itself and re-raise. + gdb.execute("continue") + +def on_exited(event): + if event.inferior in sigaction_buffers: + del sigaction_buffers[event.inferior] + +def install(): + gdb.events.stop.connect(on_stop) + gdb.events.exited.connect(on_exited) diff --git a/js/src/gdb/mozilla/autoload.py b/js/src/gdb/mozilla/autoload.py new file mode 100644 index 000000000..43cbcdf72 --- /dev/null +++ b/js/src/gdb/mozilla/autoload.py @@ -0,0 +1,33 @@ +# mozilla/autoload.py: Autoload SpiderMonkey pretty-printers. + +print("Loading JavaScript value pretty-printers; see js/src/gdb/README.") +print("If they cause trouble, type: disable pretty-printer .* SpiderMonkey") + +import gdb.printing +import mozilla.prettyprinters + +# Import the pretty-printer modules. As a side effect, loading these +# modules registers their printers with mozilla.prettyprinters. +import mozilla.GCCellPtr +import mozilla.Interpreter +import mozilla.IonGraph +import mozilla.JSObject +import mozilla.JSString +import mozilla.JSSymbol +import mozilla.Root +import mozilla.jsid +import mozilla.jsval +import mozilla.unwind + +# The user may have personal pretty-printers. Get those, too, if they exist. +try: + import my_mozilla_printers +except ImportError: + pass + +# Register our pretty-printers with |objfile|. +def register(objfile): + lookup = mozilla.prettyprinters.lookup_for_objfile(objfile) + if lookup: + gdb.printing.register_pretty_printer(objfile, lookup, replace=True) + mozilla.unwind.register_unwinder(objfile) diff --git a/js/src/gdb/mozilla/jsid.py b/js/src/gdb/mozilla/jsid.py new file mode 100644 index 000000000..b7c0e183b --- /dev/null +++ b/js/src/gdb/mozilla/jsid.py @@ -0,0 +1,69 @@ +# Pretty-printers for JSID values. + +import gdb +import mozilla.prettyprinters +import mozilla.Root + +from mozilla.prettyprinters import pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + +@pretty_printer('jsid') +class jsid(object): + # Since people don't always build with macro debugging info, I can't + # think of any way to avoid copying these values here, short of using + # inferior calls for every operation (which, I hear, is broken from + # pretty-printers in some recent GDBs). + TYPE_STRING = 0x0 + TYPE_INT = 0x1 + TYPE_VOID = 0x2 + TYPE_SYMBOL = 0x4 + TYPE_MASK = 0x7 + + def __init__(self, value, cache): + self.value = value + self.cache = cache + self.concrete_type = self.value.type.strip_typedefs() + + # SpiderMonkey has two alternative definitions of jsid: a typedef for + # ptrdiff_t, and a struct with == and != operators defined on it. + # Extract the bits from either one. + def as_bits(self): + if self.concrete_type.code == gdb.TYPE_CODE_STRUCT: + return self.value['asBits'] + elif self.concrete_type.code == gdb.TYPE_CODE_INT: + return self.value + else: + raise RuntimeError("definition of SpiderMonkey 'jsid' type" + "neither struct nor integral type") + + def to_string(self): + bits = self.as_bits() + tag = bits & jsid.TYPE_MASK + if tag == jsid.TYPE_STRING: + body = bits.cast(self.cache.JSString_ptr_t) + elif tag & jsid.TYPE_INT: + body = bits >> 1 + elif tag == jsid.TYPE_VOID: + return "JSID_VOID" + elif tag == jsid.TYPE_SYMBOL: + if bits == jsid.TYPE_SYMBOL: + return "JSID_EMPTY" + body = ((bits & ~jsid.TYPE_MASK) + .cast(self.cache.JSSymbol_ptr_t)) + else: + body = "" + return '$jsid(%s)' % (body,) + +# Hard-code the referent type pretty-printer for jsid roots and handles. +# See the comment for mozilla.Root.Common.__init__. +@pretty_printer('JS::Rooted') +def RootedJSID(value, cache): + return mozilla.Root.Rooted(value, cache, jsid) +@pretty_printer('JS::Handle') +def HandleJSID(value, cache): + return mozilla.Root.Handle(value, cache, jsid) +@pretty_printer('JS::MutableHandle') +def MutableHandleJSID(value, cache): + return mozilla.Root.MutableHandle(value, cache, jsid) diff --git a/js/src/gdb/mozilla/jsval.py b/js/src/gdb/mozilla/jsval.py new file mode 100644 index 000000000..f5ae78b1c --- /dev/null +++ b/js/src/gdb/mozilla/jsval.py @@ -0,0 +1,222 @@ +# Pretty-printers for SpiderMonkey jsvals. + +import gdb +import gdb.types +import mozilla.prettyprinters +from mozilla.prettyprinters import pretty_printer, ptr_pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + +# Summary of the JS::Value (also known as jsval) type: +# +# Viewed abstractly, JS::Value is a 64-bit discriminated union, with +# JSString *, JSObject *, IEEE 64-bit floating-point, and 32-bit integer +# branches (and a few others). (It is not actually a C++ union; +# 'discriminated union' just describes the overall effect.) Note that +# JS::Value is always 64 bits long, even on 32-bit architectures. +# +# The ECMAScript standard specifies that ECMAScript numbers are IEEE 64-bit +# floating-point values. A JS::Value can represent any JavaScript number +# value directly, without referring to additional storage, or represent an +# object, string, or other ECMAScript value, and remember which type it is. +# This may seem surprising: how can a 64-bit type hold all the 64-bit IEEE +# values, and still distinguish them from objects, strings, and so on, +# which have 64-bit addresses? +# +# This is possible for two reasons: +# +# - First, ECMAScript implementations aren't required to distinguish all +# the values the IEEE 64-bit format can represent. The IEEE format +# specifies many bitstrings representing NaN values, while ECMAScript +# requires only a single NaN value. This means we can use one IEEE NaN to +# represent ECMAScript's NaN, and use all the other IEEE NaNs to +# represent the other ECMAScript values. +# +# (IEEE says that any floating-point value whose 11-bit exponent field is +# 0x7ff (all ones) and whose 52-bit fraction field is non-zero is a NaN. +# So as long as we ensure the fraction field is non-zero, and save a NaN +# for ECMAScript, we have 2^52 values to play with.) +# +# - Second, on the only 64-bit architecture we support, x86_64, only the +# lower 48 bits of an address are significant. The upper sixteen bits are +# required to be the sign-extension of bit 48. Furthermore, user code +# always runs in "positive addresses": those in which bit 48 is zero. So +# we only actually need 47 bits to store all possible object or string +# addresses, even on 64-bit platforms. +# +# With a 52-bit fraction field, and 47 bits needed for the 'payload', we +# have up to five bits left to store a 'tag' value, to indicate which +# branch of our discriminated union is live. +# +# Thus, we define JS::Value representations in terms of the IEEE 64-bit +# floating-point format: +# +# - Any bitstring that IEEE calls a number or an infinity represents that +# ECMAScript number. +# +# - Any bitstring that IEEE calls a NaN represents either an ECMAScript NaN +# or a non-number ECMAScript value, as determined by a tag field stored +# towards the most significant end of the fraction field (exactly where +# depends on the address size). If the tag field indicates that this +# JS::Value is an object, the fraction field's least significant end +# holds the address of a JSObject; if a string, the address of a +# JSString; and so on. +# +# On the only 64-bit platform we support, x86_64, only the lower 48 bits of +# an address are significant, and only those values whose top bit is zero +# are used for user-space addresses. This means that x86_64 addresses are +# effectively 47 bits long, and thus fit nicely in the available portion of +# the fraction field. +# +# +# In detail: +# +# - jsval (Value.h) is a typedef for JS::Value. +# +# - JS::Value (Value.h) is a class with a lot of methods and a single data +# member, of type jsval_layout. +# +# - jsval_layout (Value.h) is a helper type for picking apart values. This +# is always 64 bits long, with a variant for each address size (32 bits +# or 64 bits) and endianness (little- or big-endian). +# +# jsval_layout is a union with 'asBits', 'asDouble', and 'asPtr' +# branches, and an 's' branch, which is a struct that tries to break out +# the bitfields a little for the non-double types. On 64-bit machines, +# jsval_layout also has an 'asUIntPtr' branch. +# +# On 32-bit platforms, the 's' structure has a 'tag' member at the +# exponent end of the 's' struct, and a 'payload' union at the mantissa +# end. The 'payload' union's branches are things like JSString *, +# JSObject *, and so on: the natural representations of the tags. +# +# On 64-bit platforms, the payload is 47 bits long; since C++ doesn't let +# us declare bitfields that hold unions, we can't break it down so +# neatly. In this case, we apply bit-shifting tricks to the 'asBits' +# branch of the union to extract the tag. + +class Box(object): + def __init__(self, asBits, jtc): + self.asBits = asBits + self.jtc = jtc + # jsval_layout::asBits is uint64, but somebody botches the sign bit, even + # though Python integers are arbitrary precision. + if self.asBits < 0: + self.asBits = self.asBits + (1 << 64) + + # Return this value's type tag. + def tag(self): raise NotImplementedError + + # Return this value as a 32-bit integer, double, or address. + def as_uint32(self): raise NotImplementedError + def as_double(self): raise NotImplementedError + def as_address(self): raise NotImplementedError + +# Packed non-number boxing --- the format used on x86_64. It would be nice to simply +# call JSVAL_TO_INT, etc. here, but the debugger is likely to see many jsvals, and +# doing several inferior calls for each one seems like a bad idea. +class Punbox(Box): + + FULL_WIDTH = 64 + TAG_SHIFT = 47 + PAYLOAD_MASK = (1 << TAG_SHIFT) - 1 + TAG_MASK = (1 << (FULL_WIDTH - TAG_SHIFT)) - 1 + TAG_MAX_DOUBLE = 0x1fff0 + TAG_TYPE_MASK = 0x0000f + + def tag(self): + tag = self.asBits >> Punbox.TAG_SHIFT + if tag <= Punbox.TAG_MAX_DOUBLE: + return self.jtc.DOUBLE + else: + return tag & Punbox.TAG_TYPE_MASK + + def as_uint32(self): return int(self.asBits & ((1 << 32) - 1)) + def as_address(self): return gdb.Value(self.asBits & Punbox.PAYLOAD_MASK) + +class Nunbox(Box): + TAG_SHIFT = 32 + TAG_CLEAR = 0xffff0000 + PAYLOAD_MASK = 0xffffffff + TAG_TYPE_MASK = 0x0000000f + + def tag(self): + tag = self.asBits >> Nunbox.TAG_SHIFT + if tag < Nunbox.TAG_CLEAR: + return self.jtc.DOUBLE + return tag & Nunbox.TAG_TYPE_MASK + + def as_uint32(self): return int(self.asBits & Nunbox.PAYLOAD_MASK) + def as_address(self): return gdb.Value(self.asBits & Nunbox.PAYLOAD_MASK) + +# Cache information about the jsval type for this objfile. +class jsvalTypeCache(object): + def __init__(self, cache): + # Capture the tag values. + d = gdb.types.make_enum_dict(gdb.lookup_type('JSValueType')) + self.DOUBLE = d['JSVAL_TYPE_DOUBLE'] + self.INT32 = d['JSVAL_TYPE_INT32'] + self.UNDEFINED = d['JSVAL_TYPE_UNDEFINED'] + self.BOOLEAN = d['JSVAL_TYPE_BOOLEAN'] + self.MAGIC = d['JSVAL_TYPE_MAGIC'] + self.STRING = d['JSVAL_TYPE_STRING'] + self.SYMBOL = d['JSVAL_TYPE_SYMBOL'] + self.NULL = d['JSVAL_TYPE_NULL'] + self.OBJECT = d['JSVAL_TYPE_OBJECT'] + + # Let self.magic_names be an array whose i'th element is the name of + # the i'th magic value. + d = gdb.types.make_enum_dict(gdb.lookup_type('JSWhyMagic')) + self.magic_names = list(range(max(d.values()) + 1)) + for (k,v) in d.items(): self.magic_names[v] = k + + # Choose an unboxing scheme for this architecture. + self.boxer = Punbox if cache.void_ptr_t.sizeof == 8 else Nunbox + +@pretty_printer('jsval_layout') +class jsval_layout(object): + def __init__(self, value, cache): + # Save the generic typecache, and create our own, if we haven't already. + self.cache = cache + if not cache.mod_jsval: + cache.mod_jsval = jsvalTypeCache(cache) + self.jtc = cache.mod_jsval + + self.value = value + self.box = self.jtc.boxer(value['asBits'], self.jtc) + + def to_string(self): + tag = self.box.tag() + if tag == self.jtc.INT32: + value = self.box.as_uint32() + signbit = 1 << 31 + value = (value ^ signbit) - signbit + elif tag == self.jtc.UNDEFINED: + return 'JSVAL_VOID' + elif tag == self.jtc.BOOLEAN: + return 'JSVAL_TRUE' if self.box.as_uint32() else 'JSVAL_FALSE' + elif tag == self.jtc.MAGIC: + value = self.box.as_uint32() + if 0 <= value and value < len(self.jtc.magic_names): + return '$jsmagic(%s)' % (self.jtc.magic_names[value],) + else: + return '$jsmagic(%d)' % (value,) + elif tag == self.jtc.STRING: + value = self.box.as_address().cast(self.cache.JSString_ptr_t) + elif tag == self.jtc.SYMBOL: + value = self.box.as_address().cast(self.cache.JSSymbol_ptr_t) + elif tag == self.jtc.NULL: + return 'JSVAL_NULL' + elif tag == self.jtc.OBJECT: + value = self.box.as_address().cast(self.cache.JSObject_ptr_t) + elif tag == self.jtc.DOUBLE: + value = self.value['asDouble'] + else: + return '$jsval(unrecognized!)' + return '$jsval(%s)' % (value,) + +@pretty_printer('JS::Value') +class JSValue(object): + def __new__(cls, value, cache): + return jsval_layout(value['data'], cache) diff --git a/js/src/gdb/mozilla/prettyprinters.py b/js/src/gdb/mozilla/prettyprinters.py new file mode 100644 index 000000000..87035d0ef --- /dev/null +++ b/js/src/gdb/mozilla/prettyprinters.py @@ -0,0 +1,368 @@ +# mozilla/prettyprinters.py --- infrastructure for SpiderMonkey's auto-loaded pretty-printers. + +import gdb +import re + +# Decorators for declaring pretty-printers. +# +# In each case, the decoratee should be a SpiderMonkey-style pretty-printer +# factory, taking both a gdb.Value instance and a TypeCache instance as +# arguments; see TypeCache, below. + +# Check that |fn| hasn't been registered as a pretty-printer under some +# other name already. (The 'enabled' flags used by GDB's +# 'enable/disable/info pretty-printer' commands are simply stored as +# properties of the function objects themselves, so a single function +# object can't carry the 'enabled' flags for two different printers.) +def check_for_reused_pretty_printer(fn): + if hasattr(fn, 'enabled'): + raise RuntimeError("pretty-printer function %r registered more than once" % fn) + +# a dictionary mapping gdb.Type tags to pretty-printer functions. +printers_by_tag = {} + +# A decorator: add the decoratee as a pretty-printer lookup function for types +# named |type_name|. +def pretty_printer(type_name): + def add(fn): + check_for_reused_pretty_printer(fn) + add_to_subprinter_list(fn, type_name) + printers_by_tag[type_name] = fn + return fn + return add + +# a dictionary mapping gdb.Type tags to pretty-printer functions for pointers to +# that type. +ptr_printers_by_tag = {} + +# A decorator: add the decoratee as a pretty-printer lookup function for +# pointers to types named |type_name|. +def ptr_pretty_printer(type_name): + def add(fn): + check_for_reused_pretty_printer(fn) + add_to_subprinter_list(fn, "ptr-to-" + type_name) + ptr_printers_by_tag[type_name] = fn + return fn + return add + +# a dictionary mapping gdb.Type tags to pretty-printer functions for +# references to that type. +ref_printers_by_tag = {} + +# A decorator: add the decoratee as a pretty-printer lookup function for +# references to instances of types named |type_name|. +def ref_pretty_printer(type_name): + def add(fn): + check_for_reused_pretty_printer(fn) + add_to_subprinter_list(fn, "ref-to-" + type_name) + ref_printers_by_tag[type_name] = fn + return fn + return add + +# a dictionary mapping the template name portion of gdb.Type tags to +# pretty-printer functions for instantiations of that template. +template_printers_by_tag = {} + +# A decorator: add the decoratee as a pretty-printer lookup function for +# instantiations of templates named |template_name|. +def template_pretty_printer(template_name): + def add(fn): + check_for_reused_pretty_printer(fn) + add_to_subprinter_list(fn, 'instantiations-of-' + template_name) + template_printers_by_tag[template_name] = fn + return fn + return add + +# A list of (REGEXP, PRINTER) pairs, such that if REGEXP (a RegexObject) +# matches the result of converting a gdb.Value's type to a string, then +# PRINTER is a pretty-printer lookup function that will probably like that +# value. +printers_by_regexp = [] + +# A decorator: add the decoratee as a pretty-printer factory for types +# that, when converted to a string, match |pattern|. Use |name| as the +# pretty-printer's name, when listing, enabling and disabling. +def pretty_printer_for_regexp(pattern, name): + compiled = re.compile(pattern) + def add(fn): + check_for_reused_pretty_printer(fn) + add_to_subprinter_list(fn, name) + printers_by_regexp.append((compiled, fn)) + return fn + return add + +# Forget all pretty-printer lookup functions defined in the module name +# |module_name|, if any exist. Use this at the top of each pretty-printer +# module like this: +# +# clear_module_printers(__name__) +def clear_module_printers(module_name): + global printers_by_tag, ptr_printers_by_tag, ref_printers_by_tag + global template_printers_by_tag, printers_by_regexp + + # Remove all pretty-printers defined in the module named |module_name| + # from d. + def clear_dictionary(d): + # Walk the dictionary, building a list of keys whose entries we + # should remove. (It's not safe to delete entries from a dictionary + # while we're iterating over it.) + to_delete = [] + for (k, v) in d.items(): + if v.__module__ == module_name: + to_delete.append(k) + remove_from_subprinter_list(v) + for k in to_delete: + del d[k] + + clear_dictionary(printers_by_tag) + clear_dictionary(ptr_printers_by_tag) + clear_dictionary(ref_printers_by_tag) + clear_dictionary(template_printers_by_tag) + + # Iterate over printers_by_regexp, deleting entries from the given module. + new_list = [] + for p in printers_by_regexp: + if p.__module__ == module_name: + remove_from_subprinter_list(p) + else: + new_list.append(p) + printers_by_regexp = new_list + +# Our subprinters array. The 'subprinters' attributes of all lookup +# functions returned by lookup_for_objfile point to this array instance, +# which we mutate as subprinters are added and removed. +subprinters = [] + +# Set up the 'name' and 'enabled' attributes on |subprinter|, and add it to our +# list of all SpiderMonkey subprinters. +def add_to_subprinter_list(subprinter, name): + subprinter.name = name + subprinter.enabled = True + subprinters.append(subprinter) + +# Remove |subprinter| from our list of all SpiderMonkey subprinters. +def remove_from_subprinter_list(subprinter): + subprinters.remove(subprinter) + +# An exception class meaning, "This objfile has no SpiderMonkey in it." +class NotSpiderMonkeyObjfileError(TypeError): + pass + +# TypeCache: a cache for frequently used information about an objfile. +# +# When a new SpiderMonkey objfile is loaded, we construct an instance of +# this class for it. Then, whenever we construct a pretty-printer for some +# gdb.Value, we also pass, as a second argument, the TypeCache for the +# objfile to which that value's type belongs. +# +# if objfile doesn't seem to have SpiderMonkey code in it, the constructor +# raises NotSpiderMonkeyObjfileError. +# +# Pretty-printer modules may add attributes to this to hold their own +# cached values. Such attributes should be named mod_NAME, where the module +# is named mozilla.NAME; for example, mozilla.JSString should store its +# metadata in the TypeCache's mod_JSString attribute. +class TypeCache(object): + def __init__(self, objfile): + self.objfile = objfile + + # Unfortunately, the Python interface doesn't allow us to specify + # the objfile in whose scope lookups should occur. But simply + # knowing that we need to lookup the types afresh is probably + # enough. + self.void_t = gdb.lookup_type('void') + self.void_ptr_t = self.void_t.pointer() + try: + self.JSString_ptr_t = gdb.lookup_type('JSString').pointer() + self.JSSymbol_ptr_t = gdb.lookup_type('JS::Symbol').pointer() + self.JSObject_ptr_t = gdb.lookup_type('JSObject').pointer() + except gdb.error: + raise NotSpiderMonkeyObjfileError + + self.mod_GCCellPtr = None + self.mod_Interpreter = None + self.mod_JSObject = None + self.mod_JSString = None + self.mod_jsval = None + self.mod_ExecutableAllocator = None + self.mod_IonGraph = None + +# Yield a series of all the types that |t| implements, by following typedefs +# and iterating over base classes. Specifically: +# - |t| itself is the first value yielded. +# - If we yield a typedef, we later yield its definition. +# - If we yield a type with base classes, we later yield those base classes. +# - If we yield a type with some base classes that are typedefs, +# we yield all the type's base classes before following the typedefs. +# (Actually, this never happens, because G++ doesn't preserve the typedefs in +# the DWARF.) +# +# This is a hokey attempt to order the implemented types by meaningfulness when +# pretty-printed. Perhaps it is entirely misguided, and we should actually +# collect all applicable pretty-printers, and then use some ordering on the +# pretty-printers themselves. +# +# We may yield a type more than once (say, if it appears more than once in the +# class hierarchy). +def implemented_types(t): + + # Yield all types that follow |t|. + def followers(t): + if t.code == gdb.TYPE_CODE_TYPEDEF: + yield t.target() + for t2 in followers(t.target()): yield t2 + elif t.code == gdb.TYPE_CODE_STRUCT: + base_classes = [] + for f in t.fields(): + if f.is_base_class: + yield f.type + base_classes.append(f.type) + for b in base_classes: + for t2 in followers(b): yield t2 + + yield t + for t2 in followers(t): yield t2 + +template_regexp = re.compile("([\w_:]+)<") + +# Construct and return a pretty-printer lookup function for objfile, or +# return None if the objfile doesn't contain SpiderMonkey code +# (specifically, definitions for SpiderMonkey types). +def lookup_for_objfile(objfile): + # Create a type cache for this objfile. + try: + cache = TypeCache(objfile) + except NotSpiderMonkeyObjfileError: + if gdb.parameter("verbose"): + gdb.write("objfile '%s' has no SpiderMonkey code; not registering pretty-printers\n" + % (objfile.filename,)) + return None + + # Return a pretty-printer for |value|, if we have one. This is the lookup + # function object we place in each gdb.Objfile's pretty-printers list, so it + # carries |name|, |enabled|, and |subprinters| attributes. + def lookup(value): + # If |table| has a pretty-printer for |tag|, apply it to |value|. + def check_table(table, tag): + if tag in table: + f = table[tag] + if f.enabled: + return f(value, cache) + return None + + def check_table_by_type_name(table, t): + if t.code == gdb.TYPE_CODE_TYPEDEF: + return check_table(table, str(t)) + elif t.code == gdb.TYPE_CODE_STRUCT and t.tag: + return check_table(table, t.tag) + else: + return None + + for t in implemented_types(value.type): + if t.code == gdb.TYPE_CODE_PTR: + for t2 in implemented_types(t.target()): + p = check_table_by_type_name(ptr_printers_by_tag, t2) + if p: return p + elif t.code == gdb.TYPE_CODE_REF: + for t2 in implemented_types(t.target()): + p = check_table_by_type_name(ref_printers_by_tag, t2) + if p: return p + else: + p = check_table_by_type_name(printers_by_tag, t) + if p: return p + if t.code == gdb.TYPE_CODE_STRUCT and t.tag: + m = template_regexp.match(t.tag) + if m: + p = check_table(template_printers_by_tag, m.group(1)) + if p: return p + + # Failing that, look for a printer in printers_by_regexp. We have + # to scan the whole list, so regexp printers should be used + # sparingly. + s = str(value.type) + for (r, f) in printers_by_regexp: + if f.enabled: + m = r.match(s) + if m: + p = f(value, cache) + if p: return p + + # No luck. + return None + + # Give |lookup| the attributes expected of a pretty-printer with + # subprinters, for enabling and disabling. + lookup.name = "SpiderMonkey" + lookup.enabled = True + lookup.subprinters = subprinters + + return lookup + +# A base class for pretty-printers for pointer values that handles null +# pointers, by declining to construct a pretty-printer for them at all. +# Derived classes may simply assume that self.value is non-null. +# +# To help share code, this class can also be used with reference types. +# +# This class provides the following methods, which subclasses are free to +# override: +# +# __init__(self, value, cache): Save value and cache as properties by those names +# on the instance. +# +# to_string(self): format the type's name and address, as GDB would, and then +# call a 'summary' method (which the subclass must define) to produce a +# description of the referent. +# +# Note that pretty-printers returning a 'string' display hint must not use +# this default 'to_string' method, as GDB will take everything it returns, +# including the type name and address, as string contents. +class Pointer(object): + def __new__(cls, value, cache): + # Don't try to provide pretty-printers for NULL pointers. + if value.type.strip_typedefs().code == gdb.TYPE_CODE_PTR and value == 0: + return None + return super(Pointer, cls).__new__(cls) + + def __init__(self, value, cache): + self.value = value + self.cache = cache + + def to_string(self): + # See comment above. + assert not hasattr(self, 'display_hint') or self.display_hint() != 'string' + concrete_type = self.value.type.strip_typedefs() + if concrete_type.code == gdb.TYPE_CODE_PTR: + address = self.value.cast(self.cache.void_ptr_t) + elif concrete_type.code == gdb.TYPE_CODE_REF: + address = '@' + str(self.value.address.cast(self.cache.void_ptr_t)) + else: + assert not "mozilla.prettyprinters.Pointer applied to bad value type" + try: + summary = self.summary() + except gdb.MemoryError as r: + summary = str(r) + v = '(%s) %s %s' % (self.value.type, address, summary) + return v + + def summary(self): + raise NotImplementedError + +field_enum_value = None + +# Given |t|, a gdb.Type instance representing an enum type, return the +# numeric value of the enum value named |name|. +# +# Pre-2012-4-18 versions of GDB store the value of an enum member on the +# gdb.Field's 'bitpos' attribute; later versions store it on the 'enumval' +# attribute. This function retrieves the value from either. +def enum_value(t, name): + global field_enum_value + f = t[name] + # Monkey-patching is a-okay in polyfills! Just because. + if not field_enum_value: + if hasattr(f, 'enumval'): + field_enum_value = lambda f: f.enumval + else: + field_enum_value = lambda f: f.bitpos + return field_enum_value(f) diff --git a/js/src/gdb/mozilla/unwind.py b/js/src/gdb/mozilla/unwind.py new file mode 100644 index 000000000..adea63ea6 --- /dev/null +++ b/js/src/gdb/mozilla/unwind.py @@ -0,0 +1,591 @@ +# mozilla/unwind.py --- unwinder and frame filter for SpiderMonkey + +import gdb +import gdb.types +from gdb.FrameDecorator import FrameDecorator +import re +import platform +from mozilla.ExecutableAllocator import jsjitExecutableAllocatorCache, jsjitExecutableAllocator + +# For ease of use in Python 2, we use "long" instead of "int" +# everywhere. +try: + long +except NameError: + long = int + +# The Python 3 |map| built-in works lazily, but in Python 2 we need +# itertools.imap to get this. +try: + from itertools import imap +except ImportError: + imap = map + +_have_unwinder = True +try: + from gdb.unwinder import Unwinder +except ImportError: + _have_unwinder = False + # We need something here; it doesn't matter what as no unwinder + # will ever be instantiated. + Unwinder = object + +def debug(something): + # print("@@ " + something) + pass + +# Maps frametype enum base names to corresponding class. +SizeOfFramePrefix = { + 'JitFrame_IonJS': 'ExitFrameLayout', + 'JitFrame_BaselineJS': 'JitFrameLayout', + 'JitFrame_BaselineStub': 'BaselineStubFrameLayout', + 'JitFrame_IonStub': 'JitStubFrameLayout', + # Technically EntryFrameLayout, but that doesn't wind up in the + # debuginfo because there are no uses of it. + 'JitFrame_Entry': 'JitFrameLayout', + 'JitFrame_Rectifier': 'RectifierFrameLayout', + 'JitFrame_IonAccessorIC': 'IonAccessorICFrameLayout', + 'JitFrame_Exit': 'ExitFrameLayout', + 'JitFrame_Bailout': 'JitFrameLayout', +} + +# All types and symbols that we need are attached to an object that we +# can dispose of as needed. +class UnwinderTypeCache(object): + def __init__(self): + self.d = None + self.frame_enum_names = {} + self.frame_class_types = {} + + # We take this bizarre approach to defer trying to look up any + # symbols until absolutely needed. Without this, the loading + # approach taken by the gdb-tests would cause spurious exceptions. + def __getattr__(self, name): + if self.d is None: + self.initialize() + return self.d[name] + + def value(self, name): + return long(gdb.parse_and_eval('js::jit::' + name)) + + def initialize(self): + self.d = {} + self.d['FRAMETYPE_MASK'] = (1 << self.value('FRAMETYPE_BITS')) - 1 + self.d['FRAMESIZE_SHIFT'] = self.value('FRAMESIZE_SHIFT') + self.d['FRAME_HEADER_SIZE_SHIFT'] = self.value('FRAME_HEADER_SIZE_SHIFT') + self.d['FRAME_HEADER_SIZE_MASK'] = self.value('FRAME_HEADER_SIZE_MASK') + + self.compute_frame_info() + commonFrameLayout = gdb.lookup_type('js::jit::CommonFrameLayout') + self.d['typeCommonFrameLayout'] = commonFrameLayout + self.d['typeCommonFrameLayoutPointer'] = commonFrameLayout.pointer() + self.d['per_tls_data'] = gdb.lookup_global_symbol('js::TlsPerThreadData') + + self.d['void_starstar'] = gdb.lookup_type('void').pointer().pointer() + self.d['mod_ExecutableAllocator'] = jsjitExecutableAllocatorCache() + + jitframe = gdb.lookup_type("js::jit::JitFrameLayout") + self.d['jitFrameLayoutPointer'] = jitframe.pointer() + + self.d['CalleeToken_Function'] = self.value("CalleeToken_Function") + self.d['CalleeToken_FunctionConstructing'] = self.value("CalleeToken_FunctionConstructing") + self.d['CalleeToken_Script'] = self.value("CalleeToken_Script") + self.d['JSFunction'] = gdb.lookup_type("JSFunction").pointer() + self.d['JSScript'] = gdb.lookup_type("JSScript").pointer() + self.d['Value'] = gdb.lookup_type("JS::Value") + + self.d['SOURCE_SLOT'] = long(gdb.parse_and_eval('js::ScriptSourceObject::SOURCE_SLOT')) + self.d['NativeObject'] = gdb.lookup_type("js::NativeObject").pointer() + self.d['HeapSlot'] = gdb.lookup_type("js::HeapSlot").pointer() + self.d['ScriptSource'] = gdb.lookup_type("js::ScriptSource").pointer() + + # Compute maps related to jit frames. + def compute_frame_info(self): + t = gdb.lookup_type('enum js::jit::FrameType') + for field in t.fields(): + # Strip off "js::jit::". + name = field.name[9:] + enumval = long(field.enumval) + self.d[name] = enumval + self.frame_enum_names[enumval] = name + class_type = gdb.lookup_type('js::jit::' + SizeOfFramePrefix[name]) + self.frame_class_types[enumval] = class_type.pointer() + +# gdb doesn't have a direct way to tell us if a given address is +# claimed by some shared library or the executable. See +# https://sourceware.org/bugzilla/show_bug.cgi?id=19288 +# In the interest of not requiring a patched gdb, instead we read +# /proc/.../maps. This only works locally, but maybe could work +# remotely using "remote get". FIXME. +def parse_proc_maps(): + mapfile = '/proc/' + str(gdb.selected_inferior().pid) + '/maps' + # Note we only examine executable mappings here. + matcher = re.compile("^([a-fA-F0-9]+)-([a-fA-F0-9]+)\s+..x.\s+\S+\s+\S+\s+\S*(.*)$") + mappings = [] + with open(mapfile, "r") as inp: + for line in inp: + match = matcher.match(line) + if not match: + # Header lines and such. + continue + start = match.group(1) + end = match.group(2) + name = match.group(3).strip() + if name is '' or (name.startswith('[') and name is not '[vdso]'): + # Skip entries not corresponding to a file. + continue + mappings.append((long(start, 16), long(end, 16))) + return mappings + +# A symbol/value pair as expected from gdb frame decorators. +class FrameSymbol(object): + def __init__(self, sym, val): + self.sym = sym + self.val = val + + def symbol(self): + return self.sym + + def value(self): + return self.val + +# This represents a single JIT frame for the purposes of display. +# That is, the frame filter creates instances of this when it sees a +# JIT frame in the stack. +class JitFrameDecorator(FrameDecorator): + def __init__(self, base, info, cache): + super(JitFrameDecorator, self).__init__(base) + self.info = info + self.cache = cache + + def _decode_jitframe(self, this_frame): + calleetoken = long(this_frame['calleeToken_']) + tag = calleetoken & 3 + calleetoken = calleetoken ^ tag + function = None + script = None + if tag == self.cache.CalleeToken_Function or tag == self.cache.CalleeToken_FunctionConstructing: + fptr = gdb.Value(calleetoken).cast(self.cache.JSFunction) + try: + atom = fptr['atom_'] + if atom: + function = str(atom) + except gdb.MemoryError: + function = "(could not read function name)" + script = fptr['u']['i']['s']['script_'] + elif tag == self.cache.CalleeToken_Script: + script = gdb.Value(calleetoken).cast(self.cache.JSScript) + return {"function": function, "script": script} + + def function(self): + if self.info["name"] is None: + return FrameDecorator.function(self) + name = self.info["name"] + result = "<<" + name + # If we have a frame, we can extract the callee information + # from it for display here. + this_frame = self.info["this_frame"] + if this_frame is not None: + if gdb.types.has_field(this_frame.type.target(), "calleeToken_"): + function = self._decode_jitframe(this_frame)["function"] + if function is not None: + result = result + " " + function + return result + ">>" + + def filename(self): + this_frame = self.info["this_frame"] + if this_frame is not None: + if gdb.types.has_field(this_frame.type.target(), "calleeToken_"): + script = self._decode_jitframe(this_frame)["script"] + if script is not None: + obj = script['sourceObject_']['value'] + # Verify that this is a ScriptSource object. + # FIXME should also deal with wrappers here. + nativeobj = obj.cast(self.cache.NativeObject) + # See bug 987069 and despair. At least this + # approach won't give exceptions. + class_name = nativeobj['group_']['value']['clasp_']['name'].string("ISO-8859-1") + if class_name != "ScriptSource": + return FrameDecorator.filename(self) + scriptsourceobj = (nativeobj + 1).cast(self.cache.HeapSlot)[self.cache.SOURCE_SLOT] + scriptsource = scriptsourceobj['value']['data']['asBits'] << 1 + scriptsource = scriptsource.cast(self.cache.ScriptSource) + return scriptsource['filename_']['mTuple']['mFirstA'].string() + return FrameDecorator.filename(self) + + def frame_args(self): + this_frame = self.info["this_frame"] + if this_frame is None: + return FrameDecorator.frame_args(self) + if not gdb.types.has_field(this_frame.type.target(), "numActualArgs_"): + return FrameDecorator.frame_args(self) + # See if this is a function call. + if self._decode_jitframe(this_frame)["function"] is None: + return FrameDecorator.frame_args(self) + # Construct and return an iterable of all the arguments. + result = [] + num_args = long(this_frame["numActualArgs_"]) + # Sometimes we see very large values here, so truncate it to + # bypass the damage. + if num_args > 10: + num_args = 10 + args_ptr = (this_frame + 1).cast(self.cache.Value.pointer()) + for i in range(num_args + 1): + # Synthesize names, since there doesn't seem to be + # anything better to do. + if i == 0: + name = 'this' + else: + name = 'arg%d' % i + result.append(FrameSymbol(name, args_ptr[i])) + return result + +# A frame filter for SpiderMonkey. +class SpiderMonkeyFrameFilter(object): + # |state_holder| is either None, or an instance of + # SpiderMonkeyUnwinder. If the latter, then this class will + # reference the |unwinder_state| attribute to find the current + # unwinder state. + def __init__(self, cache, state_holder): + self.name = "SpiderMonkey" + self.enabled = True + self.priority = 100 + self.state_holder = state_holder + self.cache = cache + + def maybe_wrap_frame(self, frame): + if self.state_holder is None or self.state_holder.unwinder_state is None: + return frame + base = frame.inferior_frame() + info = self.state_holder.unwinder_state.get_frame(base) + if info is None: + return frame + return JitFrameDecorator(frame, info, self.cache) + + def filter(self, frame_iter): + return imap(self.maybe_wrap_frame, frame_iter) + +# A frame id class, as specified by the gdb unwinder API. +class SpiderMonkeyFrameId(object): + def __init__(self, sp, pc): + self.sp = sp + self.pc = pc + +# This holds all the state needed during a given unwind. Each time a +# new unwind is done, a new instance of this class is created. It +# keeps track of all the state needed to unwind JIT frames. Note that +# this class is not directly instantiated. +# +# This is a base class, and must be specialized for each target +# architecture, both because we need to use arch-specific register +# names, and because entry frame unwinding is arch-specific. +# See https://sourceware.org/bugzilla/show_bug.cgi?id=19286 for info +# about the register name issue. +# +# Each subclass must define SP_REGISTER, PC_REGISTER, and +# SENTINEL_REGISTER (see x64UnwinderState for info); and implement +# unwind_entry_frame_registers. +class UnwinderState(object): + def __init__(self, typecache): + self.next_sp = None + self.next_type = None + self.activation = None + # An unwinder instance is specific to a thread. Record the + # selected thread for later verification. + self.thread = gdb.selected_thread() + self.frame_map = {} + self.proc_mappings = None + try: + self.proc_mappings = parse_proc_maps() + except IOError: + pass + self.typecache = typecache + + # If the given gdb.Frame was created by this unwinder, return the + # corresponding informational dictionary for the frame. + # Otherwise, return None. This is used by the frame filter to + # display extra information about the frame. + def get_frame(self, frame): + sp = long(frame.read_register(self.SP_REGISTER)) + if sp in self.frame_map: + return self.frame_map[sp] + return None + + # Add information about a frame to the frame map. This map is + # queried by |self.get_frame|. |sp| is the frame's stack pointer, + # and |name| the frame's type as a string, e.g. "JitFrame_Exit". + def add_frame(self, sp, name = None, this_frame = None): + self.frame_map[long(sp)] = { "name": name, "this_frame": this_frame } + + # See whether |pc| is claimed by some text mapping. See + # |parse_proc_maps| for details on how the decision is made. + def text_address_claimed(self, pc): + for (start, end) in self.proc_mappings: + if (pc >= start and pc <= end): + return True + return False + + # See whether |pc| is claimed by the Jit. + def is_jit_address(self, pc): + if self.proc_mappings != None: + return not self.text_address_claimed(pc) + + ptd = self.get_tls_per_thread_data() + runtime = ptd['runtime_'] + if runtime == 0: + return False + + jitRuntime = runtime['jitRuntime_'] + if jitRuntime == 0: + return False + + execAllocators = [jitRuntime['execAlloc_'], jitRuntime['backedgeExecAlloc_']] + for execAlloc in execAllocators: + for pool in jsjitExecutableAllocator(execAlloc, self.typecache): + pages = pool['m_allocation']['pages'] + size = pool['m_allocation']['size'] + if pages <= pc and pc < pages + size: + return True + return False + + # Check whether |self| is valid for the selected thread. + def check(self): + return gdb.selected_thread() is self.thread + + # Essentially js::TlsPerThreadData.get(). + def get_tls_per_thread_data(self): + return self.typecache.per_tls_data.value()['mValue'] + + # |common| is a pointer to a CommonFrameLayout object. Return a + # tuple (local_size, header_size, frame_type), where |size| is the + # integer size of the previous frame's locals; |header_size| is + # the size of this frame's header; and |frame_type| is an integer + # representing the previous frame's type. + def unpack_descriptor(self, common): + value = long(common['descriptor_']) + local_size = value >> self.typecache.FRAMESIZE_SHIFT + header_size = ((value >> self.typecache.FRAME_HEADER_SIZE_SHIFT) & + self.typecache.FRAME_HEADER_SIZE_MASK) + header_size = header_size * self.typecache.void_starstar.sizeof + frame_type = long(value & self.typecache.FRAMETYPE_MASK) + if frame_type == self.typecache.JitFrame_Entry: + # Trampoline-x64.cpp pushes a JitFrameLayout object, but + # the stack pointer is actually adjusted as if a + # CommonFrameLayout object was pushed. + header_size = self.typecache.typeCommonFrameLayout.sizeof + return (local_size, header_size, frame_type) + + # Create a new frame for gdb. This makes a new unwind info object + # and fills it in, then returns it. It also registers any + # pertinent information with the frame filter for later display. + # + # |pc| is the PC from the pending frame + # |sp| is the stack pointer to use + # |frame| points to the CommonFrameLayout object + # |frame_type| is a integer, one of the |enum FrameType| values, + # describing the current frame. + # |pending_frame| is the pending frame (see the gdb unwinder + # documentation). + def create_frame(self, pc, sp, frame, frame_type, pending_frame): + # Make a frame_id that claims that |frame| is sort of like a + # frame pointer for this frame. + frame_id = SpiderMonkeyFrameId(frame, pc) + + # Read the frame layout object to find the next such object. + # This lets us unwind the necessary registers for the next + # frame, and also update our internal state to match. + common = frame.cast(self.typecache.typeCommonFrameLayoutPointer) + next_pc = common['returnAddress_'] + (local_size, header_size, next_type) = self.unpack_descriptor(common) + next_sp = frame + header_size + local_size + + # Compute the type of the next oldest frame's descriptor. + this_class_type = self.typecache.frame_class_types[frame_type] + this_frame = frame.cast(this_class_type) + + # Register this frame so the frame filter can find it. This + # is registered using SP because we don't have any other good + # approach -- you can't get the frame id from a gdb.Frame. + # https://sourceware.org/bugzilla/show_bug.cgi?id=19800 + frame_name = self.typecache.frame_enum_names[frame_type] + self.add_frame(sp, name = frame_name, this_frame = this_frame) + + # Update internal state for the next unwind. + self.next_sp = next_sp + self.next_type = next_type + + unwind_info = pending_frame.create_unwind_info(frame_id) + unwind_info.add_saved_register(self.PC_REGISTER, next_pc) + unwind_info.add_saved_register(self.SP_REGISTER, next_sp) + # FIXME it would be great to unwind any other registers here. + return unwind_info + + # Unwind an "ordinary" JIT frame. This is used for JIT frames + # other than enter and exit frames. Returns the newly-created + # unwind info for gdb. + def unwind_ordinary(self, pc, pending_frame): + return self.create_frame(pc, self.next_sp, self.next_sp, + self.next_type, pending_frame) + + # Unwind an exit frame. Returns None if this cannot be done; + # otherwise returns the newly-created unwind info for gdb. + def unwind_exit_frame(self, pc, pending_frame): + if self.activation == 0: + # Reached the end of the list. + return None + elif self.activation is None: + ptd = self.get_tls_per_thread_data() + self.activation = ptd['runtime_']['jitActivation'] + jittop = ptd['runtime_']['jitTop'] + else: + jittop = self.activation['prevJitTop_'] + self.activation = self.activation['prevJitActivation_'] + + if jittop == 0: + return None + + exit_sp = pending_frame.read_register(self.SP_REGISTER) + frame_type = self.typecache.JitFrame_Exit + return self.create_frame(pc, exit_sp, jittop, frame_type, pending_frame) + + # A wrapper for unwind_entry_frame_registers that handles + # architecture-independent boilerplate. + def unwind_entry_frame(self, pc, pending_frame): + sp = self.next_sp + # Notify the frame filter. + self.add_frame(sp, name = 'JitFrame_Entry') + # Make an unwind_info for the per-architecture code to fill in. + frame_id = SpiderMonkeyFrameId(sp, pc) + unwind_info = pending_frame.create_unwind_info(frame_id) + self.unwind_entry_frame_registers(sp, unwind_info) + self.next_sp = None + self.next_type = None + return unwind_info + + # The main entry point that is called to try to unwind a JIT frame + # of any type. Returns None if this cannot be done; otherwise + # returns the newly-created unwind info for gdb. + def unwind(self, pending_frame): + pc = pending_frame.read_register(self.PC_REGISTER) + + # If the jit does not claim this address, bail. GDB defers to our + # unwinder by default, but we don't really want that kind of power. + if not self.is_jit_address(long(pc)): + return None + + if self.next_sp is not None: + if self.next_type == self.typecache.JitFrame_Entry: + return self.unwind_entry_frame(pc, pending_frame) + return self.unwind_ordinary(pc, pending_frame) + # Maybe we've found an exit frame. FIXME I currently don't + # know how to identify these precisely, so we'll just hope for + # the time being. + return self.unwind_exit_frame(pc, pending_frame) + +# The UnwinderState subclass for x86-64. +class x64UnwinderState(UnwinderState): + SP_REGISTER = 'rsp' + PC_REGISTER = 'rip' + + # A register unique to this architecture, that is also likely to + # have been saved in any frame. The best thing to use here is + # some arch-specific name for PC or SP. + SENTINEL_REGISTER = 'rip' + + # Must be in sync with Trampoline-x64.cpp:generateEnterJIT. Note + # that rip isn't pushed there explicitly, but rather by the + # previous function's call. + PUSHED_REGS = ["r15", "r14", "r13", "r12", "rbx", "rbp", "rip"] + + # Fill in the unwound registers for an entry frame. + def unwind_entry_frame_registers(self, sp, unwind_info): + sp = sp.cast(self.typecache.void_starstar) + # Skip the "result" push. + sp = sp + 1 + for reg in self.PUSHED_REGS: + data = sp.dereference() + sp = sp + 1 + unwind_info.add_saved_register(reg, data) + if reg is "rbp": + unwind_info.add_saved_register(self.SP_REGISTER, sp) + +# The unwinder object. This provides the "user interface" to the JIT +# unwinder, and also handles constructing or destroying UnwinderState +# objects as needed. +class SpiderMonkeyUnwinder(Unwinder): + # A list of all the possible unwinders. See |self.make_unwinder|. + UNWINDERS = [x64UnwinderState] + + def __init__(self, typecache): + super(SpiderMonkeyUnwinder, self).__init__("SpiderMonkey") + self.typecache = typecache + self.unwinder_state = None + + # Disabled by default until we figure out issues in gdb. + self.enabled = False + gdb.write("SpiderMonkey unwinder is disabled by default, to enable it type:\n" + + "\tenable unwinder .* SpiderMonkey\n") + # Some versions of gdb did not flush the internal frame cache + # when enabling or disabling an unwinder. This was fixed in + # the same release of gdb that added the breakpoint_created + # event. + if not hasattr(gdb.events, "breakpoint_created"): + gdb.write("\tflushregs\n") + + # We need to invalidate the unwinder state whenever the + # inferior starts executing. This avoids having a stale + # cache. + gdb.events.cont.connect(self.invalidate_unwinder_state) + assert self.test_sentinels() + + def test_sentinels(self): + # Self-check. + regs = {} + for unwinder in self.UNWINDERS: + if unwinder.SENTINEL_REGISTER in regs: + return False + regs[unwinder.SENTINEL_REGISTER] = 1 + return True + + def make_unwinder(self, pending_frame): + # gdb doesn't provide a good way to find the architecture. + # See https://sourceware.org/bugzilla/show_bug.cgi?id=19399 + # So, we look at each known architecture and see if the + # corresponding "unique register" is known. + for unwinder in self.UNWINDERS: + try: + pending_frame.read_register(unwinder.SENTINEL_REGISTER) + except: + # Failed to read the register, so let's keep going. + # This is more fragile than it might seem, because it + # fails if the sentinel register wasn't saved in the + # previous frame. + continue + return unwinder(self.typecache) + return None + + def __call__(self, pending_frame): + if self.unwinder_state is None or not self.unwinder_state.check(): + self.unwinder_state = self.make_unwinder(pending_frame) + if not self.unwinder_state: + return None + return self.unwinder_state.unwind(pending_frame) + + def invalidate_unwinder_state(self, *args, **kwargs): + self.unwinder_state = None + +# Register the unwinder and frame filter with |objfile|. If |objfile| +# is None, register them globally. +def register_unwinder(objfile): + type_cache = UnwinderTypeCache() + unwinder = None + # This currently only works on Linux, due to parse_proc_maps. + if _have_unwinder and platform.system() == "Linux": + unwinder = SpiderMonkeyUnwinder(type_cache) + gdb.unwinder.register_unwinder(objfile, unwinder, replace=True) + # We unconditionally register the frame filter, because at some + # point we'll add interpreter frame filtering. + filt = SpiderMonkeyFrameFilter(type_cache, unwinder) + if objfile is None: + objfile = gdb + objfile.frame_filters[filt.name] = filt diff --git a/js/src/gdb/progressbar.py b/js/src/gdb/progressbar.py new file mode 100644 index 000000000..d96a67c96 --- /dev/null +++ b/js/src/gdb/progressbar.py @@ -0,0 +1,48 @@ +# 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 sys, datetime, 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 000000000..1e4f4783b --- /dev/null +++ b/js/src/gdb/run-tests.py @@ -0,0 +1,349 @@ +#!/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, re, subprocess, sys, traceback +from threading import Thread + +# From this directory: +import progressbar +from taskpool import TaskPool, get_cpu_count + +# Backported from Python 3.1 posixpath.py +def _relpath(path, start=None): + """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) 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.builddir,), + '--ex', 'set env LD_LIBRARY_PATH %s' % os.path.join(OPTIONS.objdir, 'js', 'src'), + '--ex', 'file %s' % (os.path.join(OPTIONS.builddir, '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): + print "Command: ", 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: + out.write('Command: %s\n' % (make_shell_cmd(self.cmd()),)) + self.show_output(out) + out.write('GDB exit code: %r\n' % (self.returncode,)) + +def find_tests(dir, substring = None): + ans = [] + for dirpath, dirnames, 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): + p = subprocess.check_call(['make', 'gdb-tests'], cwd=builddir) + +def run_tests(tests, summary): + pool = TaskPool(tests, job_limit=OPTIONS.workercount, 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 in [BUILDDIR].') + (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 default for OPTIONS.builddir now, since we've computed OPTIONS.objdir. + if not OPTIONS.builddir: + OPTIONS.builddir = os.path.join(OPTIONS.objdir, 'js', 'src', 'gdb') + + 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(test_dir, 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(test_dir, 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(test_dir, 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 000000000..8bfcb3a34 --- /dev/null +++ b/js/src/gdb/taskpool.py @@ -0,0 +1,219 @@ +import fcntl, os, select, time +from subprocess import Popen, PIPE + +# 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. +class TaskPool(object): + + # 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 = self.get_next_pending() + + # Set self.next_pending to the next task that has not yet been executed. + def get_next_pending(self): + try: + return self.pending.next() + except StopIteration: + return 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: + t = self.next_pending + p = Popen(t.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) + + t.start(p, time.time() + self.timeout) + running.add(t) + self.next_pending = self.get_next_pending() + + # 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 output != "": + try: + t.onStdout(output) + except TerminateTask: + terminate.add(t) + if t.pipe.stderr in readable: + output = t.pipe.stderr.read(16384) + if output != "": + try: + t.onStderr(output) + 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/test-ExecutableAllocator.cpp b/js/src/gdb/tests/test-ExecutableAllocator.cpp new file mode 100644 index 000000000..7cb7ad364 --- /dev/null +++ b/js/src/gdb/tests/test-ExecutableAllocator.cpp @@ -0,0 +1,44 @@ +#include "gdb-tests.h" +#include "jsapi.h" + +#include "jscntxt.h" + +#include "jit/ExecutableAllocator.h" + +FRAGMENT(ExecutableAllocator, empty) { + using namespace js::jit; + ExecutableAllocator execAlloc(cx->runtime()); + + breakpoint(); + + (void) execAlloc; +} + +FRAGMENT(ExecutableAllocator, onepool) { + using namespace js::jit; + ExecutablePool* pool = nullptr; + ExecutableAllocator execAlloc(cx->runtime()); + execAlloc.alloc(16 * 1024, &pool, BASELINE_CODE); + + breakpoint(); + + (void) pool; + (void) execAlloc; +} + +FRAGMENT(ExecutableAllocator, twopools) { + using namespace js::jit; + ExecutablePool* init = nullptr; + ExecutablePool* pool = nullptr; + ExecutableAllocator execAlloc(cx->runtime()); + + execAlloc.alloc(16 * 1024, &init, BASELINE_CODE); + + do { // Keep allocating until we get a second pool. + execAlloc.alloc(32 * 1024, &pool, ION_CODE); + } while (pool == init); + + breakpoint(); + + (void) execAlloc; +} diff --git a/js/src/gdb/tests/test-ExecutableAllocator.py b/js/src/gdb/tests/test-ExecutableAllocator.py new file mode 100644 index 000000000..b790e2d34 --- /dev/null +++ b/js/src/gdb/tests/test-ExecutableAllocator.py @@ -0,0 +1,17 @@ +# Tests for ExecutableAllocator pretty-printing + +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 000000000..cc5802dbe --- /dev/null +++ b/js/src/gdb/tests/test-GCCellPtr.cpp @@ -0,0 +1,23 @@ +#include "gdb-tests.h" +#include "jsapi.h" + +#include "js/HeapAPI.h" + +FRAGMENT(GCCellPtr, simple) { + JS::GCCellPtr nulll(nullptr); + + JS::Rooted glob(cx, JS::CurrentGlobalOrNull(cx)); + JS::Rooted empty(cx, JS_NewStringCopyN(cx, nullptr, 0)); + JS::Rooted unique(cx, JS::NewSymbol(cx, nullptr)); + + JS::GCCellPtr object(glob.get()); + JS::GCCellPtr string(empty.get()); + JS::GCCellPtr symbol(unique.get()); + + breakpoint(); + + (void) nulll; + (void) object; + (void) string; + (void) symbol; +} diff --git a/js/src/gdb/tests/test-GCCellPtr.py b/js/src/gdb/tests/test-GCCellPtr.py new file mode 100644 index 000000000..d5bb3d1e1 --- /dev/null +++ b/js/src/gdb/tests/test-GCCellPtr.py @@ -0,0 +1,10 @@ +# Tests for GCCellPtr pretty-printing + +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*) )') diff --git a/js/src/gdb/tests/test-Interpreter.cpp b/js/src/gdb/tests/test-Interpreter.cpp new file mode 100644 index 000000000..9e206ff6a --- /dev/null +++ b/js/src/gdb/tests/test-Interpreter.cpp @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jsapi.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, void* ptr) +{ + MOZ_ASSERT((uintptr_t(ptr) & AbstractFramePtr::TagMask) == 0); + frame.ptr_ = uintptr_t(ptr) | AbstractFramePtr::Tag_ScriptFrameIterData; +} + +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; +} + +} // namespace js + +FRAGMENT(Interpreter, Regs) { + struct FakeFrame { + js::InterpreterFrame frame; + JS::Value slot0; + JS::Value slot1; + JS::Value slot2; + } fakeFrame; + uint8_t fakeOpcode = JSOP_IFEQ; + + js::InterpreterRegs regs; + js::GDBTestInitInterpreterRegs(regs, &fakeFrame.frame, &fakeFrame.slot2, &fakeOpcode); + + breakpoint(); + + (void) regs; +} + +FRAGMENT(Interpreter, AbstractFramePtr) { + + js::AbstractFramePtr sfidptr; + GDBTestInitAbstractFramePtr(sfidptr, (js::ScriptFrameIter::Data*) uintptr_t(0xdeeb0)); + + 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)); + + breakpoint(); + + (void) sfidptr; + (void) ifptr; + (void) bfptr; + (void) rfptr; +} diff --git a/js/src/gdb/tests/test-Interpreter.py b/js/src/gdb/tests/test-Interpreter.py new file mode 100644 index 000000000..c1cf87c66 --- /dev/null +++ b/js/src/gdb/tests/test-Interpreter.py @@ -0,0 +1,14 @@ +# Test printing interpreter internal data structures. + +assert_subprinter_registered('SpiderMonkey', 'js::InterpreterRegs') + +run_fragment('Interpreter.Regs') + +assert_pretty('regs', '{ fp_ = , sp = fp_.slots() + 2, pc = (JSOP_IFEQ) }') + +run_fragment('Interpreter.AbstractFramePtr') + +assert_pretty('sfidptr', 'AbstractFramePtr ((js::ScriptFrameIter::Data *) ) = {ptr_ = 913072}') +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 000000000..40721b2e2 --- /dev/null +++ b/js/src/gdb/tests/test-JSObject-null.py @@ -0,0 +1,6 @@ +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 000000000..2be10e89c --- /dev/null +++ b/js/src/gdb/tests/test-JSObject.cpp @@ -0,0 +1,43 @@ +#include "gdb-tests.h" +#include "jsapi.h" + +FRAGMENT(JSObject, simple) { + JS::Rooted glob(cx, JS::CurrentGlobalOrNull(cx)); + JS::Rooted plain(cx, JS_NewPlainObject(cx)); + JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); + JS::Rooted func(cx, (JSObject*) JS_NewFunction(cx, (JSNative) 1, 0, 0, + "dys")); + JS::Rooted anon(cx, (JSObject*) JS_NewFunction(cx, (JSNative) 1, 0, 0, nullptr)); + JS::Rooted funcPtr(cx, JS_NewFunction(cx, (JSNative) 1, 0, 0, + "formFollows")); + + JSObject& plainRef = *plain; + JSFunction& funcRef = *funcPtr; + JSObject* plainRaw = plain; + JSObject* funcRaw = func; + + static const JSClass cls = { "\xc7X" }; + JS::RootedObject badClassName(cx, JS_NewObject(cx, &cls)); + + breakpoint(); + + (void) glob; + (void) plain; + (void) func; + (void) anon; + (void) funcPtr; + (void) &plainRef; + (void) &funcRef; + (void) plainRaw; + (void) funcRaw; +} + +FRAGMENT(JSObject, null) { + JS::Rooted null(cx, nullptr); + JSObject* nullRaw = null; + + breakpoint(); + + (void) null; + (void) nullRaw; +} diff --git a/js/src/gdb/tests/test-JSObject.py b/js/src/gdb/tests/test-JSObject.py new file mode 100644 index 000000000..3d4c7c37b --- /dev/null +++ b/js/src/gdb/tests/test-JSObject.py @@ -0,0 +1,22 @@ +# Printing JSObjects. + +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] delegate') +assert_pretty('plain', '(JSObject *) [object Object]') +assert_pretty('func', '(JSObject *) [object Function "dys"]') +assert_pretty('anon', '(JSObject *) [object Function ]') +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 000000000..eec3e03ba --- /dev/null +++ b/js/src/gdb/tests/test-JSString-null.py @@ -0,0 +1,6 @@ +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 000000000..4539cd145 --- /dev/null +++ b/js/src/gdb/tests/test-JSString-subclasses.py @@ -0,0 +1,5 @@ +# We can print pointers to subclasses of JSString. + +run_fragment('JSString.subclasses') + +assert_pretty('flat', '"Hi!"') diff --git a/js/src/gdb/tests/test-JSString.cpp b/js/src/gdb/tests/test-JSString.cpp new file mode 100644 index 000000000..679640bc7 --- /dev/null +++ b/js/src/gdb/tests/test-JSString.cpp @@ -0,0 +1,64 @@ +#include "gdb-tests.h" +#include "jsatom.h" +#include "jscntxt.h" + +// When JSGC_ANALYSIS is #defined, Rooted needs the definition +// of JSFlatString in order to figure out its ThingRootKind +#include "vm/String.h" + +FRAGMENT(JSString, simple) { + JS::Rooted empty(cx, JS_NewStringCopyN(cx, nullptr, 0)); + JS::Rooted x(cx, JS_NewStringCopyN(cx, "x", 1)); + JS::Rooted z(cx, JS_NewStringCopyZ(cx, "z")); + + // I expect this will be a non-inlined string. + JS::Rooted stars(cx, JS_NewStringCopyZ(cx, + "*************************" + "*************************" + "*************************" + "*************************")); + + // This may well be an inlined string. + JS::Rooted xz(cx, JS_ConcatStrings(cx, x, z)); + + // This will probably be a rope. + JS::Rooted doubleStars(cx, JS_ConcatStrings(cx, stars, stars)); + + // Ensure we're not confused by typedefs for pointer types. + JSString* xRaw = x; + + breakpoint(); + + (void) empty; + (void) x; + (void) z; + (void) stars; + (void) xz; + (void) doubleStars; + (void) xRaw; +} + +FRAGMENT(JSString, null) { + JS::Rooted null(cx, nullptr); + JSString* nullRaw = null; + + breakpoint(); + + (void) null; + (void) nullRaw; +} + +FRAGMENT(JSString, subclasses) { + JS::Rooted flat(cx, JS_FlattenString(cx, JS_NewStringCopyZ(cx, "Hi!"))); + + breakpoint(); + + (void) flat; +} + +FRAGMENT(JSString, atom) { + JSAtom* molybdenum = js::Atomize(cx, "molybdenum", 10); + breakpoint(); + + (void) molybdenum; +} diff --git a/js/src/gdb/tests/test-JSString.py b/js/src/gdb/tests/test-JSString.py new file mode 100644 index 000000000..3189f34a4 --- /dev/null +++ b/js/src/gdb/tests/test-JSString.py @@ -0,0 +1,23 @@ +# Printing JSStrings. + +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), "'*' ") + +doubleStars = gdb.parse_and_eval('doubleStars') +assert_eq(str(doubleStars), "'*' ") + +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 000000000..933f3075c --- /dev/null +++ b/js/src/gdb/tests/test-JSSymbol.cpp @@ -0,0 +1,20 @@ +#include "gdb-tests.h" +#include "jsapi.h" + +FRAGMENT(JSSymbol, simple) { + using namespace JS; + + RootedString hello(cx, JS_NewStringCopyZ(cx, "Hello!")); + + Rooted unique(cx, NewSymbol(cx, nullptr)); + Rooted unique_with_desc(cx, NewSymbol(cx, hello)); + Rooted registry(cx, GetSymbolFor(cx, hello)); + Rooted well_known(cx, GetWellKnownSymbol(cx, SymbolCode::iterator)); + + breakpoint(); + + (void) unique; + (void) unique_with_desc; + (void) registry; + (void) 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 000000000..a9ffb5f41 --- /dev/null +++ b/js/src/gdb/tests/test-JSSymbol.py @@ -0,0 +1,10 @@ +# Printing JS::Symbols. + +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 000000000..9abf2e801 --- /dev/null +++ b/js/src/gdb/tests/test-Root-null.py @@ -0,0 +1,20 @@ +# 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. + +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 000000000..6a8d8928b --- /dev/null +++ b/js/src/gdb/tests/test-Root.cpp @@ -0,0 +1,61 @@ +#include "gdb-tests.h" + +#include "jsapi.h" +#include "jsfun.h" + +#include "gc/Barrier.h" + +FRAGMENT(Root, null) { + JS::Rooted null(cx, nullptr); + + breakpoint(); + + (void) null; +} + +void callee(JS::Handle obj, JS::MutableHandle 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 global(cx, JS::CurrentGlobalOrNull(cx)); + callee(global, &global); + (void) global; +} + +FRAGMENT(Root, HeapSlot) { + JS::Rooted plinth(cx, JS::StringValue(JS_NewStringCopyZ(cx, "plinth"))); + JS::Rooted array(cx, JS_NewArrayObject(cx, JS::HandleValueArray(plinth))); + + breakpoint(); + + (void) plinth; + (void) array; +} + +FRAGMENT(Root, barriers) { + JSObject* obj = JS_NewPlainObject(cx); + js::PreBarriered prebarriered(obj); + js::GCPtrObject heapptr(obj); + js::HeapPtr relocatable(obj); + + JS::Value val = JS::ObjectValue(*obj); + js::PreBarrieredValue prebarrieredValue(JS::ObjectValue(*obj)); + js::GCPtrValue heapValue(JS::ObjectValue(*obj)); + js::HeapPtr relocatableValue(JS::ObjectValue(*obj)); + + breakpoint(); + + (void) prebarriered; + (void) heapptr; + (void) relocatable; + (void) val; + (void) prebarrieredValue; + (void) heapValue; + (void) relocatableValue; +} + diff --git a/js/src/gdb/tests/test-Root.py b/js/src/gdb/tests/test-Root.py new file mode 100644 index 000000000..e2b215082 --- /dev/null +++ b/js/src/gdb/tests/test-Root.py @@ -0,0 +1,27 @@ +# Test printing Handles. + +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] delegate') +assert_pretty('mutableObj', '(JSObject *) [object global] delegate') + +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.get())->elements_[0]', '$jsval("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', '$jsval((JSObject *) [object Object])'); +assert_pretty('heapValue', '$jsval((JSObject *) [object Object])'); +assert_pretty('prebarrieredValue', '$jsval((JSObject *) [object Object])'); +assert_pretty('relocatableValue', '$jsval((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 000000000..a9f16f1a1 --- /dev/null +++ b/js/src/gdb/tests/test-asmjs.cpp @@ -0,0 +1,38 @@ +#include "gdb-tests.h" +#include "jsapi.h" + +#include + +FRAGMENT(asmjs, segfault) { + using namespace JS; + + int line0 = __LINE__; + const char* bytes = "\n" + "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"; + + CompileOptions opts(cx); + opts.setFileAndLine(__FILE__, line0 + 1); + opts.asmJSOption = JS::AsmJSOption::Enabled; + RootedValue rval(cx); + bool ok; + ok = false; + + ok = Evaluate(cx, opts, bytes, strlen(bytes), &rval); + + breakpoint(); + + (void) ok; + (void) rval; +} diff --git a/js/src/gdb/tests/test-asmjs.py b/js/src/gdb/tests/test-asmjs.py new file mode 100644 index 000000000..219cb1a4d --- /dev/null +++ b/js/src/gdb/tests/test-asmjs.py @@ -0,0 +1,15 @@ +# 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. + +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', '$jsval("ok")') diff --git a/js/src/gdb/tests/test-jsid.cpp b/js/src/gdb/tests/test-jsid.cpp new file mode 100644 index 000000000..9dd1ff90a --- /dev/null +++ b/js/src/gdb/tests/test-jsid.cpp @@ -0,0 +1,44 @@ +#include "gdb-tests.h" +#include "jsapi.h" + +FRAGMENT(jsid, simple) { + JS::Rooted string(cx, JS_NewStringCopyZ(cx, "moon")); + JS::Rooted interned(cx, JS_AtomizeAndPinJSString(cx, string)); + JS::Rooted string_id(cx, INTERNED_STRING_TO_JSID(cx, interned)); + jsid int_id = INT_TO_JSID(1729); + JS::Rooted unique_symbol_id( + cx, SYMBOL_TO_JSID(JS::NewSymbol(cx, interned))); + JS::Rooted registry_symbol_id( + cx, SYMBOL_TO_JSID(JS::GetSymbolFor(cx, interned))); + JS::Rooted well_known_symbol_id( + cx, SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::iterator))); + jsid void_id = JSID_VOID; + jsid empty_id = JSID_EMPTY; + + breakpoint(); + + (void) string_id; + (void) int_id; + (void) unique_symbol_id; + (void) registry_symbol_id; + (void) well_known_symbol_id; + (void) void_id; + (void) empty_id; +} + +void +jsid_handles(JS::Handle jsid_handle, + JS::MutableHandle 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) { + JS::Rooted string(cx, JS_NewStringCopyZ(cx, "shovel")); + JS::Rooted interned(cx, JS_AtomizeAndPinJSString(cx, string)); + JS::Rooted string_id(cx, INTERNED_STRING_TO_JSID(cx, 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 000000000..265e8ac6e --- /dev/null +++ b/js/src/gdb/tests/test-jsid.py @@ -0,0 +1,19 @@ +# Tests for jsid pretty-printing + +assert_subprinter_registered('SpiderMonkey', 'jsid') + +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', 'JSID_VOID') +assert_pretty('empty_id', 'JSID_EMPTY') + +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-jsval.cpp b/js/src/gdb/tests/test-jsval.cpp new file mode 100644 index 000000000..f3c3247e2 --- /dev/null +++ b/js/src/gdb/tests/test-jsval.cpp @@ -0,0 +1,40 @@ +#include "gdb-tests.h" +#include "jsapi.h" + +FRAGMENT(jsval, simple) { + using namespace JS; + + RootedValue fortytwo(cx, Int32Value(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 global(cx); + global.setObject(*CurrentGlobalOrNull(cx)); + + // Some interesting value that floating-point won't munge. + RootedValue onehundredthirtysevenonehundredtwentyeighths(cx, DoubleValue(137.0 / 128.0)); + + breakpoint(); + + (void) fortytwo; + (void) negone; + (void) undefined; + (void) js_true; + (void) js_false; + (void) null; + (void) elements_hole; + (void) empty_string; + (void) friendly_string; + (void) symbol; + (void) global; +} diff --git a/js/src/gdb/tests/test-jsval.py b/js/src/gdb/tests/test-jsval.py new file mode 100644 index 000000000..f39a6591f --- /dev/null +++ b/js/src/gdb/tests/test-jsval.py @@ -0,0 +1,18 @@ +# Basic unit tests for jsval pretty-printer. + +assert_subprinter_registered('SpiderMonkey', 'JS::Value') + +run_fragment('jsval.simple') + +assert_pretty('fortytwo', '$jsval(42)') +assert_pretty('negone', '$jsval(-1)') +assert_pretty('undefined', 'JSVAL_VOID') +assert_pretty('null', 'JSVAL_NULL') +assert_pretty('js_true', 'JSVAL_TRUE') +assert_pretty('js_false', 'JSVAL_FALSE') +assert_pretty('elements_hole', '$jsmagic(JS_ELEMENTS_HOLE)') +assert_pretty('empty_string', '$jsval("")') +assert_pretty('friendly_string', '$jsval("Hello!")') +assert_pretty('symbol', '$jsval(Symbol.for("Hello!"))') +assert_pretty('global', '$jsval((JSObject *) [object global] delegate)') +assert_pretty('onehundredthirtysevenonehundredtwentyeighths', '$jsval(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 000000000..794e4fd56 --- /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; + A a; + B b; + C c; + C_ c_; + E e; + E_ e_; + F f; + H h; + + breakpoint(); + + (void) i; + (void) a; + (void) b; + (void) c; + (void) c_; + (void) e; + (void) e_; + (void) f; + (void) h; +} diff --git a/js/src/gdb/tests/test-prettyprinters.py b/js/src/gdb/tests/test-prettyprinters.py new file mode 100644 index 000000000..9c380fdda --- /dev/null +++ b/js/src/gdb/tests/test-prettyprinters.py @@ -0,0 +1,22 @@ +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']) +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 000000000..6c8b7b86a --- /dev/null +++ b/js/src/gdb/tests/test-unwind.cpp @@ -0,0 +1,52 @@ +#include "gdb-tests.h" +#include "jsapi.h" +#include "jit/JitOptions.h" + +#include + +static bool +Something(JSContext* cx, unsigned argc, JS::Value* vp) +{ + JS::CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setInt32(23); + breakpoint(); + return true; +} + +static const JSFunctionSpecWithHelp unwind_functions[] = { + JS_FN_HELP("something", Something, 0, 0, +"something()", +" Test function for test-unwind."), + JS_FS_HELP_END +}; + +FRAGMENT(unwind, simple) { + using namespace JS; + + JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); + if (!JS_DefineFunctionsWithHelp(cx, global, unwind_functions)) + return; + + // baseline-eager. + uint32_t saveThreshold = js::jit::JitOptions.baselineWarmUpThreshold; + js::jit::JitOptions.baselineWarmUpThreshold = 0; + + int line0 = __LINE__; + const char* bytes = "\n" + "function unwindFunctionInner() {\n" + " return something();\n" + "}\n" + "\n" + "function unwindFunctionOuter() {\n" + " return unwindFunctionInner();\n" + "}\n" + "\n" + "unwindFunctionOuter();\n"; + + CompileOptions opts(cx); + opts.setFileAndLine(__FILE__, line0 + 1); + RootedValue rval(cx); + Evaluate(cx, opts, bytes, strlen(bytes), &rval); + + js::jit::JitOptions.baselineWarmUpThreshold = saveThreshold; +} diff --git a/js/src/gdb/tests/test-unwind.py b/js/src/gdb/tests/test-unwind.py new file mode 100644 index 000000000..6ddf0cd22 --- /dev/null +++ b/js/src/gdb/tests/test-unwind.py @@ -0,0 +1,58 @@ +# Test the unwinder and the frame filter. + +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() == "<>": + found_exit = True + elif frame.function() == "<>": + 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 + import gdb.frames + do_unwinder_test() + except: + pass diff --git a/js/src/gdb/tests/typedef-printers.cpp b/js/src/gdb/tests/typedef-printers.cpp new file mode 100644 index 000000000..7573b5de5 --- /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(); + + (void) i; +} diff --git a/js/src/gdb/tests/typedef-printers.py b/js/src/gdb/tests/typedef-printers.py new file mode 100644 index 000000000..ed37e438e --- /dev/null +++ b/js/src/gdb/tests/typedef-printers.py @@ -0,0 +1,14 @@ +# Test that we can find pretty-printers for typedef names, not just for +# struct types and templates. + +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') -- cgit v1.2.3