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