1*748408edSchristos#!/usr/bin/env python 2*748408edSchristos 3*748408edSchristos""" 4*748408edSchristosThis program parses the output from pcap_compile() to visualize the CFG after 5*748408edSchristoseach optimize phase. 6*748408edSchristos 7*748408edSchristosUsage guide: 8*748408edSchristos1. Enable optimizer debugging code when configure libpcap, 9*748408edSchristos and build libpcap & the test programs 10*748408edSchristos ./configure --enable-optimizer-dbg 11*748408edSchristos make 12*748408edSchristos make testprogs 13*748408edSchristos2. Run filtertest to compile BPF expression and produce the CFG as a 14*748408edSchristos DOT graph, save to output a.txt 15*748408edSchristos testprogs/filtertest -g EN10MB host 192.168.1.1 > a.txt 16*748408edSchristos3. Send a.txt to this program's standard input 17*748408edSchristos cat a.txt | testprogs/visopts.py 18*748408edSchristos (Graphviz must be installed) 19*748408edSchristos4. Step 2&3 can be merged: 20*748408edSchristos testprogs/filtertest -g EN10MB host 192.168.1.1 | testprogs/visopts.py 21*748408edSchristos5. The standard output is something like this: 22*748408edSchristos generated files under directory: /tmp/visopts-W9ekBw 23*748408edSchristos the directory will be removed when this programs finished. 24*748408edSchristos open this link: http://localhost:39062/expr1.html 25*748408edSchristos6. Open the URL at the 3rd line in a browser. 26*748408edSchristos 27*748408edSchristosNote: 28*748408edSchristos1. The CFG is translated to SVG images, expr1.html embeds them as external 29*748408edSchristos documents. If you open expr1.html as local file using file:// protocol, some 30*748408edSchristos browsers will deny such requests so the web page will not work properly. 31*748408edSchristos For Chrome, you can run it using the following command to avoid this: 32*748408edSchristos chromium --disable-web-security 33*748408edSchristos That's why this program starts a localhost HTTP server. 34*748408edSchristos2. expr1.html uses jQuery from https://ajax.googleapis.com, so it needs Internet 35*748408edSchristos access to work. 36*748408edSchristos""" 37*748408edSchristos 38*748408edSchristosimport sys, os 39*748408edSchristosimport string 40*748408edSchristosimport subprocess 41*748408edSchristosimport json 42*748408edSchristos 43*748408edSchristoshtml_template = string.Template(""" 44*748408edSchristos<html> 45*748408edSchristos <head> 46*748408edSchristos <title>BPF compiler optimization phases for $expr </title> 47*748408edSchristos <style type="text/css"> 48*748408edSchristos .hc { 49*748408edSchristos /* half width container */ 50*748408edSchristos display: inline-block; 51*748408edSchristos float: left; 52*748408edSchristos width: 50%; 53*748408edSchristos } 54*748408edSchristos </style> 55*748408edSchristos 56*748408edSchristos <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"/></script> 57*748408edSchristos <!--script type="text/javascript" src="./jquery.min.js"/></script--> 58*748408edSchristos <script type="text/javascript"> 59*748408edSchristos var expr = '$expr'; 60*748408edSchristos var exprid = 1; 61*748408edSchristos var gcount = $gcount; 62*748408edSchristos var logs = JSON.parse('$logs'); 63*748408edSchristos logs[gcount] = ""; 64*748408edSchristos 65*748408edSchristos var leftsvg = null; 66*748408edSchristos var rightsvg = null; 67*748408edSchristos 68*748408edSchristos function gurl(index) { 69*748408edSchristos index += 1; 70*748408edSchristos if (index < 10) 71*748408edSchristos s = "00" + index; 72*748408edSchristos else if (index < 100) 73*748408edSchristos s = "0" + index; 74*748408edSchristos else 75*748408edSchristos s = "" + index; 76*748408edSchristos return "./expr" + exprid + "_g" + s + ".svg" 77*748408edSchristos } 78*748408edSchristos 79*748408edSchristos function annotate_svgs() { 80*748408edSchristos if (!leftsvg || !rightsvg) return; 81*748408edSchristos 82*748408edSchristos $$.each([$$(leftsvg), $$(rightsvg)], function() { 83*748408edSchristos $$(this).find("[id|='block'][opacity]").each(function() { 84*748408edSchristos $$(this).removeAttr('opacity'); 85*748408edSchristos }); 86*748408edSchristos }); 87*748408edSchristos 88*748408edSchristos $$(leftsvg).find("[id|='block']").each(function() { 89*748408edSchristos var has = $$(rightsvg).find("#" + this.id).length != 0; 90*748408edSchristos if (!has) $$(this).attr("opacity", "0.4"); 91*748408edSchristos else { 92*748408edSchristos $$(this).click(function() { 93*748408edSchristos var target = $$(rightsvg).find("#" + this.id); 94*748408edSchristos var offset = $$("#rightsvgc").offset().top + target.position().top; 95*748408edSchristos window.scrollTo(0, offset); 96*748408edSchristos target.focus(); 97*748408edSchristos }); 98*748408edSchristos } 99*748408edSchristos }); 100*748408edSchristos $$(rightsvg).find("[id|='block']").each(function() { 101*748408edSchristos var has = $$(leftsvg).find("#" + this.id).length != 0; 102*748408edSchristos if (!has) $$(this).attr("opacity", "0.4"); 103*748408edSchristos else { 104*748408edSchristos $$(this).click(function() { 105*748408edSchristos var target = $$(leftsvg).find("#" + this.id); 106*748408edSchristos var offset = $$("#leftsvgc").offset().top + target.position().top; 107*748408edSchristos window.scrollTo(0, offset); 108*748408edSchristos target.focus(); 109*748408edSchristos }); 110*748408edSchristos } 111*748408edSchristos }); 112*748408edSchristos } 113*748408edSchristos 114*748408edSchristos function init_svgroot(svg) { 115*748408edSchristos svg.setAttribute("width", "100%"); 116*748408edSchristos svg.setAttribute("height", "100%"); 117*748408edSchristos } 118*748408edSchristos function wait_leftsvg() { 119*748408edSchristos if (leftsvg) return; 120*748408edSchristos var doc = document.getElementById("leftsvgc").getSVGDocument(); 121*748408edSchristos if (doc == null) { 122*748408edSchristos setTimeout(wait_leftsvg, 500); 123*748408edSchristos return; 124*748408edSchristos } 125*748408edSchristos leftsvg = doc.documentElement; 126*748408edSchristos //console.log(leftsvg); 127*748408edSchristos // initialize it 128*748408edSchristos init_svgroot(leftsvg); 129*748408edSchristos annotate_svgs(); 130*748408edSchristos } 131*748408edSchristos function wait_rightsvg() { 132*748408edSchristos if (rightsvg) return; 133*748408edSchristos var doc = document.getElementById("rightsvgc").getSVGDocument(); 134*748408edSchristos if (doc == null) { 135*748408edSchristos setTimeout(wait_rightsvg, 500); 136*748408edSchristos return; 137*748408edSchristos } 138*748408edSchristos rightsvg = doc.documentElement; 139*748408edSchristos //console.log(rightsvg); 140*748408edSchristos // initialize it 141*748408edSchristos init_svgroot(rightsvg); 142*748408edSchristos annotate_svgs(); 143*748408edSchristos } 144*748408edSchristos function load_left(index) { 145*748408edSchristos var url = gurl(index); 146*748408edSchristos var frag = "<embed id='leftsvgc' type='image/svg+xml' pluginspage='https://www.adobe.com/svg/viewer/install/' src='" + url + "'/>"; 147*748408edSchristos $$("#lsvg").html(frag); 148*748408edSchristos $$("#lcomment").html(logs[index]); 149*748408edSchristos $$("#lsvglink").attr("href", url); 150*748408edSchristos leftsvg = null; 151*748408edSchristos wait_leftsvg(); 152*748408edSchristos } 153*748408edSchristos function load_right(index) { 154*748408edSchristos var url = gurl(index); 155*748408edSchristos var frag = "<embed id='rightsvgc' type='image/svg+xml' pluginspage='https://www.adobe.com/svg/viewer/install/' src='" + url + "'/>"; 156*748408edSchristos $$("#rsvg").html(frag); 157*748408edSchristos $$("#rcomment").html(logs[index]); 158*748408edSchristos $$("#rsvglink").attr("href", url); 159*748408edSchristos rightsvg = null; 160*748408edSchristos wait_rightsvg(); 161*748408edSchristos } 162*748408edSchristos 163*748408edSchristos $$(document).ready(function() { 164*748408edSchristos for (var i = 0; i < gcount; i++) { 165*748408edSchristos var opt = "<option value='" + i + "'>loop" + i + " -- " + logs[i] + "</option>"; 166*748408edSchristos $$("#lselect").append(opt); 167*748408edSchristos $$("#rselect").append(opt); 168*748408edSchristos } 169*748408edSchristos var on_selected = function() { 170*748408edSchristos var index = parseInt($$(this).children("option:selected").val()); 171*748408edSchristos if (this.id == "lselect") 172*748408edSchristos load_left(index); 173*748408edSchristos else 174*748408edSchristos load_right(index); 175*748408edSchristos } 176*748408edSchristos $$("#lselect").change(on_selected); 177*748408edSchristos $$("#rselect").change(on_selected); 178*748408edSchristos 179*748408edSchristos $$("#backward").click(function() { 180*748408edSchristos var index = parseInt($$("#lselect option:selected").val()); 181*748408edSchristos if (index <= 0) return; 182*748408edSchristos $$("#lselect").val(index - 1).change(); 183*748408edSchristos $$("#rselect").val(index).change(); 184*748408edSchristos }); 185*748408edSchristos $$("#forward").click(function() { 186*748408edSchristos var index = parseInt($$("#rselect option:selected").val()); 187*748408edSchristos if (index >= gcount - 1) return; 188*748408edSchristos $$("#lselect").val(index).change(); 189*748408edSchristos $$("#rselect").val(index + 1).change(); 190*748408edSchristos }); 191*748408edSchristos 192*748408edSchristos if (gcount >= 1) $$("#lselect").val(0).change(); 193*748408edSchristos if (gcount >= 2) $$("#rselect").val(1).change(); 194*748408edSchristos }); 195*748408edSchristos </script> 196*748408edSchristos </head> 197*748408edSchristos <body style="width: 96%"> 198*748408edSchristos <div> 199*748408edSchristos <h1>$expr</h1> 200*748408edSchristos <div style="text-align: center;"> 201*748408edSchristos <button id="backward" type="button"><<</button> 202*748408edSchristos 203*748408edSchristos <button id="forward" type="button">>></button> 204*748408edSchristos </div> 205*748408edSchristos </div> 206*748408edSchristos <br/> 207*748408edSchristos <div style="clear: both;"> 208*748408edSchristos <div class="hc lc"> 209*748408edSchristos <select id="lselect"></select> 210*748408edSchristos <a id="lsvglink" target="_blank">open this svg in browser</a> 211*748408edSchristos <p id="lcomment"></p> 212*748408edSchristos </div> 213*748408edSchristos <div class="hc rc"> 214*748408edSchristos <select id="rselect"></select> 215*748408edSchristos <a id="rsvglink" target="_blank">open this svg in browser</a> 216*748408edSchristos <p id="rcomment"></p> 217*748408edSchristos </div> 218*748408edSchristos </div> 219*748408edSchristos <br/> 220*748408edSchristos <div style="clear: both;"> 221*748408edSchristos <div id="lsvg" class="hc lc"></div> 222*748408edSchristos <div id="rsvg" class="hc rc"></div> 223*748408edSchristos </div> 224*748408edSchristos </body> 225*748408edSchristos</html> 226*748408edSchristos""") 227*748408edSchristos 228*748408edSchristosdef write_html(expr, gcount, logs): 229*748408edSchristos logs = map(lambda s: s.strip().replace("\n", "<br/>"), logs) 230*748408edSchristos 231*748408edSchristos global html_template 232*748408edSchristos html = html_template.safe_substitute(expr=expr.encode("string-escape"), gcount=gcount, logs=json.dumps(logs).encode("string-escape")) 233*748408edSchristos with file("expr1.html", "wt") as f: 234*748408edSchristos f.write(html) 235*748408edSchristos 236*748408edSchristosdef render_on_html(infile): 237*748408edSchristos expr = None 238*748408edSchristos gid = 1 239*748408edSchristos log = "" 240*748408edSchristos dot = "" 241*748408edSchristos indot = 0 242*748408edSchristos logs = [] 243*748408edSchristos 244*748408edSchristos for line in infile: 245*748408edSchristos if line.startswith("machine codes for filter:"): 246*748408edSchristos expr = line[len("machine codes for filter:"):].strip() 247*748408edSchristos break 248*748408edSchristos elif line.startswith("digraph BPF {"): 249*748408edSchristos indot = 1 250*748408edSchristos dot = line 251*748408edSchristos elif indot: 252*748408edSchristos dot += line 253*748408edSchristos if line.startswith("}"): 254*748408edSchristos indot = 2 255*748408edSchristos else: 256*748408edSchristos log += line 257*748408edSchristos 258*748408edSchristos if indot == 2: 259*748408edSchristos try: 260*748408edSchristos p = subprocess.Popen(['dot', '-Tsvg'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 261*748408edSchristos except OSError as ose: 262*748408edSchristos print "Failed to run 'dot':", ose 263*748408edSchristos print "(Is Graphviz installed?)" 264*748408edSchristos exit(1) 265*748408edSchristos 266*748408edSchristos svg = p.communicate(dot)[0] 267*748408edSchristos with file("expr1_g%03d.svg" % (gid), "wt") as f: 268*748408edSchristos f.write(svg) 269*748408edSchristos 270*748408edSchristos logs.append(log) 271*748408edSchristos gid += 1 272*748408edSchristos log = "" 273*748408edSchristos dot = "" 274*748408edSchristos indot = 0 275*748408edSchristos 276*748408edSchristos if indot != 0: 277*748408edSchristos #unterminated dot graph for expression 278*748408edSchristos return False 279*748408edSchristos if expr is None: 280*748408edSchristos # BPF parser encounter error(s) 281*748408edSchristos return False 282*748408edSchristos write_html(expr, gid - 1, logs) 283*748408edSchristos return True 284*748408edSchristos 285*748408edSchristosdef run_httpd(): 286*748408edSchristos import SimpleHTTPServer 287*748408edSchristos import SocketServer 288*748408edSchristos 289*748408edSchristos class MySocketServer(SocketServer.TCPServer): 290*748408edSchristos allow_reuse_address = True 291*748408edSchristos Handler = SimpleHTTPServer.SimpleHTTPRequestHandler 292*748408edSchristos httpd = MySocketServer(("localhost", 0), Handler) 293*748408edSchristos print "open this link: http://localhost:%d/expr1.html" % (httpd.server_address[1]) 294*748408edSchristos try: 295*748408edSchristos httpd.serve_forever() 296*748408edSchristos except KeyboardInterrupt as e: 297*748408edSchristos pass 298*748408edSchristos 299*748408edSchristosdef main(): 300*748408edSchristos import tempfile 301*748408edSchristos import atexit 302*748408edSchristos import shutil 303*748408edSchristos os.chdir(tempfile.mkdtemp(prefix="visopts-")) 304*748408edSchristos atexit.register(shutil.rmtree, os.getcwd()) 305*748408edSchristos print "generated files under directory: %s" % os.getcwd() 306*748408edSchristos print " the directory will be removed when this program has finished." 307*748408edSchristos 308*748408edSchristos if not render_on_html(sys.stdin): 309*748408edSchristos return 1 310*748408edSchristos run_httpd() 311*748408edSchristos return 0 312*748408edSchristos 313*748408edSchristosif __name__ == "__main__": 314*748408edSchristos if '-h' in sys.argv or '--help' in sys.argv: 315*748408edSchristos print __doc__ 316*748408edSchristos exit(0) 317*748408edSchristos exit(main()) 318