318 lines
10 KiB
Python
Executable File
318 lines
10 KiB
Python
Executable File
#!/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"><<</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:
|
|
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())
|