From 654cd1dc9ef2add379b01eac83f07730d9a9fe29 Mon Sep 17 00:00:00 2001 From: Christoph Urlacher Date: Sun, 18 Jan 2026 20:58:13 +0100 Subject: [PATCH] resultbrowser: remove circular detaildealer import --- tools/analysis/resultbrowser/app/data.py | 61 ++++--- .../resultbrowser/app/detaildealer.py | 4 + tools/analysis/resultbrowser/app/model.py | 172 +++++++++++++----- tools/analysis/resultbrowser/app/views.py | 49 +++-- tools/analysis/resultbrowser/run.py | 3 +- 5 files changed, 198 insertions(+), 91 deletions(-) create mode 100644 tools/analysis/resultbrowser/app/detaildealer.py diff --git a/tools/analysis/resultbrowser/app/data.py b/tools/analysis/resultbrowser/app/data.py index 15528314..441b583f 100644 --- a/tools/analysis/resultbrowser/app/data.py +++ b/tools/analysis/resultbrowser/app/data.py @@ -1,9 +1,12 @@ from pprint import pprint + from . import details -from . import model +from app.detaildealer import detaildealer + def scrub(table_name): - return ''.join( chr for chr in table_name if chr.isalnum() or chr == '_' ) + return "".join(chr for chr in table_name if chr.isalnum() or chr == "_") + class Resulttype: def __init__(self, name, count): @@ -16,28 +19,27 @@ class Resulttype: 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.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 + if not mapper: # try benchmark mapper mapper = self.details.getMapper() - if not mapper: # of not there, try parent tables mapper + 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() + if not mapper: # no mapper found at all, try default mapper + mapper = detaildealer.getDefaultMapper() return mapper - def addResulttype(self, name, count): mapper = self.getMapper() label = mapper.getLabel(name) @@ -70,17 +72,28 @@ class Variant: 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" + 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" + ret += "\t" + v.name + ": " + str(v.count) + "\n" return ret __repr__ = __str__ -'''A ResultTable contains n Variants''' -class ResultTable: +"""A ResultTable contains n Variants""" + + +class ResultTable: def __init__(self, name, cfg): self.name = scrub(name) self.details = cfg.getTable(name) @@ -89,7 +102,7 @@ class ResultTable: def addVariant(self, var): if var.getId() in self.variants: return - self.variants[var.getId()] = var # Add if not existing yet + self.variants[var.getId()] = var # Add if not existing yet def getVariant(self, id): if id in self.variants: @@ -97,7 +110,7 @@ class ResultTable: return None def getVariantById(self, varid): - for k,v in self.variants.items(): + for k, v in self.variants.items(): if int(v.getId()) == int(varid): return v return None @@ -110,12 +123,16 @@ class ResultTable: def __str__(self): ret = "Result: " + self.getDetails().getTitle() + "\n" - for k,v in self.variants.items(): + for k, v in self.variants.items(): ret += "\t" + str(v) + "\n" return ret + __repr__ = __str__ -'''Overview has n ResultTables''' + +"""Overview has n ResultTables""" + + class Overview: def __init__(self): self.tables = {} @@ -130,7 +147,7 @@ class Overview: return self.tables.get(dbname, None) def getVariantById(self, variant_id): - for key,table in self.tables.items(): + for key, table in self.tables.items(): variant = table.getVariantById(variant_id) if variant: return variant @@ -139,5 +156,3 @@ class Overview: def length(self): return len(self.tables) - - diff --git a/tools/analysis/resultbrowser/app/detaildealer.py b/tools/analysis/resultbrowser/app/detaildealer.py new file mode 100644 index 00000000..cacd2f69 --- /dev/null +++ b/tools/analysis/resultbrowser/app/detaildealer.py @@ -0,0 +1,4 @@ +# Instantiate global detail dealer, will be initialized in reloadOverview +from app import details + +detaildealer = details.DetailDealer() diff --git a/tools/analysis/resultbrowser/app/model.py b/tools/analysis/resultbrowser/app/model.py index d9529f2e..58500246 100755 --- a/tools/analysis/resultbrowser/app/model.py +++ b/tools/analysis/resultbrowser/app/model.py @@ -1,31 +1,60 @@ #!/usr/bin/env python -import MySQLdb -import MySQLdb.cursors -import yaml - -import sys import os.path +import yaml +import sys + +import MySQLdb +import MySQLdb.cursors + +from app.detaildealer import detaildealer + from pprint import pprint -from . import data -from . import details +from . import data, 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") +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) @@ -35,50 +64,74 @@ if opts.details: checkConfigFile("Details", opts.details) # Instantiate global detail dealer, will be initialized in reloadOverview -detaildealer = details.DetailDealer() +# 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 == '_' ) + 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) + db = MySQLdb.connect( + read_default_file=dbconf, cursorclass=MySQLdb.cursors.DictCursor + ) return db.cursor() def closeSession(): - if cur: cur.close() + if cur: + cur.close() global db db.close() db = None -'''Populate variant results for overview data''' +"""Populate variant results for overview data""" + + def getVariants(cur, table): restbl = table.getDetails().getDBName() - cur.execute("""SELECT sum((t.time2 - t.time1 + 1) * width) AS total, resulttype,variant, v.id as variant_id, benchmark, details FROM variant v JOIN trace t ON v.id = t.variant_id JOIN fspgroup g ON g.variant_id = t.variant_id AND g.instr2 = t.instr2 AND g.data_address = t.data_address JOIN %s r ON r.pilot_id = g.pilot_id JOIN fsppilot p ON r.pilot_id = p.id GROUP BY v.id, resulttype, details""" % (restbl)) # % is used here, as a tablename must not be quoted + cur.execute( + """SELECT sum((t.time2 - t.time1 + 1) * width) AS total, resulttype,variant, v.id as variant_id, benchmark, details FROM variant v JOIN trace t ON v.id = t.variant_id JOIN fspgroup g ON g.variant_id = t.variant_id AND g.instr2 = t.instr2 AND g.data_address = t.data_address JOIN %s r ON r.pilot_id = g.pilot_id JOIN fsppilot p ON r.pilot_id = p.id GROUP BY v.id, resulttype, details""" + % (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']) + 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']) + 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''' + +"""Get overview data for index page""" + + def reloadOverview(): overview = data.Overview() detaildealer.reload(opts.details) @@ -89,33 +142,42 @@ def reloadOverview(): for rdic in result_tables: # r is the tablename, -> result_FOOBAR for key, tablename in rdic.items(): - table = data.ResultTable(tablename,detaildealer) + 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) + 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 r join fsppilot on r.pilot_id=fsppilot.id join variant on fsppilot.variant_id=variant.id" % (restbl) + stmt = ( + "SELECT resulttype, count(*) as total from %s r join fsppilot on r.pilot_id=fsppilot.id join variant on fsppilot.variant_id=variant.id" + % (restbl) + ) where = " WHERE variant.id = %s group by resulttype ORDER BY resulttype " stmt = stmt + where cur.execute(stmt, variantid) @@ -123,10 +185,13 @@ def getVariantResult(table, variantid): closeSession() return res -'''Show objdump together with according injection result types.''' + +"""Show objdump together with according injection result types.""" + + def getCode(result_table, variant_id, resultlabel=None): result_table = scrub(result_table) - filt = '' + filt = "" if not variant_id or not result_table: return None variant = overview_data.getVariantById(variant_id) @@ -137,15 +202,18 @@ def getCode(result_table, variant_id, resultlabel=None): filt = " and ( " for dbn in dbnames[:-1]: filt += "resulttype = '" + dbn + "' OR " - filt += "resulttype = '" + dbnames[-1] +"' ) " + filt += "resulttype = '" + dbnames[-1] + "' ) " else: filt = " and resulttype = '" + resultlabel + "' " # I especially like this one: select = "SELECT instr_address, opcode, disassemble, comment, sum(t.time2 - t.time1 + 1) as totals, GROUP_CONCAT(DISTINCT resulttype SEPARATOR ', ') as results FROM variant v " - join = " JOIN trace t ON v.id = t.variant_id JOIN fspgroup g ON g.variant_id = t.variant_id AND g.instr2 = t.instr2 AND g.data_address = t.data_address JOIN %s r ON r.pilot_id = g.pilot_id JOIN fsppilot p ON r.pilot_id = p.id JOIN objdump ON objdump.variant_id = v.id AND objdump.instr_address = injection_instr_absolute " %(scrub(result_table)) - where = "WHERE v.id = %s " - group = "GROUP BY injection_instr_absolute ORDER BY totals DESC " + join = ( + " JOIN trace t ON v.id = t.variant_id JOIN fspgroup g ON g.variant_id = t.variant_id AND g.instr2 = t.instr2 AND g.data_address = t.data_address JOIN %s r ON r.pilot_id = g.pilot_id JOIN fsppilot p ON r.pilot_id = p.id JOIN objdump ON objdump.variant_id = v.id AND objdump.instr_address = injection_instr_absolute " + % (scrub(result_table)) + ) + where = "WHERE v.id = %s " + group = "GROUP BY injection_instr_absolute ORDER BY totals DESC " cur = loadSession(sqlconfig) stmt = select + join + where + filt + group @@ -153,27 +221,35 @@ def getCode(result_table, variant_id, resultlabel=None): dump = cur.fetchall() closeSession() - resulttypes = variant.getResultLabels() + resulttypes = variant.getResultLabels() return dump, resulttypes + def getCodeExcerpt(variant_id, instr_addr): code = {} limit = 8 cur = loadSession(sqlconfig) - cur.execute( """(SELECT instr_address, opcode, disassemble, comment FROM objdump \ + 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)) + 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 \ + 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)) + ORDER BY instr_address ASC LIMIT %s""", + (instr_addr, variant_id, limit + 1), + ) upper = cur.fetchall() - code['upper'] = upper + code["upper"] = upper closeSession() return code + def getResultsbyInstruction(result_table, variant_id, instr_addr, resultlabel=None): restypefilter = None if resultlabel: @@ -185,12 +261,15 @@ def getResultsbyInstruction(result_table, variant_id, instr_addr, resultlabel=No restypefilter = " and ( " for dbn in dbnames[:-1]: restypefilter += "resulttype = '" + dbn + "' OR " - restypefilter += "resulttype = '" + dbnames[-1] +"' ) " + restypefilter += "resulttype = '" + dbnames[-1] + "' ) " - select = "SELECT bitoffset as 'Bit Offset', hex(injection_instr_absolute) as 'Instruction Address', hex(original_value) as 'Original Value', hex(data_address) as 'Data Address', resulttype as 'Result Type', details as 'Details' 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" + select = ( + "SELECT bitoffset as 'Bit Offset', hex(injection_instr_absolute) as 'Instruction Address', hex(original_value) as 'Original Value', hex(data_address) as 'Data Address', resulttype as 'Result Type', details as 'Details' 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: @@ -204,8 +283,7 @@ def getResultsbyInstruction(result_table, variant_id, instr_addr, resultlabel=No closeSession() return res + def showDBstatus(): res = "TODO" return res - - diff --git a/tools/analysis/resultbrowser/app/views.py b/tools/analysis/resultbrowser/app/views.py index 240802d7..b22a8e78 100644 --- a/tools/analysis/resultbrowser/app/views.py +++ b/tools/analysis/resultbrowser/app/views.py @@ -1,42 +1,53 @@ -from flask import render_template,request +from flask import render_template, request + from app import app # import model # import data - from . import model + from . import data -@app.route('/') -@app.route('/index') + +@app.route("/") +@app.route("/index") def index(): - reload_overview = request.args.get('reload', False) + 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()) + return render_template( + "index.html", overview=model.getOverview(), objdump_there=model.objdumpExists() + ) -@app.route('/code') + +@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) + 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 ) + return render_template( + "code.html", results=res, resulttypes=restypes, variant_details=var_dets + ) -@app.route('/instr_details') + +@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) + 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) + return render_template( + "instr_details.html", code=codeexcerpt, result=results, variant_details=var_dets + ) -@app.route('/about') + +@app.route("/about") def about(): stat = model.showDBstatus() return render_template("about.html", status=stat) diff --git a/tools/analysis/resultbrowser/run.py b/tools/analysis/resultbrowser/run.py index 10848933..61d0eb9c 100755 --- a/tools/analysis/resultbrowser/run.py +++ b/tools/analysis/resultbrowser/run.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -from app import app -from app import model +from app import app, model app.run(debug=False, port=int(model.opts.port), host=model.opts.host)