diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/src/gdb/mozilla | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/gdb/mozilla')
-rw-r--r-- | js/src/gdb/mozilla/CellHeader.py | 25 | ||||
-rw-r--r-- | js/src/gdb/mozilla/ExecutableAllocator.py | 97 | ||||
-rw-r--r-- | js/src/gdb/mozilla/GCCellPtr.py | 125 | ||||
-rw-r--r-- | js/src/gdb/mozilla/Interpreter.py | 90 | ||||
-rw-r--r-- | js/src/gdb/mozilla/IonGraph.py | 283 | ||||
-rw-r--r-- | js/src/gdb/mozilla/JSObject.py | 119 | ||||
-rw-r--r-- | js/src/gdb/mozilla/JSString.py | 105 | ||||
-rw-r--r-- | js/src/gdb/mozilla/JSSymbol.py | 40 | ||||
-rw-r--r-- | js/src/gdb/mozilla/PropertyKey.py | 62 | ||||
-rw-r--r-- | js/src/gdb/mozilla/Root.py | 104 | ||||
-rw-r--r-- | js/src/gdb/mozilla/__init__.py | 5 | ||||
-rw-r--r-- | js/src/gdb/mozilla/asmjs.py | 51 | ||||
-rw-r--r-- | js/src/gdb/mozilla/autoload.py | 41 | ||||
-rw-r--r-- | js/src/gdb/mozilla/jitsrc.py | 161 | ||||
-rw-r--r-- | js/src/gdb/mozilla/jsop.py | 60 | ||||
-rw-r--r-- | js/src/gdb/mozilla/jsval.py | 188 | ||||
-rw-r--r-- | js/src/gdb/mozilla/prettyprinters.py | 446 | ||||
-rw-r--r-- | js/src/gdb/mozilla/unwind.py | 596 |
18 files changed, 2598 insertions, 0 deletions
diff --git a/js/src/gdb/mozilla/CellHeader.py b/js/src/gdb/mozilla/CellHeader.py new file mode 100644 index 0000000000..21dcba849d --- /dev/null +++ b/js/src/gdb/mozilla/CellHeader.py @@ -0,0 +1,25 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import gdb + + +def get_header_ptr(value, ptr_t): + # Return the pointer stored in Cell::header_ for subclasses of + # TenuredCellWithNonGCPointer and CellWithTenuredGCPointer. + return value["header_"]["value_"].cast(ptr_t) + + +def get_header_length_and_flags(value, cache): + # Return the length and flags values for subclasses of + # CellWithLengthAndFlags. + flags = value["header_"]["value_"].cast(cache.uintptr_t) + try: + length = value["length_"] + except gdb.error: + # If we couldn't fetch the length directly, it must be stored + # within `flags`. + length = flags >> 32 + flags = flags % 2**32 + return length, flags diff --git a/js/src/gdb/mozilla/ExecutableAllocator.py b/js/src/gdb/mozilla/ExecutableAllocator.py new file mode 100644 index 0000000000..5187e2d1f1 --- /dev/null +++ b/js/src/gdb/mozilla/ExecutableAllocator.py @@ -0,0 +1,97 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +""" +All jitted code is allocated via the ExecutableAllocator class. Make GDB aware +of them, such that we can query for pages which are containing code which are +allocated by the Jits. +""" + +import gdb + +import mozilla.prettyprinters +from mozilla.prettyprinters import pretty_printer, ptr_pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + + +class jsjitExecutableAllocatorCache(object): + """Cache information about the ExecutableAllocator type for this objfile.""" + + def __init__(self): + self.d = None + + def __getattr__(self, name): + if self.d is None: + self.initialize() + return self.d[name] + + def initialize(self): + self.d = {} + self.d["ExecutableAllocator"] = gdb.lookup_type("js::jit::ExecutableAllocator") + self.d["ExecutablePool"] = gdb.lookup_type("js::jit::ExecutablePool") + self.d["HashNumber"] = gdb.lookup_type("mozilla::HashNumber") + + +@pretty_printer("js::jit::ExecutableAllocator") +class jsjitExecutableAllocator(object): + def __init__(self, value, cache): + if not cache.mod_ExecutableAllocator: + cache.mod_ExecutableAllocator = jsjitExecutableAllocatorCache() + self.value = value + self.cache = cache.mod_ExecutableAllocator + + def to_string(self): + return "ExecutableAllocator([%s])" % ", ".join([str(x) for x in self]) + + def __iter__(self): + return self.PoolIterator(self) + + class PoolIterator(object): + def __init__(self, allocator): + self.allocator = allocator + self.entryType = allocator.cache.ExecutablePool.pointer() + self.hashNumType = allocator.cache.HashNumber + # Emulate the HashSet::Range + self.table = allocator.value["m_pools"]["mImpl"]["mTable"] + self.index = 0 + kHashNumberBits = 32 + hashShift = allocator.value["m_pools"]["mImpl"]["mHashShift"] + self.capacity = 1 << (kHashNumberBits - hashShift) + if self.table == 0: + self.capacity = 0 + # auto hashes = reinterpret_cast<HashNumber*>(mTable); + self.hashes = self.table.cast(self.hashNumType.pointer()) + # auto entries = reinterpret_cast<Entry*>(&hashes[capacity()]); + self.entries = (self.hashes + self.capacity).cast(self.entryType.pointer()) + + def __iter__(self): + return self + + def next(self): + return self.__next__() + + def __next__(self): + cur = self.index + if cur >= self.capacity: + raise StopIteration() + self.index = self.index + 1 + if self.hashes[cur] > 1: # table[i]->isLive() + return self.entries[cur] + return self.__next__() + + +@ptr_pretty_printer("js::jit::ExecutablePool") +class jsjitExecutablePool(mozilla.prettyprinters.Pointer): + def __init__(self, value, cache): + if not cache.mod_ExecutableAllocator: + cache.mod_ExecutableAllocator = jsjitExecutableAllocatorCache() + self.value = value + self.cache = cache.mod_ExecutableAllocator + + def to_string(self): + pages = self.value["m_allocation"]["pages"] + size = self.value["m_allocation"]["size"] + return "ExecutablePool %08x-%08x" % (pages, pages + size) diff --git a/js/src/gdb/mozilla/GCCellPtr.py b/js/src/gdb/mozilla/GCCellPtr.py new file mode 100644 index 0000000000..e42ecfa893 --- /dev/null +++ b/js/src/gdb/mozilla/GCCellPtr.py @@ -0,0 +1,125 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Pretty-printers for GCCellPtr values. + +import gdb + +import mozilla.prettyprinters +from mozilla.prettyprinters import pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + +# Cache information about the types for this objfile. + + +class GCCellPtrTypeCache(object): + def __init__(self, cache): + self.TraceKind_t = gdb.lookup_type("JS::TraceKind") + self.AllocKind_t = gdb.lookup_type("js::gc::AllocKind") + self.Arena_t = gdb.lookup_type("js::gc::Arena") + self.Cell_t = gdb.lookup_type("js::gc::Cell") + self.TenuredCell_t = gdb.lookup_type("js::gc::TenuredCell") + + trace_kinds = gdb.types.make_enum_dict(self.TraceKind_t) + alloc_kinds = gdb.types.make_enum_dict(self.AllocKind_t) + + def trace_kind(k): + return trace_kinds["JS::TraceKind::" + k] + + def alloc_kind(k): + return alloc_kinds["js::gc::AllocKind::" + k] + + # Build a mapping from TraceKind enum values to the types they denote. + trace_map = { + # Inline types. + "Object": "JSObject", + "BigInt": "JS::BigInt", + "String": "JSString", + "Symbol": "JS::Symbol", + "Shape": "js::Shape", + "BaseShape": "js::BaseShape", + "Null": "std::nullptr_t", + # Out-of-line types. + "JitCode": "js::jit::JitCode", + "Script": "js::BaseScript", + "Scope": "js::Scope", + "RegExpShared": "js::RegExpShared", + "GetterSetter": "js::GetterSetter", + "PropMap": "js::PropMap", + } + + # Map from AllocKind to TraceKind for out-of-line types. + alloc_map = { + "JITCODE": "JitCode", + "SCRIPT": "Script", + "SCOPE": "Scope", + "REGEXP_SHARED": "RegExpShared", + "GETTER_SETTER": "GetterSetter", + "COMPACT_PROP_MAP": "PropMap", + "NORMAL_PROP_MAP": "PropMap", + "DICT_PROP_MAP": "PropMap", + } + + self.trace_kind_to_type = { + trace_kind(k): gdb.lookup_type(v) for k, v in trace_map.items() + } + self.alloc_kind_to_trace_kind = { + alloc_kind(k): trace_kind(v) for k, v in alloc_map.items() + } + + self.Null = trace_kind("Null") + self.tracekind_mask = gdb.parse_and_eval("JS::OutOfLineTraceKindMask") + self.arena_mask = gdb.parse_and_eval("js::gc::ArenaMask") + + +@pretty_printer("JS::GCCellPtr") +class GCCellPtr(object): + def __init__(self, value, cache): + self.value = value + if not cache.mod_GCCellPtr: + cache.mod_GCCellPtr = GCCellPtrTypeCache(cache) + self.cache = cache + + def to_string(self): + ptr = self.value["ptr"] + kind = ptr & self.cache.mod_GCCellPtr.tracekind_mask + if kind == self.cache.mod_GCCellPtr.Null: + return "JS::GCCellPtr(nullptr)" + if kind == self.cache.mod_GCCellPtr.tracekind_mask: + # Out-of-line trace kinds. + # + # Compute the underlying type for out-of-line kinds by + # reimplementing the GCCellPtr::outOfLineKind() method. + # + # The extra casts below are only present to make it easier to + # compare this code against the C++ implementation. + + # GCCellPtr::asCell() + cell_ptr = ptr & ~self.cache.mod_GCCellPtr.tracekind_mask + cell = cell_ptr.reinterpret_cast(self.cache.mod_GCCellPtr.Cell_t.pointer()) + + # Cell::asTenured() + tenured = cell.cast(self.cache.mod_GCCellPtr.TenuredCell_t.pointer()) + + # TenuredCell::arena() + addr = int(tenured) + arena_ptr = addr & ~self.cache.mod_GCCellPtr.arena_mask + arena = arena_ptr.reinterpret_cast( + self.cache.mod_GCCellPtr.Arena_t.pointer() + ) + + # Arena::getAllocKind() + alloc_kind = arena["allocKind"].cast(self.cache.mod_GCCellPtr.AllocKind_t) + alloc_idx = int( + alloc_kind.cast(self.cache.mod_GCCellPtr.AllocKind_t.target()) + ) + + # Map the AllocKind to a TraceKind. + kind = self.cache.mod_GCCellPtr.alloc_kind_to_trace_kind[alloc_idx] + type_name = self.cache.mod_GCCellPtr.trace_kind_to_type[int(kind)] + return "JS::GCCellPtr(({}*) {})".format( + type_name, ptr.cast(self.cache.void_ptr_t) + ) diff --git a/js/src/gdb/mozilla/Interpreter.py b/js/src/gdb/mozilla/Interpreter.py new file mode 100644 index 0000000000..825ae40e9d --- /dev/null +++ b/js/src/gdb/mozilla/Interpreter.py @@ -0,0 +1,90 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Pretty-printers for InterpreterRegs. + +import gdb + +import mozilla.prettyprinters as prettyprinters + +prettyprinters.clear_module_printers(__name__) + +from mozilla.prettyprinters import pretty_printer + + +class InterpreterTypeCache(object): + # Cache information about the Interpreter types for this objfile. + def __init__(self): + self.tValue = gdb.lookup_type("JS::Value") + self.tJSOp = gdb.lookup_type("JSOp") + try: + self.tScriptFrameIterData = gdb.lookup_type("js::ScriptFrameIter::Data") + except gdb.error: + # Work around problem with gcc optimized debuginfo where it doesn't + # seem to be able to see that ScriptFrameIter inherits the + # FrameIter::Data type. + self.tScriptFrameIterData = gdb.lookup_type("js::FrameIter::Data") + self.tInterpreterFrame = gdb.lookup_type("js::InterpreterFrame") + self.tBaselineFrame = gdb.lookup_type("js::jit::BaselineFrame") + self.tRematerializedFrame = gdb.lookup_type("js::jit::RematerializedFrame") + self.tDebugFrame = gdb.lookup_type("js::wasm::DebugFrame") + + +@pretty_printer("js::InterpreterRegs") +class InterpreterRegs(object): + def __init__(self, value, cache): + self.value = value + self.cache = cache + if not cache.mod_Interpreter: + cache.mod_Interpreter = InterpreterTypeCache() + self.itc = cache.mod_Interpreter + + # There's basically no way to co-operate with 'set print pretty' (how would + # you get the current level of indentation?), so we don't even bother + # trying. No 'children', just 'to_string'. + def to_string(self): + fp_ = "fp_ = {}".format(self.value["fp_"]) + slots = (self.value["fp_"] + 1).cast(self.itc.tValue.pointer()) + sp = "sp = fp_.slots() + {}".format(self.value["sp"] - slots) + pc = "pc = {}".format(self.value["pc"]) + return "{{ {}, {}, {} }}".format(fp_, sp, pc) + + +@pretty_printer("js::AbstractFramePtr") +class AbstractFramePtr(object): + Tag_InterpreterFrame = 0x0 + Tag_BaselineFrame = 0x1 + Tag_RematerializedFrame = 0x2 + Tag_WasmDebugFrame = 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_InterpreterFrame: + label = "js::InterpreterFrame" + ptr = ptr.cast(self.itc.tInterpreterFrame.pointer()) + if tag == AbstractFramePtr.Tag_BaselineFrame: + label = "js::jit::BaselineFrame" + ptr = ptr.cast(self.itc.tBaselineFrame.pointer()) + if tag == AbstractFramePtr.Tag_RematerializedFrame: + label = "js::jit::RematerializedFrame" + ptr = ptr.cast(self.itc.tRematerializedFrame.pointer()) + if tag == AbstractFramePtr.Tag_WasmDebugFrame: + label = "js::wasm::DebugFrame" + ptr = ptr.cast(self.itc.tDebugFrame.pointer()) + return "AbstractFramePtr (({} *) {})".format(label, ptr) + + # Provide the ptr_ field as a child, so it prints after the pretty string + # provided above. + def children(self): + yield ("ptr_", self.value["ptr_"]) diff --git a/js/src/gdb/mozilla/IonGraph.py b/js/src/gdb/mozilla/IonGraph.py new file mode 100644 index 0000000000..639baf3ace --- /dev/null +++ b/js/src/gdb/mozilla/IonGraph.py @@ -0,0 +1,283 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +""" +Debugging JIT Compilations can be obscure without large context. This python +script provide commands to let GDB open an image viewer to display the graph of +any compilation, as they are executed within GDB. + +This python script should be sourced within GDB after loading the python scripts +provided with SpiderMonkey. +""" + +import os +import subprocess +import tempfile +import time + +import gdb + +import mozilla.prettyprinters +from mozilla.prettyprinters import pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + +# Cache information about the JSString type for this objfile. + + +class jsvmPrinterCache(object): + def __init__(self): + self.d = None + + def __getattr__(self, name): + if self.d is None: + self.initialize() + return self.d[name] + + def initialize(self): + self.d = {} + self.d["char"] = gdb.lookup_type("char") + + +# Dummy class used to store the content of the type cache in the context of the +# iongraph command, which uses the jsvmLSprinter. + + +class ModuleCache(object): + def __init__(self): + self.mod_IonGraph = None + + +@pretty_printer("js::vm::LSprinter") +class jsvmLSprinter(object): + def __init__(self, value, cache): + self.value = value + if not cache.mod_IonGraph: + cache.mod_IonGraph = jsvmPrinterCache() + self.cache = cache.mod_IonGraph + + def to_string(self): + next = self.value["head_"] + tail = self.value["tail_"] + if next == 0: + return "" + res = "" + while next != tail: + chars = (next + 1).cast(self.cache.char.pointer()) + res = res + chars.string("ascii", "ignore", next["length"]) + next = next["next"] + length = next["length"] - self.value["unused_"] + chars = (next + 1).cast(self.cache.char.pointer()) + res = res + chars.string("ascii", "ignore", length) + return res + + +def search_in_path(bin): + paths = os.getenv("PATH", "") + if paths == "": + return "" + for d in paths.split(":"): + f = os.path.join(d, bin) + if os.access(f, os.X_OK): + return f + return "" + + +class IonGraphBinParameter(gdb.Parameter): + set_doc = "Set the path to iongraph binary, used by iongraph command." + show_doc = "Show the path to iongraph binary, used by iongraph command." + + def get_set_string(self): + return "Path to iongraph binary changed to: %s" % self.value + + def get_show_string(self, value): + return "Path to iongraph binary set to: %s" % value + + def __init__(self): + super(IonGraphBinParameter, self).__init__( + "iongraph-bin", gdb.COMMAND_SUPPORT, gdb.PARAM_FILENAME + ) + self.value = os.getenv("GDB_IONGRAPH", "") + if self.value == "": + self.value = search_in_path("iongraph") + + +class DotBinParameter(gdb.Parameter): + set_doc = "Set the path to dot binary, used by iongraph command." + show_doc = "Show the path to dot binary, used by iongraph command." + + def get_set_string(self): + return "Path to dot binary changed to: %s" % self.value + + def get_show_string(self, value): + return "Path to dot binary set to: %s" % value + + def __init__(self): + super(DotBinParameter, self).__init__( + "dot-bin", gdb.COMMAND_SUPPORT, gdb.PARAM_FILENAME + ) + self.value = os.getenv("GDB_DOT", "") + if self.value == "": + self.value = search_in_path("dot") + + +class PngViewerBinParameter(gdb.Parameter): + set_doc = "Set the path to a png viewer binary, used by iongraph command." + show_doc = "Show the path to a png viewer binary, used by iongraph command." + + def get_set_string(self): + return "Path to a png viewer binary changed to: %s" % self.value + + def get_show_string(self): + return "Path to a png viewer binary set to: %s" % self.value + + def __init__(self): + super(PngViewerBinParameter, self).__init__( + "pngviewer-bin", gdb.COMMAND_SUPPORT, gdb.PARAM_FILENAME + ) + self.value = os.getenv("GDB_PNGVIEWER", "") + if self.value == "": + self.value = search_in_path("xdg-open") + + +iongraph = IonGraphBinParameter() +dot = DotBinParameter() +pngviewer = PngViewerBinParameter() + + +class IonGraphCommand(gdb.Command): + """Command used to display the current state of the MIR graph in a png + viewer by providing an expression to access the MIRGenerator. + """ + + def __init__(self): + super(IonGraphCommand, self).__init__( + "iongraph", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION + ) + self.typeCache = ModuleCache() + + def invoke(self, mirGenExpr, from_tty): + """Call function from the graph spewer to populate the json printer with + the content generated by the jsonSpewer. Then we read the json content + from the jsonPrinter internal data, and gives that as input of iongraph + command.""" + + # From the MIRGenerator, find the graph spewer which contains both the + # jsonPrinter (containing the result of the output), and the jsonSpewer + # (continaining methods for spewing the graph). + mirGen = gdb.parse_and_eval(mirGenExpr) + jsonPrinter = mirGen["gs_"]["jsonPrinter_"] + jsonSpewer = mirGen["gs_"]["jsonSpewer_"] + graph = mirGen["graph_"] + + # These commands are doing side-effects which are saving the state of + # the compiled code on the LSprinter dedicated for logging. Fortunately, + # if you are using these gdb command, this probably means that other + # ways of getting this content failed you already, so making a mess in + # these logging strings should not cause much issues. + gdb.parse_and_eval( + "(*(%s*)(%s)).clear()" + % ( + jsonPrinter.type, + jsonPrinter.address, + ) + ) + gdb.parse_and_eval( + "(*(%s*)(%s)).beginFunction((JSScript*)0)" + % ( + jsonSpewer.type, + jsonSpewer.address, + ) + ) + gdb.parse_and_eval( + '(*(%s*)(%s)).beginPass("gdb")' + % ( + jsonSpewer.type, + jsonSpewer.address, + ) + ) + gdb.parse_and_eval( + "(*(%s*)(%s)).spewMIR((%s)%s)" + % ( + jsonSpewer.type, + jsonSpewer.address, + graph.type, + graph, + ) + ) + gdb.parse_and_eval( + "(*(%s*)(%s)).spewLIR((%s)%s)" + % ( + jsonSpewer.type, + jsonSpewer.address, + graph.type, + graph, + ) + ) + gdb.parse_and_eval( + "(*(%s*)(%s)).endPass()" + % ( + jsonSpewer.type, + jsonSpewer.address, + ) + ) + gdb.parse_and_eval( + "(*(%s*)(%s)).endFunction()" + % ( + jsonSpewer.type, + jsonSpewer.address, + ) + ) + + # Dump the content of the LSprinter containing the JSON view of the + # graph into a python string. + json = jsvmLSprinter(jsonPrinter, self.typeCache).to_string() + + # We are in the middle of the program execution and are messing up with + # the state of the logging data. As this might not be the first time we + # call beginFunction, we might have an extra comma at the beginning of + # the output, just strip it. + if json[0] == ",": + json = json[1:] + + # Usually this is added by the IonSpewer. + json = '{ "functions": [%s] }' % json + + # Display the content of the json with iongraph and other tools. + self.displayMIRGraph(json) + + def displayMIRGraph(self, jsonStr): + png = tempfile.NamedTemporaryFile() + + # start all processes in a shell-like equivalent of: + # iongraph < json | dot > tmp.png; xdg-open tmp.png + i = subprocess.Popen( + [iongraph.value, "--funcnum", "0", "--passnum", "0", "--out-mir", "-", "-"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + d = subprocess.Popen([dot.value, "-Tpng"], stdin=i.stdout, stdout=png) + + # Write the json file as the input of the iongraph command. + i.stdin.write(jsonStr.encode("utf8")) + i.stdin.close() + i.stdout.close() + + # Wait for iongraph and dot, such that the png file contains all the + # bits needed to by the png viewer. + i.wait() + d.communicate()[0] + + # Spawn & detach the png viewer, to which we give the name of the + # temporary file. Note, as we do not want to wait on the image viewer, + # there is a minor race between the removal of the temporary file, which + # would happen at the next garbage collection cycle, and the start of + # the png viewer. We could use a pipe, but unfortunately, this does not + # seems to be supported by xdg-open. + subprocess.Popen([pngviewer.value, png.name], stdin=None, stdout=None) + time.sleep(1) + + +iongraph_cmd = IonGraphCommand() diff --git a/js/src/gdb/mozilla/JSObject.py b/js/src/gdb/mozilla/JSObject.py new file mode 100644 index 0000000000..95accc93b9 --- /dev/null +++ b/js/src/gdb/mozilla/JSObject.py @@ -0,0 +1,119 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Pretty-printers for SpiderMonkey JSObjects. + +import re + +import gdb + +import mozilla.prettyprinters as prettyprinters +from mozilla.CellHeader import get_header_ptr +from mozilla.jsval import JSValue +from mozilla.prettyprinters import ptr_pretty_printer, ref_pretty_printer + +prettyprinters.clear_module_printers(__name__) + + +class JSObjectTypeCache(object): + def __init__(self): + object_flag = gdb.lookup_type("js::ObjectFlag") + self.objectflag_IsUsedAsPrototype = prettyprinters.enum_value( + object_flag, "js::ObjectFlag::IsUsedAsPrototype" + ) + self.value_ptr_t = gdb.lookup_type("JS::Value").pointer() + self.func_ptr_t = gdb.lookup_type("JSFunction").pointer() + self.class_NON_NATIVE = gdb.parse_and_eval("JSClass::NON_NATIVE") + self.BaseShape_ptr_t = gdb.lookup_type("js::BaseShape").pointer() + self.Shape_ptr_t = gdb.lookup_type("js::Shape").pointer() + self.JSClass_ptr_t = gdb.lookup_type("JSClass").pointer() + self.JSScript_ptr_t = gdb.lookup_type("JSScript").pointer() + self.JSFunction_AtomSlot = gdb.parse_and_eval("JSFunction::AtomSlot") + self.JSFunction_NativeJitInfoOrInterpretedScriptSlot = gdb.parse_and_eval( + "JSFunction::NativeJitInfoOrInterpretedScriptSlot" + ) + + +# There should be no need to register this for JSFunction as well, since we +# search for pretty-printers under the names of base classes, and +# JSFunction has JSObject as a base class. + + +gdb_string_regexp = re.compile(r'(?:0x[0-9a-z]+ )?(?:<.*> )?"(.*)"', re.I) + + +@ptr_pretty_printer("JSObject") +class JSObjectPtrOrRef(prettyprinters.Pointer): + def __init__(self, value, cache): + super(JSObjectPtrOrRef, self).__init__(value, cache) + if not cache.mod_JSObject: + cache.mod_JSObject = JSObjectTypeCache() + self.otc = cache.mod_JSObject + + def summary(self): + shape = get_header_ptr(self.value, self.otc.Shape_ptr_t) + baseshape = get_header_ptr(shape, self.otc.BaseShape_ptr_t) + classp = get_header_ptr(baseshape, self.otc.JSClass_ptr_t) + non_native = classp["flags"] & self.otc.class_NON_NATIVE + + # Use GDB to format the class name, but then strip off the address + # and the quotes. + class_name = str(classp["name"]) + m = gdb_string_regexp.match(class_name) + if m: + class_name = m.group(1) + + if non_native: + return "[object {}]".format(class_name) + else: + flags = shape["objectFlags_"]["flags_"] + used_as_prototype = bool(flags & self.otc.objectflag_IsUsedAsPrototype) + name = None + if class_name == "Function": + function = self.value + concrete_type = function.type.strip_typedefs() + if concrete_type.code == gdb.TYPE_CODE_REF: + function = function.address + name = get_function_name(function, self.cache) + return "[object {}{}]{}".format( + class_name, + " " + name if name else "", + " used_as_prototype" if used_as_prototype else "", + ) + + +def get_function_name(function, cache): + if not cache.mod_JSObject: + cache.mod_JSObject = JSObjectTypeCache() + otc = cache.mod_JSObject + + function = function.cast(otc.func_ptr_t) + fixed_slots = (function + 1).cast(otc.value_ptr_t) + atom_value = JSValue(fixed_slots[otc.JSFunction_AtomSlot], cache) + + if atom_value.is_undefined(): + return "<unnamed>" + + return str(atom_value.get_string()) + + +def get_function_script(function, cache): + if not cache.mod_JSObject: + cache.mod_JSObject = JSObjectTypeCache() + otc = cache.mod_JSObject + + function = function.cast(otc.func_ptr_t) + fixed_slots = (function + 1).cast(otc.value_ptr_t) + slot = otc.JSFunction_NativeJitInfoOrInterpretedScriptSlot + script_value = JSValue(fixed_slots[slot], cache) + + if script_value.is_undefined(): + return 0 + + return script_value.get_private() + + +@ref_pretty_printer("JSObject") +def JSObjectRef(value, cache): + return JSObjectPtrOrRef(value, cache) diff --git a/js/src/gdb/mozilla/JSString.py b/js/src/gdb/mozilla/JSString.py new file mode 100644 index 0000000000..3c758e0352 --- /dev/null +++ b/js/src/gdb/mozilla/JSString.py @@ -0,0 +1,105 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Pretty-printers for SpiderMonkey strings. + +import gdb + +import mozilla.prettyprinters +from mozilla.CellHeader import get_header_length_and_flags +from mozilla.prettyprinters import ptr_pretty_printer + +try: + chr(10000) # UPPER RIGHT PENCIL +except ValueError: # yuck, we are in Python 2.x, so chr() is 8-bit + chr = unichr # replace with teh unicodes + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + + +class JSStringTypeCache(object): + # Cache information about the JSString type for this objfile. + def __init__(self, cache): + dummy = gdb.Value(0).cast(cache.JSString_ptr_t) + self.ATOM_BIT = dummy["ATOM_BIT"] + self.LINEAR_BIT = dummy["LINEAR_BIT"] + self.INLINE_CHARS_BIT = dummy["INLINE_CHARS_BIT"] + self.TYPE_FLAGS_MASK = dummy["TYPE_FLAGS_MASK"] + self.LATIN1_CHARS_BIT = dummy["LATIN1_CHARS_BIT"] + + +class Common(mozilla.prettyprinters.Pointer): + def __init__(self, value, cache): + super(Common, self).__init__(value, cache) + if not cache.mod_JSString: + cache.mod_JSString = JSStringTypeCache(cache) + self.stc = cache.mod_JSString + + +@ptr_pretty_printer("JSString") +class JSStringPtr(Common): + def display_hint(self): + return "string" + + def chars(self): + d = self.value["d"] + length, flags = get_header_length_and_flags(self.value, self.cache) + + corrupt = { + 0x2F2F2F2F: "JS_FRESH_NURSERY_PATTERN", + 0x2B2B2B2B: "JS_SWEPT_NURSERY_PATTERN", + 0xE5E5E5E5: "jemalloc freed memory", + }.get(flags & 0xFFFFFFFF) + if corrupt: + for ch in "<CORRUPT:%s>" % corrupt: + yield ch + return + is_rope = (flags & self.stc.LINEAR_BIT) == 0 + if is_rope: + for c in JSStringPtr(d["s"]["u2"]["left"], self.cache).chars(): + yield c + for c in JSStringPtr(d["s"]["u3"]["right"], self.cache).chars(): + yield c + else: + is_inline = (flags & self.stc.INLINE_CHARS_BIT) != 0 + is_latin1 = (flags & self.stc.LATIN1_CHARS_BIT) != 0 + if is_inline: + if is_latin1: + chars = d["inlineStorageLatin1"] + else: + chars = d["inlineStorageTwoByte"] + else: + if is_latin1: + chars = d["s"]["u2"]["nonInlineCharsLatin1"] + else: + chars = d["s"]["u2"]["nonInlineCharsTwoByte"] + for i in range(int(length)): + yield chars[i] + + def to_string(self, maxlen=200): + s = "" + invalid_chars_allowed = 2 + for c in self.chars(): + if len(s) >= maxlen: + s += "..." + break + + try: + # Convert from gdb.Value to string. + s += chr(c) + except ValueError: + if invalid_chars_allowed == 0: + s += "<TOO_MANY_INVALID_CHARS>" + break + else: + invalid_chars_allowed -= 1 + s += "\\x%04x" % (c & 0xFFFF) + return s + + +@ptr_pretty_printer("JSAtom") +class JSAtomPtr(Common): + def to_string(self): + return self.value.cast(self.cache.JSString_ptr_t) diff --git a/js/src/gdb/mozilla/JSSymbol.py b/js/src/gdb/mozilla/JSSymbol.py new file mode 100644 index 0000000000..db96898c91 --- /dev/null +++ b/js/src/gdb/mozilla/JSSymbol.py @@ -0,0 +1,40 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Pretty-printer for SpiderMonkey symbols. + +import mozilla.prettyprinters +from mozilla.CellHeader import get_header_ptr +from mozilla.prettyprinters import ptr_pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + +# JS::SymbolCode enumerators +PrivateNameSymbol = 0xFFFFFFFD +InSymbolRegistry = 0xFFFFFFFE +UniqueSymbol = 0xFFFFFFFF + + +@ptr_pretty_printer("JS::Symbol") +class JSSymbolPtr(mozilla.prettyprinters.Pointer): + def __init__(self, value, cache): + super(JSSymbolPtr, self).__init__(value, cache) + self.value = value + + def to_string(self): + code = int(self.value["code_"]) & 0xFFFFFFFF + desc = str(get_header_ptr(self.value, self.cache.JSString_ptr_t)) + if code == InSymbolRegistry: + return "Symbol.for({})".format(desc) + elif code == UniqueSymbol: + return "Symbol({})".format(desc) + elif code == PrivateNameSymbol: + return "#{}".format(desc) + else: + # Well-known symbol. Strip off the quotes added by the JSString * + # pretty-printer. + assert desc[0] == '"' + assert desc[-1] == '"' + return desc[1:-1] diff --git a/js/src/gdb/mozilla/PropertyKey.py b/js/src/gdb/mozilla/PropertyKey.py new file mode 100644 index 0000000000..517976a6da --- /dev/null +++ b/js/src/gdb/mozilla/PropertyKey.py @@ -0,0 +1,62 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Pretty-printers for JSID values. + +import mozilla.prettyprinters +import mozilla.Root +from mozilla.prettyprinters import pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + + +@pretty_printer("JS::PropertyKey") +class PropertyKey(object): + # Since people don't always build with macro debugging info, I can't + # think of any way to avoid copying these values here, short of using + # inferior calls for every operation (which, I hear, is broken from + # pretty-printers in some recent GDBs). + StringTypeTag = 0x0 + IntTagBit = 0x1 + VoidTypeTag = 0x2 + SymbolTypeTag = 0x4 + TypeMask = 0x7 + + def __init__(self, value, cache): + self.value = value + self.cache = cache + self.concrete_type = self.value.type.strip_typedefs() + + def to_string(self): + bits = self.value["asBits_"] + tag = bits & PropertyKey.TypeMask + if tag == PropertyKey.StringTypeTag: + body = bits.cast(self.cache.JSString_ptr_t) + elif tag & PropertyKey.IntTagBit: + body = bits >> 1 + elif tag == PropertyKey.VoidTypeTag: + return "JS::VoidPropertyKey" + elif tag == PropertyKey.SymbolTypeTag: + body = (bits & ~PropertyKey.TypeMask).cast(self.cache.JSSymbol_ptr_t) + else: + body = "<unrecognized>" + return "$jsid(%s)" % (body,) + + +@pretty_printer("JS::Rooted<long>") +def RootedPropertyKey(value, cache): + # Hard-code the referent type pretty-printer for PropertyKey roots and + # handles. See the comment for mozilla.Root.Common.__init__. + return mozilla.Root.Rooted(value, cache, PropertyKey) + + +@pretty_printer("JS::Handle<long>") +def HandlePropertyKey(value, cache): + return mozilla.Root.Handle(value, cache, PropertyKey) + + +@pretty_printer("JS::MutableHandle<long>") +def MutableHandlePropertyKey(value, cache): + return mozilla.Root.MutableHandle(value, cache, PropertyKey) diff --git a/js/src/gdb/mozilla/Root.py b/js/src/gdb/mozilla/Root.py new file mode 100644 index 0000000000..fa80f3fec6 --- /dev/null +++ b/js/src/gdb/mozilla/Root.py @@ -0,0 +1,104 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Pretty-printers and utilities for SpiderMonkey rooting templates: +# Rooted, Handle, MutableHandle, etc. + +import mozilla.prettyprinters +from mozilla.prettyprinters import template_pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + + +class Common(object): + # Common base class for all the rooting template pretty-printers. All these + # templates have one member holding the referent (or a pointer to it), so + # there's not much to it. + + # The name of the template member holding the referent. + member = "ptr" + + # If True, this is a handle type, and should be dereferenced. If False, + # the template member holds the referent directly. + handle = False + + # If True, we should strip typedefs from our referent type. (Rooted<T> + # uses template magic that gives the referent a noisy type.) + strip_typedefs = False + + # Initialize a pretty-printer for |value|, using |cache|. + # + # If given, |content_printer| is a pretty-printer constructor to use for + # this handle/root/etc.'s referent. Usually, we can just omit this argument + # and let GDB choose a pretty-printer for the referent given its type, but + # when the referent is a typedef of an integral type (say, |jsid| in a + # non-|DEBUG| build), the GNU toolchain (at least) loses the typedef name, + # and all we know about the referent is its fundamental integer type --- + # |JS::Rooted<jsid>|, for example, appears in GDB as |JS::Rooted<long>| --- + # and we are left with no way to choose a meaningful pretty-printer based on + # the type of the referent alone. However, because we know that the only + # integer type for which |JS::Rooted| is likely to be instantiated is + # |jsid|, we *can* register a pretty-printer constructor for the full + # instantiation |JS::Rooted<long>|. That constructor creates a |JS::Rooted| + # pretty-printer, and explicitly specifies the constructor for the referent, + # using this initializer's |content_printer| argument. + def __init__(self, value, cache, content_printer=None): + self.value = value + self.cache = cache + self.content_printer = content_printer + + def to_string(self): + ptr = self.value[self.member] + if self.handle: + ptr = ptr.dereference() + if self.strip_typedefs: + ptr = ptr.cast(ptr.type.strip_typedefs()) + if self.content_printer: + return self.content_printer(ptr, self.cache).to_string() + else: + # As of 2012-11, GDB suppresses printing pointers in replacement + # values; see http://sourceware.org/ml/gdb/2012-11/msg00055.html + # That means that simply returning the 'ptr' member won't work. + # Instead, just invoke GDB's formatter ourselves. + return str(ptr) + + +@template_pretty_printer("JS::Rooted") +class Rooted(Common): + strip_typedefs = True + + +@template_pretty_printer("JS::Handle") +class Handle(Common): + handle = True + + +@template_pretty_printer("JS::MutableHandle") +class MutableHandle(Common): + handle = True + + +@template_pretty_printer("js::BarrieredBase") +class BarrieredBase(Common): + member = "value" + + +def deref(root): + # Return the referent of a HeapPtr, Rooted, or Handle. + tag = root.type.strip_typedefs().tag + if not tag: + raise TypeError( + "Can't dereference type with no structure tag: %s" % (root.type,) + ) + elif tag.startswith("js::HeapPtr<"): + return root["value"] + elif tag.startswith("JS::Rooted<"): + return root["ptr"] + elif tag.startswith("JS::Handle<"): + return root["ptr"] + elif tag.startswith("js::GCPtr<"): + return root["value"] + else: + raise NotImplementedError("Unrecognized tag: " + tag) diff --git a/js/src/gdb/mozilla/__init__.py b/js/src/gdb/mozilla/__init__.py new file mode 100644 index 0000000000..7084d5052e --- /dev/null +++ b/js/src/gdb/mozilla/__init__.py @@ -0,0 +1,5 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Yes, Python, this is a package. diff --git a/js/src/gdb/mozilla/asmjs.py b/js/src/gdb/mozilla/asmjs.py new file mode 100644 index 0000000000..85a19a5cfc --- /dev/null +++ b/js/src/gdb/mozilla/asmjs.py @@ -0,0 +1,51 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +""" +In asm code, out-of-bounds heap accesses cause segfaults, which the engine +handles internally. Make GDB ignore them. +""" + +import gdb + +SIGSEGV = 11 + +# A sigaction buffer for each inferior process. +sigaction_buffers = {} + + +def on_stop(event): + if isinstance(event, gdb.SignalEvent) and event.stop_signal == "SIGSEGV": + # Allocate memory for sigaction, once per js shell process. + process = gdb.selected_inferior() + buf = sigaction_buffers.get(process) + if buf is None: + buf = gdb.parse_and_eval( + "(struct sigaction *) malloc(sizeof(struct sigaction))" + ) + sigaction_buffers[process] = buf + + # See if WasmFaultHandler is installed as the SIGSEGV signal action. + sigaction_fn = gdb.parse_and_eval( + "(void(*)(int,void*,void*))__sigaction" + ).dereference() + sigaction_fn(SIGSEGV, 0, buf) + WasmTrapHandler = gdb.parse_and_eval("WasmTrapHandler") + if buf["__sigaction_handler"]["sa_handler"] == WasmTrapHandler: + # Advise the user that magic is happening. + print("js/src/gdb/mozilla/asmjs.py: Allowing WasmTrapHandler to run.") + + # If WasmTrapHandler doesn't handle this segfault, it will unhook + # itself and re-raise. + gdb.execute("continue") + + +def on_exited(event): + if event.inferior in sigaction_buffers: + del sigaction_buffers[event.inferior] + + +def install(): + gdb.events.stop.connect(on_stop) + gdb.events.exited.connect(on_exited) diff --git a/js/src/gdb/mozilla/autoload.py b/js/src/gdb/mozilla/autoload.py new file mode 100644 index 0000000000..bc62e04555 --- /dev/null +++ b/js/src/gdb/mozilla/autoload.py @@ -0,0 +1,41 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# mozilla/autoload.py: Autoload SpiderMonkey pretty-printers. + +print("Loading JavaScript value pretty-printers; see js/src/gdb/README.") +print("If they cause trouble, type: disable pretty-printer .* SpiderMonkey") + +import gdb.printing + +import mozilla.ExecutableAllocator + +# Import the pretty-printer modules. As a side effect, loading these +# modules registers their printers with mozilla.prettyprinters. +import mozilla.GCCellPtr +import mozilla.Interpreter +import mozilla.IonGraph +import mozilla.JSObject +import mozilla.jsop +import mozilla.JSString +import mozilla.JSSymbol +import mozilla.jsval +import mozilla.prettyprinters +import mozilla.PropertyKey +import mozilla.Root +import mozilla.unwind + +# The user may have personal pretty-printers. Get those, too, if they exist. +try: + import my_mozilla_printers # NOQA: F401 +except ImportError: + pass + + +def register(objfile): + # Register our pretty-printers with |objfile|. + lookup = mozilla.prettyprinters.lookup_for_objfile(objfile) + if lookup: + gdb.printing.register_pretty_printer(objfile, lookup, replace=True) + mozilla.unwind.register_unwinder(objfile) diff --git a/js/src/gdb/mozilla/jitsrc.py b/js/src/gdb/mozilla/jitsrc.py new file mode 100644 index 0000000000..d986cdae06 --- /dev/null +++ b/js/src/gdb/mozilla/jitsrc.py @@ -0,0 +1,161 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# This is a gdb extension to automate the process of tracing backwards in rr +# from a jit instruction to the code that generated that instruction. +# +# Usage: +# (rr) x/i $pc +# => 0x240e954ac13a: pushq (%rbx) +# (rr) jitsrc 0x240e954ac13a + +import re + +import gdb + +# (base_name, hops, func_name, source_var, dest_var) tuples, such that : +# - `base_name`: a regex matching the name of the function that implements +# the actual write +# - `hops`: the number of stack frames between `base_name` and `func_name` +# - `func_name`: a regex matching the name of the function that calls memcpy +# - `source_var`: an expression that can be evaluated in the frame +# corresponding to `func_name` to get the source of the memcpy +# - `dest_var`: an expression that can be evaluated in the frame +# corresponding to `func_name` to get the destination of the memcpy +# +# If an invocation of `jitsrc` stops in the middle of a memcpy, the solution +# is normally to add a new pattern here. +patterns = [ + ( + "__memmove", + 1, + "js::jit::X86Encoding::BaseAssembler::executableCopy", + "src", + "dst", + ), + ( + "__memcpy", + 1, + "js::jit::X86Encoding::BaseAssembler::executableCopy", + "src", + "dst", + ), + ( + "__memmove", + 1, + "arena_t::RallocSmallOrLarge", + "aPtr", + "ret", + ), + ("__memcpy", 1, "arena_t::RallocSmallOrLarge", "aPtr", "ret"), + ( + "mozilla::detail::VectorImpl<.*>::new_<.*>", + 3, + "mozilla::Vector<.*>::convertToHeapStorage", + "beginNoCheck()", + "newBuf", + ), + ( + "__memmove", + 1, + "js::jit::AssemblerBufferWithConstantPools", + "&cur->instructions[0]", + "dest", + ), + ( + "__memcpy", + 1, + "js::jit::AssemblerBufferWithConstantPools", + "&cur->instructions[0]", + "dest", + ), + ( + "__memcpy", + 2, + "js::jit::AssemblerX86Shared::executableCopy", + "masm.m_formatter.m_buffer.m_buffer.mBegin", + "buffer", + ), + ("__memcpy", 1, "arena_t::RallocSmallOrLarge", "aPtr", "ret"), + ("js::jit::X86Encoding::SetInt32", 0, "js::jit::X86Encoding::SetInt32", "0", "0"), + ( + "js::jit::X86Encoding::SetPointer", + 0, + "js::jit::X86Encoding::SetPointer", + "0", + "0", + ), + ( + "<unnamed>", + 1, + "js::jit::AssemblerBufferWithConstantPools<.*>::executableCopy", + "&cur->instructions[0]", + "dest", + ), + ("std::__copy_move", 4, "CopySpan", "source.data()", "target.data()"), + ( + "__memmove_(avx|evex)_unaligned_erms", + 1, + "mozilla::detail::EndianUtils::copyAndSwapTo<.*0,.*0", + "aSrc", + "(size_t) aDest", + ), +] + + +class JitSource(gdb.Command): + def __init__(self): + super(JitSource, self).__init__("jitsrc", gdb.COMMAND_RUNNING) + self.dont_repeat() + + def disable_breakpoints(self): + self.disabled_breakpoints = [b for b in gdb.breakpoints() if b.enabled] + for b in self.disabled_breakpoints: + b.enabled = False + + def enable_breakpoints(self): + for b in self.disabled_breakpoints: + b.enabled = True + + def search_stack(self, base_name, hops, name, src, dst, address): + current_frame_name = gdb.newest_frame().name() or "<unnamed>" + if not re.match(base_name, current_frame_name): + return None + f = gdb.newest_frame() + for _ in range(hops): + f = f.older() + if not re.match(name, f.name()): + return None + f.select() + src_val = gdb.parse_and_eval(src) + dst_val = gdb.parse_and_eval(dst) + return hex(src_val + int(address, 16) - dst_val) + + def next_address(self, old): + for pattern in patterns: + found = self.search_stack(*pattern, old) + if found: + return found + return None + + def runback(self, address): + b = gdb.Breakpoint( + "*" + address, type=gdb.BP_WATCHPOINT, wp_class=gdb.WP_WRITE, internal=True + ) + while b.hit_count == 0: + gdb.execute("rc", to_string=True) + b.delete() + + def invoke(self, arg, from_tty): + args = gdb.string_to_argv(arg) + address = args[0] + self.disable_breakpoints() + while address: + self.runback(address) + address = self.next_address(address) + self.enable_breakpoints() + + +# Register to the gdb runtime +JitSource() diff --git a/js/src/gdb/mozilla/jsop.py b/js/src/gdb/mozilla/jsop.py new file mode 100644 index 0000000000..635acb7528 --- /dev/null +++ b/js/src/gdb/mozilla/jsop.py @@ -0,0 +1,60 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Pretty-printers for JSOp and jsbytecode values. + +import gdb +import gdb.types + +import mozilla.prettyprinters +from mozilla.prettyprinters import pretty_printer, ptr_pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + + +class JSOpTypeCache(object): + # Cache information about the JSOp type for this objfile. + def __init__(self, cache): + self.tJSOp = gdb.lookup_type("JSOp") + + @classmethod + def get_or_create(cls, cache): + if not cache.mod_JSOp: + cache.mod_JSOp = cls(cache) + return cache.mod_JSOp + + +@pretty_printer("JSOp") +class JSOp(object): + def __init__(self, value, cache): + self.value = value + self.cache = cache + self.jotc = JSOpTypeCache.get_or_create(cache) + + def to_string(self): + # JSOp's storage type is |uint8_t|, but gdb uses a signed value. + # Manually convert it to an unsigned value. + # + # https://sourceware.org/bugzilla/show_bug.cgi?id=25325 + idx = int(self.value.cast(self.jotc.tJSOp.target())) + assert 0 <= idx and idx <= 255 + fields = self.jotc.tJSOp.fields() + if idx < len(fields): + return fields[idx].name + return "(JSOp) {:d}".format(idx) + + +@ptr_pretty_printer("jsbytecode") +class JSBytecodePtr(mozilla.prettyprinters.Pointer): + def __init__(self, value, cache): + super(JSBytecodePtr, self).__init__(value, cache) + self.jotc = JSOpTypeCache.get_or_create(cache) + + def to_string(self): + try: + opcode = str(self.value.dereference().cast(self.jotc.tJSOp)) + except Exception: + opcode = "bad pc" + return "{} ({})".format(self.value.cast(self.cache.void_ptr_t), opcode) diff --git a/js/src/gdb/mozilla/jsval.py b/js/src/gdb/mozilla/jsval.py new file mode 100644 index 0000000000..ffabf6004f --- /dev/null +++ b/js/src/gdb/mozilla/jsval.py @@ -0,0 +1,188 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Pretty-printers for SpiderMonkey's JS::Value. + +import struct + +import gdb +import gdb.types + +import mozilla.prettyprinters +from mozilla.prettyprinters import pretty_printer + +# Forget any printers from previous loads of this module. +mozilla.prettyprinters.clear_module_printers(__name__) + +# See Value.h [SMDOC] JS::Value Boxing Formats for details on the JS::Value boxing +# formats handled below. + + +class Box(object): + def __init__(self, asBits, jtc): + self.asBits = asBits + self.jtc = jtc + # Value::asBits is uint64_t, but somehow the sign bit can be botched + # here, even though Python integers are arbitrary precision. + if self.asBits < 0: + self.asBits = self.asBits + (1 << 64) + + # Return this value's type tag. + def tag(self): + raise NotImplementedError + + # Return this value as a 32-bit integer, double, or address. + def as_uint32(self): + raise NotImplementedError + + def as_double(self): + packed = struct.pack("q", self.asBits) + (unpacked,) = struct.unpack("d", packed) + return unpacked + + def as_address(self): + raise NotImplementedError + + +class Punbox(Box): + # Packed non-number boxing --- the format used on x86_64. It would be nice to + # simply call Value::toInt32, etc. here, but the debugger is likely to see many + # Values, and doing several inferior calls for each one seems like a bad idea. + + FULL_WIDTH = 64 + TAG_SHIFT = 47 + PAYLOAD_MASK = (1 << TAG_SHIFT) - 1 + TAG_MASK = (1 << (FULL_WIDTH - TAG_SHIFT)) - 1 + TAG_MAX_DOUBLE = 0x1FFF0 + TAG_TYPE_MASK = 0x0000F + + def tag(self): + tag = self.asBits >> Punbox.TAG_SHIFT + if tag <= Punbox.TAG_MAX_DOUBLE: + return self.jtc.DOUBLE + else: + return tag & Punbox.TAG_TYPE_MASK + + def as_uint32(self): + return int(self.asBits & ((1 << 32) - 1)) + + def as_address(self): + return gdb.Value(self.asBits & Punbox.PAYLOAD_MASK) + + +class Nunbox(Box): + TAG_SHIFT = 32 + TAG_CLEAR = 0xFFFF0000 + PAYLOAD_MASK = 0xFFFFFFFF + TAG_TYPE_MASK = 0x0000000F + + def tag(self): + tag = self.asBits >> Nunbox.TAG_SHIFT + if tag < Nunbox.TAG_CLEAR: + return self.jtc.DOUBLE + return tag & Nunbox.TAG_TYPE_MASK + + def as_uint32(self): + return int(self.asBits & Nunbox.PAYLOAD_MASK) + + def as_address(self): + return gdb.Value(self.asBits & Nunbox.PAYLOAD_MASK) + + +class JSValueTypeCache(object): + # Cache information about the Value type for this objfile. + + def __init__(self, cache): + # Capture the tag values. + d = gdb.types.make_enum_dict(gdb.lookup_type("JSValueType")) + + # The enum keys are prefixed when building with some compilers (clang at + # a minimum), so use a helper function to handle either key format. + def get(key): + val = d.get(key) + if val is not None: + return val + return d["JSValueType::" + key] + + self.DOUBLE = get("JSVAL_TYPE_DOUBLE") + self.INT32 = get("JSVAL_TYPE_INT32") + self.UNDEFINED = get("JSVAL_TYPE_UNDEFINED") + self.BOOLEAN = get("JSVAL_TYPE_BOOLEAN") + self.MAGIC = get("JSVAL_TYPE_MAGIC") + self.STRING = get("JSVAL_TYPE_STRING") + self.SYMBOL = get("JSVAL_TYPE_SYMBOL") + self.BIGINT = get("JSVAL_TYPE_BIGINT") + self.NULL = get("JSVAL_TYPE_NULL") + self.OBJECT = get("JSVAL_TYPE_OBJECT") + + # Let self.magic_names be an array whose i'th element is the name of + # the i'th magic value. + d = gdb.types.make_enum_dict(gdb.lookup_type("JSWhyMagic")) + self.magic_names = list(range(max(d.values()) + 1)) + for k, v in d.items(): + self.magic_names[v] = k + + # Choose an unboxing scheme for this architecture. + self.boxer = Punbox if cache.void_ptr_t.sizeof == 8 else Nunbox + + +@pretty_printer("JS::Value") +class JSValue(object): + def __init__(self, value, cache): + # Save the generic typecache, and create our own, if we haven't already. + self.cache = cache + if not cache.mod_JS_Value: + cache.mod_JS_Value = JSValueTypeCache(cache) + self.jtc = cache.mod_JS_Value + + self.value = value + self.box = self.jtc.boxer(value["asBits_"], self.jtc) + + def to_string(self): + tag = self.box.tag() + + if tag == self.jtc.UNDEFINED: + return "$JS::UndefinedValue()" + if tag == self.jtc.NULL: + return "$JS::NullValue()" + if tag == self.jtc.BOOLEAN: + return "$JS::BooleanValue(%s)" % str(self.box.as_uint32() != 0).lower() + if tag == self.jtc.MAGIC: + value = self.box.as_uint32() + if 0 <= value and value < len(self.jtc.magic_names): + return "$JS::MagicValue(%s)" % (self.jtc.magic_names[value],) + else: + return "$JS::MagicValue(%d)" % (value,) + + if tag == self.jtc.INT32: + value = self.box.as_uint32() + signbit = 1 << 31 + value = (value ^ signbit) - signbit + return "$JS::Int32Value(%s)" % value + + if tag == self.jtc.DOUBLE: + return "$JS::DoubleValue(%s)" % self.box.as_double() + + if tag == self.jtc.STRING: + value = self.box.as_address().cast(self.cache.JSString_ptr_t) + elif tag == self.jtc.OBJECT: + value = self.box.as_address().cast(self.cache.JSObject_ptr_t) + elif tag == self.jtc.SYMBOL: + value = self.box.as_address().cast(self.cache.JSSymbol_ptr_t) + elif tag == self.jtc.BIGINT: + return "$JS::BigIntValue()" + else: + value = "unrecognized!" + return "$JS::Value(%s)" % (value,) + + def is_undefined(self): + return self.box.tag() == self.jtc.UNDEFINED + + def get_string(self): + assert self.box.tag() == self.jtc.STRING + return self.box.as_address().cast(self.cache.JSString_ptr_t) + + def get_private(self): + assert self.box.tag() == self.jtc.DOUBLE + return self.box.asBits diff --git a/js/src/gdb/mozilla/prettyprinters.py b/js/src/gdb/mozilla/prettyprinters.py new file mode 100644 index 0000000000..b533d1f1f5 --- /dev/null +++ b/js/src/gdb/mozilla/prettyprinters.py @@ -0,0 +1,446 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# mozilla/prettyprinters.py --- infrastructure for SpiderMonkey's auto-loaded pretty-printers. + +import re + +import gdb + +# Decorators for declaring pretty-printers. +# +# In each case, the decoratee should be a SpiderMonkey-style pretty-printer +# factory, taking both a gdb.Value instance and a TypeCache instance as +# arguments; see TypeCache, below. + + +def check_for_reused_pretty_printer(fn): + # Check that |fn| hasn't been registered as a pretty-printer under some + # other name already. (The 'enabled' flags used by GDB's + # 'enable/disable/info pretty-printer' commands are simply stored as + # properties of the function objects themselves, so a single function + # object can't carry the 'enabled' flags for two different printers.) + if hasattr(fn, "enabled"): + raise RuntimeError("pretty-printer function %r registered more than once" % fn) + + +# a dictionary mapping gdb.Type tags to pretty-printer functions. +printers_by_tag = {} + +# A decorator: add the decoratee as a pretty-printer lookup function for types +# named |type_name|. + + +def pretty_printer(type_name): + def add(fn): + check_for_reused_pretty_printer(fn) + add_to_subprinter_list(fn, type_name) + printers_by_tag[type_name] = fn + return fn + + return add + + +# a dictionary mapping gdb.Type tags to pretty-printer functions for pointers to +# that type. +ptr_printers_by_tag = {} + +# A decorator: add the decoratee as a pretty-printer lookup function for +# pointers to types named |type_name|. + + +def ptr_pretty_printer(type_name): + def add(fn): + check_for_reused_pretty_printer(fn) + add_to_subprinter_list(fn, "ptr-to-" + type_name) + ptr_printers_by_tag[type_name] = fn + return fn + + return add + + +# a dictionary mapping gdb.Type tags to pretty-printer functions for +# references to that type. +ref_printers_by_tag = {} + +# A decorator: add the decoratee as a pretty-printer lookup function for +# references to instances of types named |type_name|. + + +def ref_pretty_printer(type_name): + def add(fn): + check_for_reused_pretty_printer(fn) + add_to_subprinter_list(fn, "ref-to-" + type_name) + ref_printers_by_tag[type_name] = fn + return fn + + return add + + +# a dictionary mapping the template name portion of gdb.Type tags to +# pretty-printer functions for instantiations of that template. +template_printers_by_tag = {} + +# A decorator: add the decoratee as a pretty-printer lookup function for +# instantiations of templates named |template_name|. + + +def template_pretty_printer(template_name): + def add(fn): + check_for_reused_pretty_printer(fn) + add_to_subprinter_list(fn, "instantiations-of-" + template_name) + template_printers_by_tag[template_name] = fn + return fn + + return add + + +# A list of (REGEXP, PRINTER) pairs, such that if REGEXP (a RegexObject) +# matches the result of converting a gdb.Value's type to a string, then +# PRINTER is a pretty-printer lookup function that will probably like that +# value. +printers_by_regexp = [] + +# A decorator: add the decoratee as a pretty-printer factory for types +# that, when converted to a string, match |pattern|. Use |name| as the +# pretty-printer's name, when listing, enabling and disabling. + + +def pretty_printer_for_regexp(pattern, name): + compiled = re.compile(pattern) + + def add(fn): + check_for_reused_pretty_printer(fn) + add_to_subprinter_list(fn, name) + printers_by_regexp.append((compiled, fn)) + return fn + + return add + + +# Forget all pretty-printer lookup functions defined in the module name +# |module_name|, if any exist. Use this at the top of each pretty-printer +# module like this: +# +# clear_module_printers(__name__) + + +def clear_module_printers(module_name): + global printers_by_tag, ptr_printers_by_tag, ref_printers_by_tag + global template_printers_by_tag, printers_by_regexp + + # Remove all pretty-printers defined in the module named |module_name| + # from d. + def clear_dictionary(d): + # Walk the dictionary, building a list of keys whose entries we + # should remove. (It's not safe to delete entries from a dictionary + # while we're iterating over it.) + to_delete = [] + for k, v in d.items(): + if v.__module__ == module_name: + to_delete.append(k) + remove_from_subprinter_list(v) + for k in to_delete: + del d[k] + + clear_dictionary(printers_by_tag) + clear_dictionary(ptr_printers_by_tag) + clear_dictionary(ref_printers_by_tag) + clear_dictionary(template_printers_by_tag) + + # Iterate over printers_by_regexp, deleting entries from the given module. + new_list = [] + for p in printers_by_regexp: + if p.__module__ == module_name: + remove_from_subprinter_list(p) + else: + new_list.append(p) + printers_by_regexp = new_list + + +# Our subprinters array. The 'subprinters' attributes of all lookup +# functions returned by lookup_for_objfile point to this array instance, +# which we mutate as subprinters are added and removed. +subprinters = [] + +# Set up the 'name' and 'enabled' attributes on |subprinter|, and add it to our +# list of all SpiderMonkey subprinters. + + +def add_to_subprinter_list(subprinter, name): + subprinter.name = name + subprinter.enabled = True + subprinters.append(subprinter) + + +# Remove |subprinter| from our list of all SpiderMonkey subprinters. + + +def remove_from_subprinter_list(subprinter): + subprinters.remove(subprinter) + + +# An exception class meaning, "This objfile has no SpiderMonkey in it." + + +class NotSpiderMonkeyObjfileError(TypeError): + pass + + +# TypeCache: a cache for frequently used information about an objfile. +# +# When a new SpiderMonkey objfile is loaded, we construct an instance of +# this class for it. Then, whenever we construct a pretty-printer for some +# gdb.Value, we also pass, as a second argument, the TypeCache for the +# objfile to which that value's type belongs. +# +# if objfile doesn't seem to have SpiderMonkey code in it, the constructor +# raises NotSpiderMonkeyObjfileError. +# +# Pretty-printer modules may add attributes to this to hold their own +# cached values. Such attributes should be named mod_NAME, where the module +# is named mozilla.NAME; for example, mozilla.JSString should store its +# metadata in the TypeCache's mod_JSString attribute. + + +class TypeCache(object): + def __init__(self, objfile): + self.objfile = objfile + + # Unfortunately, the Python interface doesn't allow us to specify + # the objfile in whose scope lookups should occur. But simply + # knowing that we need to lookup the types afresh is probably + # enough. + self.void_t = gdb.lookup_type("void") + self.void_ptr_t = self.void_t.pointer() + self.uintptr_t = gdb.lookup_type("uintptr_t") + try: + self.JSString_ptr_t = gdb.lookup_type("JSString").pointer() + self.JSSymbol_ptr_t = gdb.lookup_type("JS::Symbol").pointer() + self.JSObject_ptr_t = gdb.lookup_type("JSObject").pointer() + except gdb.error: + raise NotSpiderMonkeyObjfileError + + self.mod_GCCellPtr = None + self.mod_Interpreter = None + self.mod_JSObject = None + self.mod_JSOp = None + self.mod_JSString = None + self.mod_JS_Value = None + self.mod_ExecutableAllocator = None + self.mod_IonGraph = None + + +# Yield a series of all the types that |t| implements, by following typedefs +# and iterating over base classes. Specifically: +# - |t| itself is the first value yielded. +# - If we yield a typedef, we later yield its definition. +# - If we yield a type with base classes, we later yield those base classes. +# - If we yield a type with some base classes that are typedefs, +# we yield all the type's base classes before following the typedefs. +# +# This is a hokey attempt to order the implemented types by meaningfulness when +# pretty-printed. Perhaps it is entirely misguided, and we should actually +# collect all applicable pretty-printers, and then use some ordering on the +# pretty-printers themselves. +# +# We may yield a type more than once (say, if it appears more than once in the +# class hierarchy). + + +def implemented_types(t): + # Yield all types that follow |t|. + def followers(t): + if t.code == gdb.TYPE_CODE_TYPEDEF: + yield t.target() + for t2 in followers(t.target()): + yield t2 + elif is_struct_or_union(t): + base_classes = [] + for f in t.fields(): + if f.is_base_class: + yield f.type + base_classes.append(f.type) + for b in base_classes: + for t2 in followers(b): + yield t2 + + yield t + for t2 in followers(t): + yield t2 + + +template_regexp = re.compile("([\w_:]+)<") + + +def is_struct_or_union(t): + return t.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION) + + +def is_struct_or_union_or_enum(t): + return t.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION, gdb.TYPE_CODE_ENUM) + + +# Construct and return a pretty-printer lookup function for objfile, or +# return None if the objfile doesn't contain SpiderMonkey code +# (specifically, definitions for SpiderMonkey types). + + +def lookup_for_objfile(objfile): + # Create a type cache for this objfile. + try: + cache = TypeCache(objfile) + except NotSpiderMonkeyObjfileError: + if gdb.parameter("verbose"): + gdb.write( + "objfile '%s' has no SpiderMonkey code; not registering pretty-printers\n" + % (objfile.filename,) + ) + return None + + # Return a pretty-printer for |value|, if we have one. This is the lookup + # function object we place in each gdb.Objfile's pretty-printers list, so it + # carries |name|, |enabled|, and |subprinters| attributes. + def lookup(value): + # If |table| has a pretty-printer for |tag|, apply it to |value|. + def check_table(table, tag): + if tag in table: + f = table[tag] + if f.enabled: + return f(value, cache) + return None + + def check_table_by_type_name(table, t): + if t.code == gdb.TYPE_CODE_TYPEDEF: + return check_table(table, str(t)) + elif is_struct_or_union_or_enum(t) and t.tag: + return check_table(table, t.tag) + else: + return None + + for t in implemented_types(value.type): + if t.code == gdb.TYPE_CODE_PTR: + for t2 in implemented_types(t.target()): + p = check_table_by_type_name(ptr_printers_by_tag, t2) + if p: + return p + elif t.code == gdb.TYPE_CODE_REF: + for t2 in implemented_types(t.target()): + p = check_table_by_type_name(ref_printers_by_tag, t2) + if p: + return p + else: + p = check_table_by_type_name(printers_by_tag, t) + if p: + return p + if is_struct_or_union(t) and t.tag: + m = template_regexp.match(t.tag) + if m: + p = check_table(template_printers_by_tag, m.group(1)) + if p: + return p + + # Failing that, look for a printer in printers_by_regexp. We have + # to scan the whole list, so regexp printers should be used + # sparingly. + s = str(value.type) + for r, f in printers_by_regexp: + if f.enabled: + m = r.match(s) + if m: + p = f(value, cache) + if p: + return p + + # No luck. + return None + + # Give |lookup| the attributes expected of a pretty-printer with + # subprinters, for enabling and disabling. + lookup.name = "SpiderMonkey" + lookup.enabled = True + lookup.subprinters = subprinters + + return lookup + + +# A base class for pretty-printers for pointer values that handles null +# pointers, by declining to construct a pretty-printer for them at all. +# Derived classes may simply assume that self.value is non-null. +# +# To help share code, this class can also be used with reference types. +# +# This class provides the following methods, which subclasses are free to +# override: +# +# __init__(self, value, cache): Save value and cache as properties by those names +# on the instance. +# +# to_string(self): format the type's name and address, as GDB would, and then +# call a 'summary' method (which the subclass must define) to produce a +# description of the referent. +# +# Note that pretty-printers returning a 'string' display hint must not use +# this default 'to_string' method, as GDB will take everything it returns, +# including the type name and address, as string contents. + + +class Pointer(object): + def __new__(cls, value, cache): + # Don't try to provide pretty-printers for NULL pointers. + if value.type.strip_typedefs().code == gdb.TYPE_CODE_PTR and value == 0: + return None + return super(Pointer, cls).__new__(cls) + + def __init__(self, value, cache): + self.value = value + self.cache = cache + + def to_string(self): + # See comment above. + assert not hasattr(self, "display_hint") or self.display_hint() != "string" + concrete_type = self.value.type.strip_typedefs() + if concrete_type.code == gdb.TYPE_CODE_PTR: + address = self.value.cast(self.cache.void_ptr_t) + elif concrete_type.code == gdb.TYPE_CODE_REF: + address = "@" + str(self.value.address.cast(self.cache.void_ptr_t)) + else: + assert not "mozilla.prettyprinters.Pointer applied to bad value type" + try: + summary = self.summary() + except gdb.MemoryError as r: + summary = str(r) + v = "(%s) %s %s" % (self.value.type, address, summary) + return v + + def summary(self): + raise NotImplementedError + + +field_enum_value = None + +# Given |t|, a gdb.Type instance representing an enum type, return the +# numeric value of the enum value named |name|. +# +# Pre-2012-4-18 versions of GDB store the value of an enum member on the +# gdb.Field's 'bitpos' attribute; later versions store it on the 'enumval' +# attribute. This function retrieves the value from either. + + +def enum_value(t, name): + global field_enum_value + f = t[name] + # Monkey-patching is a-okay in polyfills! Just because. + if not field_enum_value: + if hasattr(f, "enumval"): + + def field_enum_value(f): + return f.enumval + + else: + + def field_enum_value(f): + return f.bitpos + + return field_enum_value(f) diff --git a/js/src/gdb/mozilla/unwind.py b/js/src/gdb/mozilla/unwind.py new file mode 100644 index 0000000000..8ad4393b7a --- /dev/null +++ b/js/src/gdb/mozilla/unwind.py @@ -0,0 +1,596 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# mozilla/unwind.py --- unwinder and frame filter for SpiderMonkey + +import platform + +import gdb +import gdb.types +from gdb.FrameDecorator import FrameDecorator + +from mozilla.JSObject import get_function_name, get_function_script +from mozilla.prettyprinters import TypeCache + +# For ease of use in Python 2, we use "long" instead of "int" +# everywhere. +try: + long +except NameError: + long = int + +# The Python 3 |map| built-in works lazily, but in Python 2 we need +# itertools.imap to get this. +try: + from itertools import imap +except ImportError: + imap = map + +_have_unwinder = True +try: + from gdb.unwinder import Unwinder +except ImportError: + _have_unwinder = False + # We need something here; it doesn't matter what as no unwinder + # will ever be instantiated. + Unwinder = object + + +def debug(something): + # print("@@ " + something) + pass + + +# Maps frametype enum base names to corresponding class. +SizeOfFramePrefix = { + "FrameType::IonJS": "ExitFrameLayout", + "FrameType::BaselineJS": "JitFrameLayout", + "FrameType::BaselineStub": "BaselineStubFrameLayout", + "FrameType::CppToJSJit": "JitFrameLayout", + "FrameType::WasmToJSJit": "JitFrameLayout", + "FrameType::JSJitToWasm": "JitFrameLayout", + "FrameType::Rectifier": "RectifierFrameLayout", + "FrameType::IonAccessorIC": "IonAccessorICFrameLayout", + "FrameType::IonICCall": "IonICCallFrameLayout", + "FrameType::Exit": "ExitFrameLayout", + "FrameType::Bailout": "JitFrameLayout", +} + + +# We cannot have semi-colon as identifier names, so use a colon instead, +# and forward the name resolution to the type cache class. +class UnwinderTypeCacheFrameType(object): + def __init__(self, tc): + self.tc = tc + + def __getattr__(self, name): + return self.tc.__getattr__("FrameType::" + name) + + +class UnwinderTypeCache(TypeCache): + # All types and symbols that we need are attached to an object that we + # can dispose of as needed. + + def __init__(self): + self.d = None + self.frame_enum_names = {} + self.frame_class_types = {} + super(UnwinderTypeCache, self).__init__(None) + + # We take this bizarre approach to defer trying to look up any + # symbols until absolutely needed. Without this, the loading + # approach taken by the gdb-tests would cause spurious exceptions. + def __getattr__(self, name): + if self.d is None: + self.initialize() + if name == "frame_type": + return UnwinderTypeCacheFrameType(self) + if name not in self.d: + return None + return self.d[name] + + def value(self, name): + return long(gdb.lookup_symbol(name)[0].value()) + + def jit_value(self, name): + return self.value("js::jit::" + name) + + def initialize(self): + self.d = {} + self.d["FRAMETYPE_MASK"] = (1 << self.jit_value("FRAMETYPE_BITS")) - 1 + self.d["FRAMESIZE_SHIFT"] = self.jit_value("FRAMESIZE_SHIFT") + self.d["FRAME_HEADER_SIZE_SHIFT"] = self.jit_value("FRAME_HEADER_SIZE_SHIFT") + self.d["FRAME_HEADER_SIZE_MASK"] = self.jit_value("FRAME_HEADER_SIZE_MASK") + + self.compute_frame_info() + commonFrameLayout = gdb.lookup_type("js::jit::CommonFrameLayout") + self.d["typeCommonFrameLayout"] = commonFrameLayout + self.d["typeCommonFrameLayoutPointer"] = commonFrameLayout.pointer() + self.d["per_tls_context"] = gdb.lookup_global_symbol("js::TlsContext") + + self.d["void_starstar"] = gdb.lookup_type("void").pointer().pointer() + + jitframe = gdb.lookup_type("js::jit::JitFrameLayout") + self.d["jitFrameLayoutPointer"] = jitframe.pointer() + + self.d["CalleeToken_Function"] = self.jit_value("CalleeToken_Function") + self.d["CalleeToken_FunctionConstructing"] = self.jit_value( + "CalleeToken_FunctionConstructing" + ) + self.d["CalleeToken_Script"] = self.jit_value("CalleeToken_Script") + self.d["JSScript"] = gdb.lookup_type("JSScript").pointer() + self.d["Value"] = gdb.lookup_type("JS::Value") + + self.d["SOURCE_SLOT"] = self.value("js::ScriptSourceObject::SOURCE_SLOT") + self.d["NativeObject"] = gdb.lookup_type("js::NativeObject").pointer() + self.d["HeapSlot"] = gdb.lookup_type("js::HeapSlot").pointer() + self.d["ScriptSource"] = gdb.lookup_type("js::ScriptSource").pointer() + + # ProcessExecutableMemory, used to identify if a pc is in the section + # pre-allocated by the JIT. + self.d["MaxCodeBytesPerProcess"] = self.jit_value("MaxCodeBytesPerProcess") + self.d["execMemory"] = gdb.lookup_symbol("::execMemory")[0].value() + + # Compute maps related to jit frames. + def compute_frame_info(self): + t = gdb.lookup_type("enum js::jit::FrameType") + for field in t.fields(): + # Strip off "js::jit::", remains: "FrameType::*". + name = field.name[9:] + enumval = long(field.enumval) + self.d[name] = enumval + self.frame_enum_names[enumval] = name + class_type = gdb.lookup_type("js::jit::" + SizeOfFramePrefix[name]) + self.frame_class_types[enumval] = class_type.pointer() + + +class FrameSymbol(object): + "A symbol/value pair as expected from gdb frame decorators." + + def __init__(self, sym, val): + self.sym = sym + self.val = val + + def symbol(self): + return self.sym + + def value(self): + return self.val + + +class JitFrameDecorator(FrameDecorator): + """This represents a single JIT frame for the purposes of display. + That is, the frame filter creates instances of this when it sees a + JIT frame in the stack.""" + + def __init__(self, base, info, cache): + super(JitFrameDecorator, self).__init__(base) + self.info = info + self.cache = cache + + def _decode_jitframe(self, this_frame): + calleetoken = long(this_frame["calleeToken_"]) + tag = calleetoken & 3 + calleetoken = calleetoken ^ tag + function = None + script = None + if ( + tag == self.cache.CalleeToken_Function + or tag == self.cache.CalleeToken_FunctionConstructing + ): + value = gdb.Value(calleetoken) + function = get_function_name(value, self.cache) + script = get_function_script(value, self.cache) + elif tag == self.cache.CalleeToken_Script: + script = gdb.Value(calleetoken).cast(self.cache.JSScript) + return {"function": function, "script": script} + + def function(self): + if self.info["name"] is None: + return FrameDecorator.function(self) + name = self.info["name"] + result = "<<" + name + # If we have a frame, we can extract the callee information + # from it for display here. + this_frame = self.info["this_frame"] + if this_frame is not None: + if gdb.types.has_field(this_frame.type.target(), "calleeToken_"): + function = self._decode_jitframe(this_frame)["function"] + if function is not None: + result = result + " " + function + return result + ">>" + + def filename(self): + this_frame = self.info["this_frame"] + if this_frame is not None: + if gdb.types.has_field(this_frame.type.target(), "calleeToken_"): + script = self._decode_jitframe(this_frame)["script"] + if script is not None: + obj = script["sourceObject_"]["value"] + # Verify that this is a ScriptSource object. + # FIXME should also deal with wrappers here. + nativeobj = obj.cast(self.cache.NativeObject) + # See bug 987069 and despair. At least this + # approach won't give exceptions. + class_name = nativeobj["group_"]["value"]["clasp_"]["name"].string( + "ISO-8859-1" + ) + if class_name != "ScriptSource": + return FrameDecorator.filename(self) + scriptsourceobj = (nativeobj + 1).cast(self.cache.HeapSlot)[ + self.cache.SOURCE_SLOT + ] + scriptsource = scriptsourceobj["value"]["asBits_"] << 1 + scriptsource = scriptsource.cast(self.cache.ScriptSource) + return scriptsource["filename_"]["mTuple"]["mFirstA"].string() + return FrameDecorator.filename(self) + + def frame_args(self): + this_frame = self.info["this_frame"] + if this_frame is None: + return FrameDecorator.frame_args(self) + if not gdb.types.has_field(this_frame.type.target(), "numActualArgs_"): + return FrameDecorator.frame_args(self) + # See if this is a function call. + if self._decode_jitframe(this_frame)["function"] is None: + return FrameDecorator.frame_args(self) + # Construct and return an iterable of all the arguments. + result = [] + num_args = long(this_frame["numActualArgs_"]) + # Sometimes we see very large values here, so truncate it to + # bypass the damage. + if num_args > 10: + num_args = 10 + args_ptr = (this_frame + 1).cast(self.cache.Value.pointer()) + for i in range(num_args + 1): + # Synthesize names, since there doesn't seem to be + # anything better to do. + if i == 0: + name = "this" + else: + name = "arg%d" % i + result.append(FrameSymbol(name, args_ptr[i])) + return result + + +class SpiderMonkeyFrameFilter(object): + "A frame filter for SpiderMonkey." + + # |state_holder| is either None, or an instance of + # SpiderMonkeyUnwinder. If the latter, then this class will + # reference the |unwinder_state| attribute to find the current + # unwinder state. + def __init__(self, cache, state_holder): + self.name = "SpiderMonkey" + self.enabled = True + self.priority = 100 + self.state_holder = state_holder + self.cache = cache + + def maybe_wrap_frame(self, frame): + if self.state_holder is None or self.state_holder.unwinder_state is None: + return frame + base = frame.inferior_frame() + info = self.state_holder.unwinder_state.get_frame(base) + if info is None: + return frame + return JitFrameDecorator(frame, info, self.cache) + + def filter(self, frame_iter): + return imap(self.maybe_wrap_frame, frame_iter) + + +class SpiderMonkeyFrameId(object): + "A frame id class, as specified by the gdb unwinder API." + + def __init__(self, sp, pc): + self.sp = sp + self.pc = pc + + +class UnwinderState(object): + """This holds all the state needed during a given unwind. Each time a + new unwind is done, a new instance of this class is created. It + keeps track of all the state needed to unwind JIT frames. Note that + this class is not directly instantiated. + + This is a base class, and must be specialized for each target + architecture, both because we need to use arch-specific register + names, and because entry frame unwinding is arch-specific. + See https://sourceware.org/bugzilla/show_bug.cgi?id=19286 for info + about the register name issue. + + Each subclass must define SP_REGISTER, PC_REGISTER, and + SENTINEL_REGISTER (see x64UnwinderState for info); and implement + unwind_entry_frame_registers.""" + + def __init__(self, typecache): + self.next_sp = None + self.next_type = None + self.activation = None + # An unwinder instance is specific to a thread. Record the + # selected thread for later verification. + self.thread = gdb.selected_thread() + self.frame_map = {} + self.typecache = typecache + + # If the given gdb.Frame was created by this unwinder, return the + # corresponding informational dictionary for the frame. + # Otherwise, return None. This is used by the frame filter to + # display extra information about the frame. + def get_frame(self, frame): + sp = long(frame.read_register(self.SP_REGISTER)) + if sp in self.frame_map: + return self.frame_map[sp] + return None + + # Add information about a frame to the frame map. This map is + # queried by |self.get_frame|. |sp| is the frame's stack pointer, + # and |name| the frame's type as a string, e.g. "FrameType::Exit". + def add_frame(self, sp, name=None, this_frame=None): + self.frame_map[long(sp)] = {"name": name, "this_frame": this_frame} + + # See whether |pc| is claimed by the Jit. + def is_jit_address(self, pc): + execMem = self.typecache.execMemory + base = long(execMem["base_"]) + length = self.typecache.MaxCodeBytesPerProcess + + # If the base pointer is null, then no memory got allocated yet. + if long(base) == 0: + return False + + # If allocated, then we allocated MaxCodeBytesPerProcess. + return base <= pc and pc < base + length + + # Check whether |self| is valid for the selected thread. + def check(self): + return gdb.selected_thread() is self.thread + + # Essentially js::TlsContext.get(). + def get_tls_context(self): + return self.typecache.per_tls_context.value()["mValue"] + + # |common| is a pointer to a CommonFrameLayout object. Return a + # tuple (local_size, header_size, frame_type), where |size| is the + # integer size of the previous frame's locals; |header_size| is + # the size of this frame's header; and |frame_type| is an integer + # representing the previous frame's type. + def unpack_descriptor(self, common): + value = long(common["descriptor_"]) + local_size = value >> self.typecache.FRAMESIZE_SHIFT + header_size = ( + value >> self.typecache.FRAME_HEADER_SIZE_SHIFT + ) & self.typecache.FRAME_HEADER_SIZE_MASK + header_size = header_size * self.typecache.void_starstar.sizeof + frame_type = long(value & self.typecache.FRAMETYPE_MASK) + if frame_type == self.typecache.frame_type.CppToJSJit: + # Trampoline-x64.cpp pushes a JitFrameLayout object, but + # the stack pointer is actually adjusted as if a + # CommonFrameLayout object was pushed. + header_size = self.typecache.typeCommonFrameLayout.sizeof + return (local_size, header_size, frame_type) + + # Create a new frame for gdb. This makes a new unwind info object + # and fills it in, then returns it. It also registers any + # pertinent information with the frame filter for later display. + # + # |pc| is the PC from the pending frame + # |sp| is the stack pointer to use + # |frame| points to the CommonFrameLayout object + # |frame_type| is a integer, one of the |enum FrameType| values, + # describing the current frame. + # |pending_frame| is the pending frame (see the gdb unwinder + # documentation). + def create_frame(self, pc, sp, frame, frame_type, pending_frame): + # Make a frame_id that claims that |frame| is sort of like a + # frame pointer for this frame. + frame_id = SpiderMonkeyFrameId(frame, pc) + + # Read the frame layout object to find the next such object. + # This lets us unwind the necessary registers for the next + # frame, and also update our internal state to match. + common = frame.cast(self.typecache.typeCommonFrameLayoutPointer) + next_pc = common["returnAddress_"] + (local_size, header_size, next_type) = self.unpack_descriptor(common) + next_sp = frame + header_size + local_size + + # Compute the type of the next oldest frame's descriptor. + this_class_type = self.typecache.frame_class_types[frame_type] + this_frame = frame.cast(this_class_type) + + # Register this frame so the frame filter can find it. This + # is registered using SP because we don't have any other good + # approach -- you can't get the frame id from a gdb.Frame. + # https://sourceware.org/bugzilla/show_bug.cgi?id=19800 + frame_name = self.typecache.frame_enum_names[frame_type] + self.add_frame(sp, name=frame_name, this_frame=this_frame) + + # Update internal state for the next unwind. + self.next_sp = next_sp + self.next_type = next_type + + unwind_info = pending_frame.create_unwind_info(frame_id) + unwind_info.add_saved_register(self.PC_REGISTER, next_pc) + unwind_info.add_saved_register(self.SP_REGISTER, next_sp) + # FIXME it would be great to unwind any other registers here. + return unwind_info + + # Unwind an "ordinary" JIT frame. This is used for JIT frames + # other than enter and exit frames. Returns the newly-created + # unwind info for gdb. + def unwind_ordinary(self, pc, pending_frame): + return self.create_frame( + pc, self.next_sp, self.next_sp, self.next_type, pending_frame + ) + + # Unwind an exit frame. Returns None if this cannot be done; + # otherwise returns the newly-created unwind info for gdb. + def unwind_exit_frame(self, pc, pending_frame): + if self.activation == 0: + # Reached the end of the list. + return None + elif self.activation is None: + cx = self.get_tls_context() + self.activation = cx["jitActivation"]["value"] + else: + self.activation = self.activation["prevJitActivation_"] + + packedExitFP = self.activation["packedExitFP_"] + if packedExitFP == 0: + return None + + exit_sp = pending_frame.read_register(self.SP_REGISTER) + frame_type = self.typecache.frame_type.Exit + return self.create_frame(pc, exit_sp, packedExitFP, frame_type, pending_frame) + + # A wrapper for unwind_entry_frame_registers that handles + # architecture-independent boilerplate. + def unwind_entry_frame(self, pc, pending_frame): + sp = self.next_sp + # Notify the frame filter. + self.add_frame(sp, name="FrameType::CppToJSJit") + # Make an unwind_info for the per-architecture code to fill in. + frame_id = SpiderMonkeyFrameId(sp, pc) + unwind_info = pending_frame.create_unwind_info(frame_id) + self.unwind_entry_frame_registers(sp, unwind_info) + self.next_sp = None + self.next_type = None + return unwind_info + + # The main entry point that is called to try to unwind a JIT frame + # of any type. Returns None if this cannot be done; otherwise + # returns the newly-created unwind info for gdb. + def unwind(self, pending_frame): + pc = pending_frame.read_register(self.PC_REGISTER) + + # If the jit does not claim this address, bail. GDB defers to our + # unwinder by default, but we don't really want that kind of power. + if not self.is_jit_address(long(pc)): + return None + + if self.next_sp is not None: + if self.next_type == self.typecache.frame_type.CppToJSJit: + return self.unwind_entry_frame(pc, pending_frame) + return self.unwind_ordinary(pc, pending_frame) + # Maybe we've found an exit frame. FIXME I currently don't + # know how to identify these precisely, so we'll just hope for + # the time being. + return self.unwind_exit_frame(pc, pending_frame) + + +class x64UnwinderState(UnwinderState): + "The UnwinderState subclass for x86-64." + + SP_REGISTER = "rsp" + PC_REGISTER = "rip" + + # A register unique to this architecture, that is also likely to + # have been saved in any frame. The best thing to use here is + # some arch-specific name for PC or SP. + SENTINEL_REGISTER = "rip" + + # Must be in sync with Trampoline-x64.cpp:generateEnterJIT. Note + # that rip isn't pushed there explicitly, but rather by the + # previous function's call. + PUSHED_REGS = ["r15", "r14", "r13", "r12", "rbx", "rbp", "rip"] + + # Fill in the unwound registers for an entry frame. + def unwind_entry_frame_registers(self, sp, unwind_info): + sp = sp.cast(self.typecache.void_starstar) + # Skip the "result" push. + sp = sp + 1 + for reg in self.PUSHED_REGS: + data = sp.dereference() + sp = sp + 1 + unwind_info.add_saved_register(reg, data) + if reg == "rbp": + unwind_info.add_saved_register(self.SP_REGISTER, sp) + + +class SpiderMonkeyUnwinder(Unwinder): + """The unwinder object. This provides the "user interface" to the JIT + unwinder, and also handles constructing or destroying UnwinderState + objects as needed.""" + + # A list of all the possible unwinders. See |self.make_unwinder|. + UNWINDERS = [x64UnwinderState] + + def __init__(self, typecache): + super(SpiderMonkeyUnwinder, self).__init__("SpiderMonkey") + self.typecache = typecache + self.unwinder_state = None + + # Disabled by default until we figure out issues in gdb. + self.enabled = False + gdb.write( + "SpiderMonkey unwinder is disabled by default, to enable it type:\n" + + "\tenable unwinder .* SpiderMonkey\n" + ) + # Some versions of gdb did not flush the internal frame cache + # when enabling or disabling an unwinder. This was fixed in + # the same release of gdb that added the breakpoint_created + # event. + if not hasattr(gdb.events, "breakpoint_created"): + gdb.write("\tflushregs\n") + + # We need to invalidate the unwinder state whenever the + # inferior starts executing. This avoids having a stale + # cache. + gdb.events.cont.connect(self.invalidate_unwinder_state) + assert self.test_sentinels() + + def test_sentinels(self): + # Self-check. + regs = {} + for unwinder in self.UNWINDERS: + if unwinder.SENTINEL_REGISTER in regs: + return False + regs[unwinder.SENTINEL_REGISTER] = 1 + return True + + def make_unwinder(self, pending_frame): + # gdb doesn't provide a good way to find the architecture. + # See https://sourceware.org/bugzilla/show_bug.cgi?id=19399 + # So, we look at each known architecture and see if the + # corresponding "unique register" is known. + for unwinder in self.UNWINDERS: + try: + pending_frame.read_register(unwinder.SENTINEL_REGISTER) + except Exception: + # Failed to read the register, so let's keep going. + # This is more fragile than it might seem, because it + # fails if the sentinel register wasn't saved in the + # previous frame. + continue + return unwinder(self.typecache) + return None + + def __call__(self, pending_frame): + if self.unwinder_state is None or not self.unwinder_state.check(): + self.unwinder_state = self.make_unwinder(pending_frame) + if not self.unwinder_state: + return None + return self.unwinder_state.unwind(pending_frame) + + def invalidate_unwinder_state(self, *args, **kwargs): + self.unwinder_state = None + + +def register_unwinder(objfile): + """Register the unwinder and frame filter with |objfile|. If |objfile| + is None, register them globally.""" + + type_cache = UnwinderTypeCache() + unwinder = None + # This currently only works on Linux, due to parse_proc_maps. + if _have_unwinder and platform.system() == "Linux": + unwinder = SpiderMonkeyUnwinder(type_cache) + gdb.unwinder.register_unwinder(objfile, unwinder, replace=True) + # We unconditionally register the frame filter, because at some + # point we'll add interpreter frame filtering. + filt = SpiderMonkeyFrameFilter(type_cache, unwinder) + if objfile is None: + objfile = gdb + objfile.frame_filters[filt.name] = filt |