diff --git a/tools/analysis/resultbrowser/README b/tools/analysis/resultbrowser/README new file mode 100644 index 00000000..46f1bac3 --- /dev/null +++ b/tools/analysis/resultbrowser/README @@ -0,0 +1,19 @@ +Fail* Result Browser + +Requirements: + * Python + * Flask (sudo pip install Flask) + * MySQLDB (sudo aptitude install python-mysqldb) + * YAML (sudo aptitude install python-yaml) + +Based on Flask web microframework (Werkzeug, Jinja 2) +and old school MySQL bindings. + +Connects to a Fail* result database given by a mysql config file. + +Usage: + ./run.py + Defaults to mysql config file ~/.my.cnf, or + ./run.py -c + +YAML based configuration for table and variant details. diff --git a/tools/analysis/resultbrowser/app/__init__.py b/tools/analysis/resultbrowser/app/__init__.py new file mode 100644 index 00000000..a6c9e339 --- /dev/null +++ b/tools/analysis/resultbrowser/app/__init__.py @@ -0,0 +1,4 @@ +from flask import Flask + +app = Flask(__name__) +from app import views diff --git a/tools/analysis/resultbrowser/app/data.py b/tools/analysis/resultbrowser/app/data.py new file mode 100644 index 00000000..da24db22 --- /dev/null +++ b/tools/analysis/resultbrowser/app/data.py @@ -0,0 +1,143 @@ +from pprint import pprint +import details +import model + +def scrub(table_name): + return ''.join( chr for chr in table_name if chr.isalnum() or chr == '_' ) + +class Resulttype: + def __init__(self, name, count): + self.name = name + self.count = count + + def getName(self): + return self.name + + def getCount(self): + return self.count + +class Variant: + def __init__(self, id, name, table, benchmark, detail): + self.id = id + self.dbname = name + self.parenttable = table # TableDetails + self.details = detail # VariantDetails + self.benchmark = benchmark # BenchmarkDetails + self.results = {} + self.totalresults = 0 + + + def getMapper(self): + mapper = self.benchmark.getMapper() + if not mapper: #try benchmark mapper + mapper = self.details.getMapper() + if not mapper: # of not there, try parent tables mapper + mapper = self.parenttable.getMapper() + if not mapper: # no mapper found at all, try default mapper + mapper = model.detaildealer.getDefaultMapper() + return mapper + + + def addResulttype(self, name, count): + mapper = self.getMapper() + label = mapper.getLabel(name) + oldcount = self.results.setdefault(label, 0) + self.results[label] = oldcount + count + self.totalresults += count + + def getResultLabels(self): + return self.results.keys() + + def getDBName(self): + return str(self.name) + + def getId(self): + return self.id + + def getResults(self): + return self.results + + def getTableDetails(self): + return self.parenttable + + def getBenchmarkDetails(self): + return self.benchmark + + def getDetails(self): + return self.details + + def getTotals(self): + return self.totalresults + + def __str__(self): + ret = "Variant: " + self.getDetails().getTitle() + " - " + self.getBenchmarkDetails().getTitle() +" (id: " + str( self.id )+ ")" + " " + ret += "Total Results: " + str( self.totalresults ) + "\n" + for v in self.results: + ret += "\t" + v.name + ": " + str( v.count ) + "\n" + return ret + + __repr__ = __str__ + +'''A ResultTable contains n Variants''' +class ResultTable: + + def __init__(self, name, cfg): + self.name = scrub(name) + self.details = cfg.getTable(name) + self.variants = {} + + def addVariant(self, var): + if var.getId() in self.variants: + return + self.variants[var.getId()] = var # Add if not existing yet + + def getVariant(self, id): + if id in self.variants: + return self.variants[id] + return None + + def getVariantById(self, varid): + for k,v in self.variants.items(): + if int(v.getId()) == int(varid): + return v + return None + + def getDetails(self): + return self.details + + def getVariants(self): + return self.variants + + def __str__(self): + ret = "Result: " + self.getDetails().getTitle() + "\n" + for k,v in self.variants.items(): + ret += "\t" + str(v) + "\n" + return ret + __repr__ = __str__ + +'''Overview has n ResultTables''' +class Overview: + def __init__(self): + self.tables = {} + + def add(self, table): + self.tables[table.getDetails().getDBName()] = table + + def getTables(self): + return self.tables + + def getTable(self, dbname): + return self.tables.get(dbname, None) + + def getVariantById(self, variant_id): + for key,table in self.tables.items(): + variant = table.getVariantById(variant_id) + if variant: + return variant + print "Variant not found." + return None + + def length(self): + return len(self.tables) + + diff --git a/tools/analysis/resultbrowser/app/details.py b/tools/analysis/resultbrowser/app/details.py new file mode 100644 index 00000000..e58cb25d --- /dev/null +++ b/tools/analysis/resultbrowser/app/details.py @@ -0,0 +1,216 @@ + +class BasicDetails(object): + + def __init__(self,name): + self.dbname = name + self.title = name + self.details = '' + self.mapper = None + + def getDBName(self): + return self.dbname + + def getDetails(self): + return self.details + + def setDetails(self,det): + self.details = det + + def getTitle(self): + return self.title + + def addMapper(self, mapper): + self.mapper = mapper + + def getMapper(self): + return self.mapper + + def extractDetails(self, dictionary): + self.details = dictionary.pop(('details'), '') + self.title = dictionary.pop(('title'), self.dbname) + custommapping = dictionary.pop(('mapping'), None) + if custommapping: + self.mapper = ResulttypeMapper() + self.mapper.add(custommapping) + else: + self.mapper = None + + + def __repr__(self): + return self.getTitle() + ": " + self.getDetails() + + + __str__ = __repr__ + + + +class BenchmarkDetails(BasicDetails): + + def __init__(self, dbname): + BasicDetails.__init__(self, dbname) + + def __repr__(self): + return "Benchmark: " + BasicDetails.__repr__(self) + + __str__ = __repr__ + + + +class VariantDetails(BasicDetails): + + def __init__(self, dbname): + BasicDetails.__init__(self, dbname) + self.benchmarks = {} + + def addBenchmark(self, bm): + self.benchmarks[bm.getDBName()] = bm + + def getBenchmark(self, dbbm): + return self.benchmarks.get(dbbm, BenchmarkDetails(dbbm)) + + def __repr__(self): + ret = "Variant: " + BasicDetails.__repr__(self) + for v in self.benchmarks.values(): + ret += "\n\t\t" + str(v) + return ret + + + __str__ = __repr__ + +class TableDetails(BasicDetails): + + def __init__(self, tbl): + BasicDetails.__init__(self, tbl) + self.variants = {} + + def addVariant(self, var): + self.variants[var.getDBName()] = var + + def getVariant(self, varname): + return self.variants.get(varname, VariantDetails(varname)) + + def __repr__(self): + ret = "Table: " + BasicDetails.__repr__(self) + "(" + self.getDBName() + ")" + for v in self.variants.values(): + ret += "\n\t" + str(v) + return ret + + __str__ = __repr__ + +from pprint import pprint + + +class ResulttypeMapper(object): + + def __init__(self): + self.mappings = {} + + def add(self, mapping): + for label, dbnamelist in mapping.items(): + self.mappings[label] = dbnamelist + + def getLabel(self, dbname): + for label, dbnamelist in self.mappings.items(): + if dbname in dbnamelist: + return label + return dbname + + def getLabelList(self): + return self.mappings.keys() + + def getDBNames(self, label): + return self.mappings.get(label, None) + + def __repr__(self): + ret = "Resulttype Mapper:" + for label,dbnames in self.mappings.items(): + ret += "\n\t" + label + for db in dbnames: + ret += "\n\t\t" + db + return ret + + __str__ = __repr__ + +import yaml +class DetailDealer: + + def __init__(self, configfile=None): + self.tables = {} + self.defaultMapper = ResulttypeMapper() + + if not configfile: + return + self.reload(configfile) + + if not self.tables: + print "DetailDealer: no details found for " + configfile + + def reload(self, configfile): + self.tables = {} + self.defaultMapper = ResulttypeMapper() + if not configfile: + return # no details. + f = open(configfile) + # use safe_load instead load + cfg = yaml.safe_load(f) + f.close() + # Read out default mapping, if existent + self.extractDefaults(cfg) + tables = cfg.pop('tables', None) + if tables: + for tablename,details in tables.items(): + tab = TableDetails(tablename) + # pop: return and remove when key present, else return 'notfound' + tab.extractDetails(details) + variants = details.pop('variants') + for variantname, vdetails in variants.items(): + var = VariantDetails(variantname) + var.extractDetails(vdetails) + benchmarks = vdetails.pop('benchmarks') + for benchmark, bdetails in benchmarks.items(): + bm = BenchmarkDetails(benchmark) + bm.extractDetails(bdetails) + var.addBenchmark(bm) + tab.addVariant(var) + self.tables[tab.getDBName()] = (tab) + + + def extractDefaults(self, cfg): + defs = cfg.pop('defaults', None) + if defs: + defmap = defs.pop('mapping', None) + if defmap: + self.defaultMapper.add(defmap) + + def getDefaultMapper(self): + return self.defaultMapper + + def getTable(self, tablename): + tab = self.tables.get(tablename, None) + if tab: + return tab + return TableDetails(tablename) + + def getVariant(self, tablename, variantname): + tab = self.getTable(tablename) + if tab: + return tab.getVariant(variantname) + return VariantDetails(variantname) # Default + + def getBenchmark(self, table, variant, bechmark): + tab = self.getTable(table) + if tab: + var = tab.getVariant(variant) + if var: + return var.getBenchmark(bechmark) + return BenchmarkDetails(benchmark) # Default + + def __repr__(self): + ret = str(self.defaultMapper) + '\n' + for tabledetails in self.tables.values(): + ret += str(tabledetails) + '\n' + return ret + +if __name__ == "__main__": + dd = DetailDealer('./test.yml') + pprint(dd) diff --git a/tools/analysis/resultbrowser/app/model.py b/tools/analysis/resultbrowser/app/model.py new file mode 100755 index 00000000..465c3f77 --- /dev/null +++ b/tools/analysis/resultbrowser/app/model.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python +import MySQLdb +import MySQLdb.cursors +import yaml + +import sys +import os.path + +from pprint import pprint +import data +import details + +"""Get command line options""" +from optparse import OptionParser +parser = OptionParser() +parser.add_option("-c", "--conf", type="string", help="MySQL config file", dest="config", default= os.path.join(os.path.expanduser("~"),".my.cnf")) +parser.add_option("-s", "--host", type="string", help="Webserver hostname", dest="host", default="localhost") +parser.add_option("-d", "--details", type="string", help="Detailed information (YAML configuration file)", dest="details", default=None) +parser.add_option("-p", "--port", type="string", help="Webserver port", dest="port", default="5000") +opts, args = parser.parse_args() + +"""Check if configuration files exist""" +def checkConfigFile(msg, fname): + if not os.path.isfile(fname): + sys.exit("Error: '" + fname + "' not found") + else: + print msg, "->", fname + +# Check sql config +sqlconfig = opts.config +checkConfigFile("MySQL config", sqlconfig) + +# Check details file +if opts.details: + checkConfigFile("Details", opts.details) + +# Instantiate global detail dealer, will be initialized in reloadOverview +detaildealer = details.DetailDealer() + + +"""Remove all characters from string except alphanuermics and _""" +def scrub(table_name): + return ''.join( chr for chr in table_name if chr.isalnum() or chr == '_' ) + +"""Global mysql handles""" +db = None +cur = None +def loadSession(dbconf): + global db + if db: + db.close() + db = MySQLdb.connect(read_default_file=dbconf, cursorclass=MySQLdb.cursors.DictCursor) + return db.cursor() + + +def closeSession(): + if cur: cur.close() + global db + db.close() + db = None + + +'''Populate variant results for overview data''' +def getVariants(cur, table): + restbl = table.getDetails().getDBName() + cur.execute("""SELECT resulttype, variant, variant_id, benchmark, count(*) as total from %s join fsppilot on %s.pilot_id=fsppilot.id join variant on fsppilot.variant_id=variant.id group by resulttype, variant.id ORDER BY variant.id""" % (restbl, restbl)) # % is used here, as a tablename must not be quoted + res = cur.fetchall() + rdic = {} + # Build dict with variant id as key + for r in res: + # if variant entry already exists: + variant = table.getVariant(int(r['variant_id'])) + if not variant: # if variant did not exist yet, create it: + variant_details = detaildealer.getVariant(restbl, r['variant']) + benchmark_details = detaildealer.getBenchmark(restbl, r['variant'], r['benchmark']) + table_details = detaildealer.getTable(restbl) + variant = data.Variant(int(r['variant_id']), r['variant'], table_details, benchmark_details, variant_details) + variant.addResulttype(r['resulttype'], r['total']) + table.addVariant(variant) + +'''Get overview data for index page''' +def reloadOverview(): + overview = data.Overview() + detaildealer.reload(opts.details) + cur = loadSession(sqlconfig) + cur.execute("show tables like 'result_%'") + result_tables = cur.fetchall() + results = {} + for rdic in result_tables: + # r is the tablename, -> result_FOOBAR + for key, tablename in rdic.items(): + table = data.ResultTable(tablename,detaildealer) + getVariants(cur, table) + overview.add(table) + # Check if objdump table exists + cur.execute("SHOW TABLES like 'objdump'") + objdump_exists = (len(cur.fetchall()) == 1) + closeSession() + return overview, objdump_exists + +"""Load overview data at server startup""" +print "Loading overview data from database. This may take a while ..." +overview_data, objdump_exists = reloadOverview() +print "done." +## Get overview data for views.index() +def getOverview(): + return overview_data + +def objdumpExists(): + return objdump_exists + + +"""Get Results for one variant id""" +def getVariantResult(table, variantid): + cur = loadSession(sqlconfig) + restbl = scrub(table) + + stmt = "SELECT resulttype, count(*) as total from %s join fsppilot on %s.pilot_id=fsppilot.id join variant on fsppilot.variant_id=variant.id" % (restbl, restbl) + where = " WHERE variant.id = %s group by resulttype ORDER BY resulttype" + stmt = stmt + where + cur.execute(stmt, variantid) + res = cur.fetchall() + closeSession() + return res + +'''Show objdump together with according injection result types.''' +def getCode(result_table, variant_id, resultlabel=None): + result_table = scrub(result_table) + filt = '' + if not variant_id or not result_table: + return None + variant = overview_data.getVariantById(variant_id) + mapper = variant.getMapper() + if resultlabel: + dbnames = mapper.getDBNames(resultlabel) + if dbnames: + filt = " and ( " + for dbn in dbnames[:-1]: + filt += "resulttype = '" + dbn + "' OR " + filt += "resulttype = '" + dbnames[-1] +"' ) " + else: + filt = " and resulttype = '" + resultlabel + "' " + + # I especially like this one: + select = "SELECT instr_address, opcode, disassemble, comment, COUNT(*) as totals, GROUP_CONCAT(DISTINCT resulttype SEPARATOR ', ') as results FROM %s " % result_table + join = "JOIN fsppilot ON pilot_id = fsppilot.id JOIN objdump ON objdump.instr_address = injection_instr_absolute " + where = "WHERE objdump.variant_id = %s AND fsppilot.variant_id = %s " + group = "GROUP BY injection_instr_absolute ORDER BY injection_instr_absolute " + + cur = loadSession(sqlconfig) + stmt = select + join + where + filt + group + cur.execute(stmt, (variant_id, variant_id)) + dump = cur.fetchall() + + closeSession() + resulttypes = variant.getResultLabels() + return dump, resulttypes + +def getCodeExcerpt(variant_id, instr_addr): + code = {} + limit = 4 + cur = loadSession(sqlconfig) + cur.execute( """(SELECT instr_address, opcode, disassemble, comment FROM objdump \ + WHERE instr_address < %s AND variant_id = %s \ + ORDER BY instr_address DESC LIMIT %s) \ + ORDER BY instr_address ASC""" , (instr_addr, variant_id, limit)) + below = cur.fetchall() + code['below'] = below + cur.execute("""SELECT instr_address, opcode, disassemble, comment FROM objdump \ + WHERE instr_address >= %s AND variant_id = %s \ + ORDER BY instr_address ASC LIMIT %s""", (instr_addr, variant_id, limit+1)) + upper = cur.fetchall() + code['upper'] = upper + closeSession() + return code + +def getResultsbyInstruction(result_table, variant_id, instr_addr, resultlabel=None): + restypefilter = None + if resultlabel: + variant = overview_data.getVariantById(variant_id) + mapper = variant.getMapper() + if resultlabel: + dbnames = mapper.getDBNames(resultlabel) + if dbnames: + restypefilter = " and ( " + for dbn in dbnames[:-1]: + restypefilter += "resulttype = '" + dbn + "' OR " + restypefilter += "resulttype = '" + dbnames[-1] +"' ) " + + + #select = "SELECT data_address, data_width, original_value, bitoffset, experiment_number, details, resulttype from %s " % scrub(result_table) + select = "SELECT * from %s " % scrub(result_table) + join = "JOIN fsppilot ON pilot_id = fsppilot.id " + where = "WHERE variant_id = %s and injection_instr_absolute = %s " + order = "ORDER BY data_address, bitoffset" + + cur = loadSession(sqlconfig) + if not restypefilter: + stmt = select + join + where + order + cur.execute(stmt, (variant_id, instr_addr)) + else: + stmt = select + join + where + restypefilter + order + cur.execute(stmt, (variant_id, instr_addr)) + + res = cur.fetchall() + closeSession() + return res + +def showDBstatus(): + res = "TODO" + return res + + diff --git a/tools/analysis/resultbrowser/app/static/css/barchart.css b/tools/analysis/resultbrowser/app/static/css/barchart.css new file mode 100644 index 00000000..b68912c3 --- /dev/null +++ b/tools/analysis/resultbrowser/app/static/css/barchart.css @@ -0,0 +1,5 @@ +dl.horizontal {font-size:12px; width:850px;} +dl.horizontal dt {float:left; width:300px; clear:both; margin:0 0 5px 0; padding:3px;} +dl.horizontal dd {float:left; width:500px; border:1px solid #aaaaaa; margin:0 0 5px 0; padding:2px; -moz-box-shadow: 1px 1px 3px #aaaaaa;} +dl.horizontal dd span {background:#91b4e6; display:block; color:black; text-indent:4px;} + diff --git a/tools/analysis/resultbrowser/app/static/css/main.css b/tools/analysis/resultbrowser/app/static/css/main.css new file mode 100644 index 00000000..a6ec20ed --- /dev/null +++ b/tools/analysis/resultbrowser/app/static/css/main.css @@ -0,0 +1,214 @@ + +body { + margin: 0; + padding: 0; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #444; +} + +a:visited { + color : #444; +} + +a:link { + color : #444; + text-decoration: none; +} +/* + * Create dark grey header with a white logo + */ + +header { + background-color: #2B2B2B; + height: 30px; + width: 100%; + opacity: .9; + margin-bottom: 10px; +} + +header h1.logo { + margin: 0; + font-size: 1.5em; + color: #fff; + text-transform: uppercase; + float: left; +} + +header h1.logo:hover { + color: #fff; + text-decoration: none; +} + + +/* + * Center the body content + */ + +.container { + width: 95%; + margin: 0 auto; +} + +div.jumbo { + padding: 10px 0 30px 0; + background-color: #eeeeee; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +h2 { + font-size: 1.2em; + margin-top: 40px; + text-align: center; + letter-spacing: -1px; +} + +h3 { + font-size: 1.0em; + font-weight: 100; + margin-top: 30px; + text-align: center; + /*letter-spacing: -1px;*/ + color: #999; +} + + +#footer { + text-align: center; + color: #444; + font-size:10pt; +} + +/* + * Table styles + */ + +.codetable, .resulttable, .overviewtable{ + font-family: monospace; + margin: auto; +} + +.overviewtable { + border: 1px gray solid; + margin: auto; +} +/* +.overviewtable tr:nth-child(4n), .overviewtable tr:nth-child(4n-1) { + background: #fff; +} +.overviewtable tr:nth-child(4n-2), .overviewtable tr:nth-child(4n-3) { + background: #e8ffb3; +} +*/ + +.resulttable, .codetable, .overviewtable{ + margin: auto; + border: solid #ccc 1px; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 1px 1px #ccc; + -moz-box-shadow: 0 1px 1px #ccc; + box-shadow: 0 1px 1px #ccc; +} + +.codetable tr:hover, .resulttable tr:hover { + background: #fbf8e9; +/* -o-transition: all 0.1s ease-in-out; + -webkit-transition: all 0.1s ease-in-out; + -moz-transition: all 0.1s ease-in-out; + -ms-transition: all 0.1s ease-in-out; + transition: all 0.1s ease-in-out; +*/ +} + +.resulttable td, .resulttable th, .codetable td, .codetable th { + border-left: none; + border-top: none; + padding: 2px; + text-align: left; + display: table-cell; +} + +.codetable td a { + text-decoration: none; + display: block; + padding: 0px; + height: 100%; +} + +.resulttable th, .codetable th, .overviewtable th { + background-color: #dce9f9; + background-image: -webkit-gradient(linear, left top, left bottom, from(#efe), to(#91b4e6)); + background-image: -webkit-linear-gradient(top, #efe, #91b4e6); + background-image: -moz-linear-gradient(top, #efe, #91b4e6); + background-image: -ms-linear-gradient(top, #efe, #91b4e6); + background-image: -o-linear-gradient(top, #efe, #91b4e6); + background-image: linear-gradient(top, #efe, #91b4e6); + -webkit-box-shadow: 0 1px 0 rgba(255,255,255,.8) inset; + -moz-box-shadow:0 1px 0 rgba(255,255,255,.8) inset; + box-shadow: 0 1px 0 rgba(255,255,255,.8) inset; + border-top: none; + text-shadow: 0 1px 0 rgba(255,255,255,.5); +} + +.resulttable td:first-child, .resulttable th:first-child, .codetable td:first-child, .codetable th:first-child { + border-left: none; +} + +.resulttable th:first-child, .codetable th:first-child { + -moz-border-radius: 6px 0 0 0; + -webkit-border-radius: 6px 0 0 0; + border-radius: 6px 0 0 0; +} + +.resulttable th:last-child, .codetable th:last-child { + -moz-border-radius: 0 6px 0 0; + -webkit-border-radius: 0 6px 0 0; + border-radius: 0 6px 0 0; +} + +.resulttable th:only-child, .codetable th:only-child{ + -moz-border-radius: 6px 6px 0 0; + -webkit-border-radius: 6px 6px 0 0; + border-radius: 6px 6px 0 0; +} + +.resulttable tr:last-child td:first-child, .codetable tr:last-child td:first-child { + -moz-border-radius: 0 0 0 6px; + -webkit-border-radius: 0 0 0 6px; + border-radius: 0 0 0 6px; +} + +.resulttable tr:last-child td:last-child, .codetable tr:last-child td:last-child { + -moz-border-radius: 0 0 6px 0; + -webkit-border-radius: 0 0 6px 0; + border-radius: 0 0 6px 0; +} + +.resulttypemenu { + text-align: center; + font-size: 12px +} +/* + * Display navigation links inline + */ + +.menu { + float: right; + margin-top: 8px; +} + +.menu li { + display: inline; +} + +.menu li + li { + margin-left: 35px; +} + +.menu li a { + color: #999; + text-decoration: none; +} diff --git a/tools/analysis/resultbrowser/app/static/favicon.ico b/tools/analysis/resultbrowser/app/static/favicon.ico new file mode 100644 index 00000000..cf9bfb16 Binary files /dev/null and b/tools/analysis/resultbrowser/app/static/favicon.ico differ diff --git a/tools/analysis/resultbrowser/app/templates/about.html b/tools/analysis/resultbrowser/app/templates/about.html new file mode 100644 index 00000000..22d0562d --- /dev/null +++ b/tools/analysis/resultbrowser/app/templates/about.html @@ -0,0 +1,11 @@ +{% extends "layout.html" %} + +{% block content %} + +{% if status %} +

About

+{{ status }} +{% else %} +

Sorry, no status information.

+{% endif %} +{% endblock %} diff --git a/tools/analysis/resultbrowser/app/templates/code.html b/tools/analysis/resultbrowser/app/templates/code.html new file mode 100644 index 00000000..920b1da8 --- /dev/null +++ b/tools/analysis/resultbrowser/app/templates/code.html @@ -0,0 +1,50 @@ +{% extends "layout.html" %} + +{% block content %} + +

Variant Results

+ +{% if resulttypes %} + + + + + + + +
Result Table{{ variant_details.getTableDetails().getTitle() }}
Variant {{ variant_details.getDetails().getTitle() }}
Details {{ variant_details.getDetails().getDetails() }}
Benchmark {{ variant_details.getBenchmarkDetails().getTitle() }}
Details {{ variant_details.getBenchmarkDetails().getDetails() }}
Result Count {{ results|length }}
+
+

| All Results | + {% for restype in resulttypes %} + {{ restype}} | + {% endfor %} +

+
+ + + + + + + + + + {% for d in results %} + {% set link = url_for('instr_details', table=request.args.get('table'), variant_id=request.args.get('variant_id'), benchmark=request.args.get('benchmark'), variant=request.args.get('variant'), resulttype=request.args.get('resulttype'), instr_address=d['instr_address'] ) %} + + + + + + + + + {% endfor %} +
AddressOpcodeDisassemblyCommentResults# Results
{{ "0x%x"|format(d['instr_address']) }}{{ d['opcode'] }}{{ d['disassemble'] }}{{ d['comment'] }}{{ d['results'] }}{{ d['totals'] }}
+{% else %} +

Sorry, no dump found.

+{% endif %} + +{%endblock %} + + diff --git a/tools/analysis/resultbrowser/app/templates/index.html b/tools/analysis/resultbrowser/app/templates/index.html new file mode 100644 index 00000000..31abf68e --- /dev/null +++ b/tools/analysis/resultbrowser/app/templates/index.html @@ -0,0 +1,55 @@ +{% extends "layout.html" %} + +{% block content %} +{%if overview %} + {% for tablekey, resulttable in overview.getTables().items() %} +

Result table: {{ resulttable.getDetails().getTitle() }}

+ -> Reload data <- +
+
Details: +
+ {{ resulttable.getDetails().getDetails() }} +
+
+ {% if not objdump_there %} + No objdump found + {% endif %} + + {% for varid, variant in resulttable.getVariants().items() %} + + + {% endif %} + + {% endfor %} +
+ {% set variant_title=variant.getDetails().getTitle() ~ " - " ~ variant.getBenchmarkDetails().getTitle() ~ " id: " ~ variant.getId() %} + {% if objdump_there %} + {{ variant_title }} + {% else %} + {{ variant_title }} + {% endif %} + (Total: {{ variant.getTotals() }}) +
+ {% if variant.getDetails().getDetails() %} + Variant Details: {{ variant.getDetails().getDetails() }} + {% endif %} + {% if variant.getBenchmarkDetails().getDetails() %} +
Benchmark Details: {{ variant.getBenchmarkDetails().getDetails() }}
+
+ {% for reslabel,count in variant.getResults().items() %} +
+ {% if objdump_there %} + {{ reslabel }} + {% else %} + {{ reslabel }} + {% endif %} +
+
{{count}}
+ {% endfor %} +
+ {% endfor %} +{% else %} +

Sorry, no results found.

+{% endif %} + +{% endblock %} diff --git a/tools/analysis/resultbrowser/app/templates/instr_details.html b/tools/analysis/resultbrowser/app/templates/instr_details.html new file mode 100644 index 00000000..232c3792 --- /dev/null +++ b/tools/analysis/resultbrowser/app/templates/instr_details.html @@ -0,0 +1,65 @@ +{% extends "layout.html" %} + +{% block content %} +{% if code %} +

Result by Instruction

+ + + + + + + +
Result Table{{ variant_details.getTableDetails().getTitle() }}
Variant {{ variant_details.getDetails().getTitle() }}
Benchmark {{ variant_details.getBenchmarkDetails().getTitle() }}
Details {{ variant_details.getDetails().getDetails() }}
Instruction Address {{ "0x%x (Dec: %d)"|format(request.args.get('instr_address')|int, request.args.get('instr_address')|int) }}
Total Results{{ result|length }}
+
+

Code

+ + + + + + + + {% for d in code['below'] %} + + + + + + + {% endfor %} + + + + + + + {% for d in code['upper'][1:] %} + + + + + + + {% endfor %}
AddressOpcodeDisassemblyComment
{{ "0x%x"|format(d['instr_address']) }}{{ d['opcode'] }}{{ d['disassemble'] }}{{ d['comment'] }}
{{ "0x%x"|format(code['upper'][0]['instr_address']) }}{{ code['upper'][0]['opcode'] }}{{ code['upper'][0]['disassemble'] }}{{ code['upper'][0]['comment'] }}
{{ "0x%x"|format(d['instr_address']) }}{{ d['opcode'] }}{{ d['disassemble'] }}{{ d['comment'] }}
+
+

Results ({{ result|length }})

+ + + {% for key, value in result[0].items() -%} + + {% endfor -%} + + {% for r in result -%} + + {% for k,v in r.items() -%} + + {% endfor -%} + + {% endfor -%} + +
{{ key }}
{{ v }}
+{% else %} +

Sorry, no details found.

+{% endif %} +{% endblock %} diff --git a/tools/analysis/resultbrowser/app/templates/layout.html b/tools/analysis/resultbrowser/app/templates/layout.html new file mode 100644 index 00000000..e2a6402c --- /dev/null +++ b/tools/analysis/resultbrowser/app/templates/layout.html @@ -0,0 +1,32 @@ + + + + Fail* Results + + + + + +
+
+

Fail*

+ +
+
+ +
+ {% block content %} + {% endblock %} +
+ +
+ + + diff --git a/tools/analysis/resultbrowser/app/views.py b/tools/analysis/resultbrowser/app/views.py new file mode 100644 index 00000000..19ea6999 --- /dev/null +++ b/tools/analysis/resultbrowser/app/views.py @@ -0,0 +1,39 @@ +from flask import render_template,request +from app import app + +import model +import data + +@app.route('/') +@app.route('/index') +def index(): + reload_overview = request.args.get('reload', False) + if reload_overview: + print "Reloading overview..." + model.reloadOverview() + return render_template("index.html", overview=model.getOverview(), objdump_there = model.objdumpExists()) + +@app.route('/code') +def code(): + variant_id = request.args.get('variant_id', None) + resulttype = request.args.get('resulttype', None) + table = request.args.get('table', None) + res,restypes = model.getCode(table, variant_id, resulttype) + var_dets = model.getOverview().getVariantById(variant_id) + return render_template("code.html", results=res, resulttypes=restypes, variant_details=var_dets ) + +@app.route('/instr_details') +def instr_details(): + table = request.args.get('table', None) + variant_id = request.args.get('variant_id', None) + instr_addr = request.args.get('instr_address', None) + resulttype = request.args.get('resulttype', None) + codeexcerpt = model.getCodeExcerpt(variant_id, instr_addr) + var_dets = model.getOverview().getVariantById(variant_id) + results = model.getResultsbyInstruction(table, variant_id, instr_addr, resulttype) + return render_template("instr_details.html", code=codeexcerpt, result=results, variant_details=var_dets) + +@app.route('/about') +def about(): + stat = model.showDBstatus() + return render_template("about.html", status=stat) diff --git a/tools/analysis/resultbrowser/conf.yml b/tools/analysis/resultbrowser/conf.yml new file mode 100644 index 00000000..14105de3 --- /dev/null +++ b/tools/analysis/resultbrowser/conf.yml @@ -0,0 +1,69 @@ +# YAML-based: http://yaml.org/ +# Online parser for testing: http://yaml-online-parser.appspot.com/ +# Some notes: +# YAML is case-sensitive and structured by indention! +# +# The 'defaults' section describes an *optional* default result type mapping for all tables. +# The 'tables' section describes result tables in more detail. +# A table consists of variants, each variant of benchmarks. +# Each of these configuration items +# title: Table title +# details: Some textual description +# mapping: A distinct mapping, if not set, the parent item's mapping is used + +defaults: + mapping: + Everything OK: + - OK + - OK_DETECTED_ERROR + - OK_WRONG_CONTROL_FLOW + Outside Data Section: + - ERR_OUTSIDE_DATA + Hardware Trap: + - ERR_OUTSIDE_TEXT + - ERR_TRAP + Silent Data Corruption: + - ERR_WRONG_RESULT + +tables: + result_CoredVoterProtoMsg: + title: CoRed Voter Experiment Results + variants: + x86_cored_voter: + title: x86 CoRed Voter Experiment + details: Some interesting details about the experiment. + + benchmarks: + ean-random-4: + title: Random 4 bit injections + details: | + The details can also written this way. + The pipe insert the newlines. Cool, isn't it? + + ean-random-5: + title: Random 5 bit injections + details: Details about 5 bit random injection benchmark. + mapping: + Alright: + - OK + - OK_DETECTED_ERROR + - OK_WRONG_CONTROL_FLOW + Not Alright: + - ERR_OUTSIDE_DATA + - ERR_OUTSIDE_TEXT + - ERR_TRAP + - ERR_WRONG_RESULT + Timeout: + - ERR_TIMEOUT + # Another variant within result_CoredVoterProtoMsg + x86_cored_voter2: + title: variant title + details: variant details + benchmarks: + ean-random-2: + title: benchmarktitle + details: some benchmark details + + ean-random-3: + title: benchmark random 3 + details: some benchmark 3 details diff --git a/tools/analysis/resultbrowser/run.py b/tools/analysis/resultbrowser/run.py new file mode 100755 index 00000000..10848933 --- /dev/null +++ b/tools/analysis/resultbrowser/run.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +from app import app +from app import model + +app.run(debug=False, port=int(model.opts.port), host=model.opts.host)