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