You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

318 lines
10 KiB

#!/usr/bin/env python
"""
This program parses 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
(Graphviz must be installed)
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. Open the URL at the 3rd line in a browser.
Note:
1. The CFG is translated to SVG images, expr1.html embeds them as external
documents. If you open expr1.html as local file using file:// protocol, some
browsers will deny such requests so the web page will not work properly.
For Chrome, you can run it using the following command to avoid this:
chromium --disable-web-security
That's why this program starts a localhost HTTP server.
2. expr1.html uses jQuery from https://ajax.googleapis.com, so it needs Internet
access to work.
"""
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="https://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='https://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='https://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">&lt;&lt;</button>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<button id="forward" type="button">&gt;&gt;</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:
try:
p = subprocess.Popen(['dot', '-Tsvg'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
except OSError as ose:
print "Failed to run 'dot':", ose
print "(Is Graphviz installed?)"
exit(1)
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 program has 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())