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()