| #!/usr/bin/env python |
| |
| """ |
| This program parse the output from pcap_compile() to visualize the CFG after |
| each optimize phase. |
| |
| Usage guide: |
| 1. Enable optimizier debugging code when configure libpcap, |
| and build libpcap & the test programs |
| ./configure --enable-optimizer-dbg |
| make |
| make testprogs |
| 2. Run filtertest to compile BPF expression and produce the CFG as a |
| DOT graph, save to output a.txt |
| testprogs/filtertest -g EN10MB host 192.168.1.1 > a.txt |
| 3. Send a.txt to this program's standard input |
| cat a.txt | testprogs/visopts.py |
| 4. Step 2&3 can be merged: |
| testprogs/filtertest -g EN10MB host 192.168.1.1 | testprogs/visopts.py |
| 5. The standard output is something like this: |
| generated files under directory: /tmp/visopts-W9ekBw |
| the directory will be removed when this programs finished. |
| open this link: http://localhost:39062/expr1.html |
| 6. Using open link at the 3rd line `http://localhost:39062/expr1.html' |
| |
| Note: |
| 1. The CFG is translated to SVG an document, expr1.html embeded them as external |
| document. If you open expr1.html as local file using file:// protocol, some |
| browsers will deny such requests so the web pages will not shown properly. |
| For chrome, you can run it using following command to avoid this: |
| chromium --disable-web-security |
| That's why this program start a localhost http server. |
| 2. expr1.html use jquery from http://ajax.googleapis.com, so you need internet |
| access to show the web page. |
| """ |
| |
| import sys, os |
| import string |
| import subprocess |
| import json |
| |
| html_template = string.Template(""" |
| <html> |
| <head> |
| <title>BPF compiler optimization phases for $expr </title> |
| <style type="text/css"> |
| .hc { |
| /* half width container */ |
| display: inline-block; |
| float: left; |
| width: 50%; |
| } |
| </style> |
| |
| <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"/></script> |
| <!--script type="text/javascript" src="./jquery.min.js"/></script--> |
| <script type="text/javascript"> |
| var expr = '$expr'; |
| var exprid = 1; |
| var gcount = $gcount; |
| var logs = JSON.parse('$logs'); |
| logs[gcount] = ""; |
| |
| var leftsvg = null; |
| var rightsvg = null; |
| |
| function gurl(index) { |
| index += 1; |
| if (index < 10) |
| s = "00" + index; |
| else if (index < 100) |
| s = "0" + index; |
| else |
| s = "" + index; |
| return "./expr" + exprid + "_g" + s + ".svg" |
| } |
| |
| function annotate_svgs() { |
| if (!leftsvg || !rightsvg) return; |
| |
| $$.each([$$(leftsvg), $$(rightsvg)], function() { |
| $$(this).find("[id|='block'][opacity]").each(function() { |
| $$(this).removeAttr('opacity'); |
| }); |
| }); |
| |
| $$(leftsvg).find("[id|='block']").each(function() { |
| var has = $$(rightsvg).find("#" + this.id).length != 0; |
| if (!has) $$(this).attr("opacity", "0.4"); |
| else { |
| $$(this).click(function() { |
| var target = $$(rightsvg).find("#" + this.id); |
| var offset = $$("#rightsvgc").offset().top + target.position().top; |
| window.scrollTo(0, offset); |
| target.focus(); |
| }); |
| } |
| }); |
| $$(rightsvg).find("[id|='block']").each(function() { |
| var has = $$(leftsvg).find("#" + this.id).length != 0; |
| if (!has) $$(this).attr("opacity", "0.4"); |
| else { |
| $$(this).click(function() { |
| var target = $$(leftsvg).find("#" + this.id); |
| var offset = $$("#leftsvgc").offset().top + target.position().top; |
| window.scrollTo(0, offset); |
| target.focus(); |
| }); |
| } |
| }); |
| } |
| |
| function init_svgroot(svg) { |
| svg.setAttribute("width", "100%"); |
| svg.setAttribute("height", "100%"); |
| } |
| function wait_leftsvg() { |
| if (leftsvg) return; |
| var doc = document.getElementById("leftsvgc").getSVGDocument(); |
| if (doc == null) { |
| setTimeout(wait_leftsvg, 500); |
| return; |
| } |
| leftsvg = doc.documentElement; |
| //console.log(leftsvg); |
| // initialize it |
| init_svgroot(leftsvg); |
| annotate_svgs(); |
| } |
| function wait_rightsvg() { |
| if (rightsvg) return; |
| var doc = document.getElementById("rightsvgc").getSVGDocument(); |
| if (doc == null) { |
| setTimeout(wait_rightsvg, 500); |
| return; |
| } |
| rightsvg = doc.documentElement; |
| //console.log(rightsvg); |
| // initialize it |
| init_svgroot(rightsvg); |
| annotate_svgs(); |
| } |
| function load_left(index) { |
| var url = gurl(index); |
| var frag = "<embed id='leftsvgc' type='image/svg+xml' pluginspage='http://www.adobe.com/svg/viewer/install/' src='" + url + "'/>"; |
| $$("#lsvg").html(frag); |
| $$("#lcomment").html(logs[index]); |
| $$("#lsvglink").attr("href", url); |
| leftsvg = null; |
| wait_leftsvg(); |
| } |
| function load_right(index) { |
| var url = gurl(index); |
| var frag = "<embed id='rightsvgc' type='image/svg+xml' pluginspage='http://www.adobe.com/svg/viewer/install/' src='" + url + "'/>"; |
| $$("#rsvg").html(frag); |
| $$("#rcomment").html(logs[index]); |
| $$("#rsvglink").attr("href", url); |
| rightsvg = null; |
| wait_rightsvg(); |
| } |
| |
| $$(document).ready(function() { |
| for (var i = 0; i < gcount; i++) { |
| var opt = "<option value='" + i + "'>loop" + i + " -- " + logs[i] + "</option>"; |
| $$("#lselect").append(opt); |
| $$("#rselect").append(opt); |
| } |
| var on_selected = function() { |
| var index = parseInt($$(this).children("option:selected").val()); |
| if (this.id == "lselect") |
| load_left(index); |
| else |
| load_right(index); |
| } |
| $$("#lselect").change(on_selected); |
| $$("#rselect").change(on_selected); |
| |
| $$("#backward").click(function() { |
| var index = parseInt($$("#lselect option:selected").val()); |
| if (index <= 0) return; |
| $$("#lselect").val(index - 1).change(); |
| $$("#rselect").val(index).change(); |
| }); |
| $$("#forward").click(function() { |
| var index = parseInt($$("#rselect option:selected").val()); |
| if (index >= gcount - 1) return; |
| $$("#lselect").val(index).change(); |
| $$("#rselect").val(index + 1).change(); |
| }); |
| |
| if (gcount >= 1) $$("#lselect").val(0).change(); |
| if (gcount >= 2) $$("#rselect").val(1).change(); |
| }); |
| </script> |
| </head> |
| <body style="width: 96%"> |
| <div> |
| <h1>$expr</h1> |
| <div style="text-align: center;"> |
| <button id="backward" type="button"><<</button> |
| |
| <button id="forward" type="button">>></button> |
| </div> |
| </div> |
| <br/> |
| <div style="clear: both;"> |
| <div class="hc lc"> |
| <select id="lselect"></select> |
| <a id="lsvglink" target="_blank">open this svg in browser</a> |
| <p id="lcomment"></p> |
| </div> |
| <div class="hc rc"> |
| <select id="rselect"></select> |
| <a id="rsvglink" target="_blank">open this svg in browser</a> |
| <p id="rcomment"></p> |
| </div> |
| </div> |
| <br/> |
| <div style="clear: both;"> |
| <div id="lsvg" class="hc lc"></div> |
| <div id="rsvg" class="hc rc"></div> |
| </div> |
| </body> |
| </html> |
| """) |
| |
| def write_html(expr, gcount, logs): |
| logs = map(lambda s: s.strip().replace("\n", "<br/>"), logs) |
| |
| global html_template |
| html = html_template.safe_substitute(expr=expr.encode("string-escape"), gcount=gcount, logs=json.dumps(logs).encode("string-escape")) |
| with file("expr1.html", "wt") as f: |
| f.write(html) |
| |
| def render_on_html(infile): |
| expr = None |
| gid = 1 |
| log = "" |
| dot = "" |
| indot = 0 |
| logs = [] |
| |
| for line in infile: |
| if line.startswith("machine codes for filter:"): |
| expr = line[len("machine codes for filter:"):].strip() |
| break |
| elif line.startswith("digraph BPF {"): |
| indot = 1 |
| dot = line |
| elif indot: |
| dot += line |
| if line.startswith("}"): |
| indot = 2 |
| else: |
| log += line |
| |
| if indot == 2: |
| p = subprocess.Popen(['dot', '-Tsvg'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) |
| svg = p.communicate(dot)[0] |
| with file("expr1_g%03d.svg" % (gid), "wt") as f: |
| f.write(svg) |
| |
| logs.append(log) |
| gid += 1 |
| log = "" |
| dot = "" |
| indot = 0 |
| |
| if indot != 0: |
| #unterminated dot graph for expression |
| return False |
| if expr is None: |
| # BPF parser encounter error(s) |
| return False |
| write_html(expr, gid - 1, logs) |
| return True |
| |
| def run_httpd(): |
| import SimpleHTTPServer |
| import SocketServer |
| |
| class MySocketServer(SocketServer.TCPServer): |
| allow_reuse_address = True |
| Handler = SimpleHTTPServer.SimpleHTTPRequestHandler |
| httpd = MySocketServer(("localhost", 0), Handler) |
| print "open this link: http://localhost:%d/expr1.html" % (httpd.server_address[1]) |
| try: |
| httpd.serve_forever() |
| except KeyboardInterrupt as e: |
| pass |
| |
| def main(): |
| import tempfile |
| import atexit |
| import shutil |
| os.chdir(tempfile.mkdtemp(prefix="visopts-")) |
| atexit.register(shutil.rmtree, os.getcwd()) |
| print "generated files under directory: %s" % os.getcwd() |
| print " the directory will be removed when this programs finished." |
| |
| if not render_on_html(sys.stdin): |
| return 1 |
| run_httpd() |
| return 0 |
| |
| if __name__ == "__main__": |
| if '-h' in sys.argv or '--help' in sys.argv: |
| print __doc__ |
| exit(0) |
| exit(main()) |