tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

IonGraph.py (9248B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 # You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 """
      6 Debugging JIT Compilations can be obscure without large context. This python
      7 script provide commands to let GDB open an image viewer to display the graph of
      8 any compilation, as they are executed within GDB.
      9 
     10 This python script should be sourced within GDB after loading the python scripts
     11 provided with SpiderMonkey.
     12 """
     13 
     14 import os
     15 import subprocess
     16 import tempfile
     17 import time
     18 
     19 import gdb
     20 
     21 import mozilla.prettyprinters
     22 from mozilla.prettyprinters import pretty_printer
     23 
     24 # Forget any printers from previous loads of this module.
     25 mozilla.prettyprinters.clear_module_printers(__name__)
     26 
     27 # Cache information about the JSString type for this objfile.
     28 
     29 
     30 class jsvmPrinterCache:
     31    def __init__(self):
     32        self.d = None
     33 
     34    def __getattr__(self, name):
     35        if self.d is None:
     36            self.initialize()
     37        return self.d[name]
     38 
     39    def initialize(self):
     40        self.d = {}
     41        self.d["char"] = gdb.lookup_type("char")
     42 
     43 
     44 # Dummy class used to store the content of the type cache in the context of the
     45 # iongraph command, which uses the jsvmLSprinter.
     46 
     47 
     48 class ModuleCache:
     49    def __init__(self):
     50        self.mod_IonGraph = None
     51 
     52 
     53 @pretty_printer("js::vm::LSprinter")
     54 class jsvmLSprinter:
     55    def __init__(self, value, cache):
     56        self.value = value
     57        if not cache.mod_IonGraph:
     58            cache.mod_IonGraph = jsvmPrinterCache()
     59        self.cache = cache.mod_IonGraph
     60 
     61    def to_string(self):
     62        next = self.value["head_"]
     63        tail = self.value["tail_"]
     64        if next == 0:
     65            return ""
     66        res = ""
     67        while next != tail:
     68            chars = (next + 1).cast(self.cache.char.pointer())
     69            res = res + chars.string("ascii", "ignore", next["length"])
     70            next = next["next"]
     71        length = next["length"] - self.value["unused_"]
     72        chars = (next + 1).cast(self.cache.char.pointer())
     73        res = res + chars.string("ascii", "ignore", length)
     74        return res
     75 
     76 
     77 def search_in_path(bin):
     78    paths = os.getenv("PATH", "")
     79    if paths == "":
     80        return ""
     81    for d in paths.split(":"):
     82        f = os.path.join(d, bin)
     83        if os.access(f, os.X_OK):
     84            return f
     85    return ""
     86 
     87 
     88 class IonGraphBinParameter(gdb.Parameter):
     89    set_doc = "Set the path to iongraph binary, used by iongraph command."
     90    show_doc = "Show the path to iongraph binary, used by iongraph command."
     91 
     92    def get_set_string(self):
     93        return "Path to iongraph binary changed to: %s" % self.value
     94 
     95    def get_show_string(self, value):
     96        return "Path to iongraph binary set to: %s" % value
     97 
     98    def __init__(self):
     99        super().__init__("iongraph-bin", gdb.COMMAND_SUPPORT, gdb.PARAM_FILENAME)
    100        self.value = os.getenv("GDB_IONGRAPH", "")
    101        if self.value == "":
    102            self.value = search_in_path("iongraph")
    103 
    104 
    105 class DotBinParameter(gdb.Parameter):
    106    set_doc = "Set the path to dot binary, used by iongraph command."
    107    show_doc = "Show the path to dot binary, used by iongraph command."
    108 
    109    def get_set_string(self):
    110        return "Path to dot binary changed to: %s" % self.value
    111 
    112    def get_show_string(self, value):
    113        return "Path to dot binary set to: %s" % value
    114 
    115    def __init__(self):
    116        super().__init__("dot-bin", gdb.COMMAND_SUPPORT, gdb.PARAM_FILENAME)
    117        self.value = os.getenv("GDB_DOT", "")
    118        if self.value == "":
    119            self.value = search_in_path("dot")
    120 
    121 
    122 class PngViewerBinParameter(gdb.Parameter):
    123    set_doc = "Set the path to a png viewer binary, used by iongraph command."
    124    show_doc = "Show the path to a png viewer binary, used by iongraph command."
    125 
    126    def get_set_string(self):
    127        return "Path to a png viewer binary changed to: %s" % self.value
    128 
    129    def get_show_string(self):
    130        return "Path to a png viewer binary set to: %s" % self.value
    131 
    132    def __init__(self):
    133        super().__init__("pngviewer-bin", gdb.COMMAND_SUPPORT, gdb.PARAM_FILENAME)
    134        self.value = os.getenv("GDB_PNGVIEWER", "")
    135        if self.value == "":
    136            self.value = search_in_path("xdg-open")
    137 
    138 
    139 iongraph = IonGraphBinParameter()
    140 dot = DotBinParameter()
    141 pngviewer = PngViewerBinParameter()
    142 
    143 
    144 class IonGraphCommand(gdb.Command):
    145    """Command used to display the current state of the MIR graph in a png
    146    viewer by providing an expression to access the MIRGenerator.
    147    """
    148 
    149    def __init__(self):
    150        super().__init__("iongraph", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION)
    151        self.typeCache = ModuleCache()
    152 
    153    def invoke(self, mirGenExpr, from_tty):
    154        """Call function from the graph spewer to populate the json printer with
    155        the content generated by the jsonSpewer. Then we read the json content
    156        from the jsonPrinter internal data, and gives that as input of iongraph
    157        command."""
    158 
    159        # From the MIRGenerator, find the graph spewer which contains both the
    160        # jsonPrinter (containing the result of the output), and the jsonSpewer
    161        # (continaining methods for spewing the graph).
    162        mirGen = gdb.parse_and_eval(mirGenExpr)
    163        jsonPrinter = mirGen["gs_"]["jsonPrinter_"]
    164        jsonSpewer = mirGen["gs_"]["jsonSpewer_"]
    165        graph = mirGen["graph_"]
    166 
    167        # These commands are doing side-effects which are saving the state of
    168        # the compiled code on the LSprinter dedicated for logging. Fortunately,
    169        # if you are using these gdb command, this probably means that other
    170        # ways of getting this content failed you already, so making a mess in
    171        # these logging strings should not cause much issues.
    172        gdb.parse_and_eval(
    173            "(*(%s*)(%s)).clear()"
    174            % (
    175                jsonPrinter.type,
    176                jsonPrinter.address,
    177            )
    178        )
    179        gdb.parse_and_eval(
    180            "(*(%s*)(%s)).beginFunction((JSScript*)0)"
    181            % (
    182                jsonSpewer.type,
    183                jsonSpewer.address,
    184            )
    185        )
    186        gdb.parse_and_eval(
    187            '(*(%s*)(%s)).beginPass("gdb")'
    188            % (
    189                jsonSpewer.type,
    190                jsonSpewer.address,
    191            )
    192        )
    193        gdb.parse_and_eval(
    194            "(*(%s*)(%s)).spewMIR((%s)%s)"
    195            % (
    196                jsonSpewer.type,
    197                jsonSpewer.address,
    198                graph.type,
    199                graph,
    200            )
    201        )
    202        gdb.parse_and_eval(
    203            "(*(%s*)(%s)).spewLIR((%s)%s)"
    204            % (
    205                jsonSpewer.type,
    206                jsonSpewer.address,
    207                graph.type,
    208                graph,
    209            )
    210        )
    211        gdb.parse_and_eval(
    212            "(*(%s*)(%s)).endPass()"
    213            % (
    214                jsonSpewer.type,
    215                jsonSpewer.address,
    216            )
    217        )
    218        gdb.parse_and_eval(
    219            "(*(%s*)(%s)).endFunction()"
    220            % (
    221                jsonSpewer.type,
    222                jsonSpewer.address,
    223            )
    224        )
    225 
    226        # Dump the content of the LSprinter containing the JSON view of the
    227        # graph into a python string.
    228        json = jsvmLSprinter(jsonPrinter, self.typeCache).to_string()
    229 
    230        # We are in the middle of the program execution and are messing up with
    231        # the state of the logging data. As this might not be the first time we
    232        # call beginFunction, we might have an extra comma at the beginning of
    233        # the output, just strip it.
    234        if json[0] == ",":
    235            json = json[1:]
    236 
    237        # Usually this is added by the IonSpewer.
    238        json = '{ "functions": [%s] }' % json
    239 
    240        # Display the content of the json with iongraph and other tools.
    241        self.displayMIRGraph(json)
    242 
    243    def displayMIRGraph(self, jsonStr):
    244        png = tempfile.NamedTemporaryFile()
    245 
    246        # start all processes in a shell-like equivalent of:
    247        #   iongraph < json | dot > tmp.png; xdg-open tmp.png
    248        i = subprocess.Popen(
    249            [iongraph.value, "--funcnum", "0", "--passnum", "0", "--out-mir", "-", "-"],
    250            stdin=subprocess.PIPE,
    251            stdout=subprocess.PIPE,
    252        )
    253        d = subprocess.Popen([dot.value, "-Tpng"], stdin=i.stdout, stdout=png)
    254 
    255        # Write the json file as the input of the iongraph command.
    256        i.stdin.write(jsonStr.encode("utf8"))
    257        i.stdin.close()
    258        i.stdout.close()
    259 
    260        # Wait for iongraph and dot, such that the png file contains all the
    261        # bits needed to by the png viewer.
    262        i.wait()
    263        d.communicate()[0]
    264 
    265        # Spawn & detach the png viewer, to which we give the name of the
    266        # temporary file.  Note, as we do not want to wait on the image viewer,
    267        # there is a minor race between the removal of the temporary file, which
    268        # would happen at the next garbage collection cycle, and the start of
    269        # the png viewer.  We could use a pipe, but unfortunately, this does not
    270        # seems to be supported by xdg-open.
    271        subprocess.Popen([pngviewer.value, png.name], stdin=None, stdout=None)
    272        time.sleep(1)
    273 
    274 
    275 iongraph_cmd = IonGraphCommand()