| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| # |
| # Copyright (C) 2017-2020 The Project X-Ray Authors. |
| # |
| # Use of this source code is governed by a ISC-style |
| # license that can be found in the LICENSE file or at |
| # https://opensource.org/licenses/ISC |
| # |
| # SPDX-License-Identifier: ISC |
| |
| import os, re |
| from prjxray import util |
| |
| |
| def noprefix(tag): |
| n = tag.find('.') |
| assert n > 0 |
| return tag[n + 1:] |
| |
| |
| def getprefix(tag): |
| n = tag.find('.') |
| assert n > 0 |
| return tag[0:n] |
| |
| |
| def load_pipfile(pipfile, verbose=False): |
| '''Returns a set of tags containing real tile type prefixes (ex: INT_L)''' |
| todos = set() |
| tile_type = None |
| with open(pipfile, "r") as f: |
| # INT_L.WW2BEG0.SR1BEG_S0 |
| for line in f: |
| tag = line.strip().split(' ')[0] |
| prefix_line = getprefix(tag) |
| if tile_type is None: |
| tile_type = prefix_line |
| else: |
| assert tile_type == prefix_line |
| todos.add(tag) |
| return todos, tile_type |
| |
| |
| def balance_todo_list( |
| pipfile, |
| todos, |
| balance_wire_re, |
| balance_wire_direction, |
| balance_wire_cnt, |
| verbose=False): |
| """Balance the contents of the todo list |
| |
| Todo list balancing allows to specify the name, direction and minimal number of |
| occurrences of a PIP wire in the final todo list. |
| The mechanism should be used in cases where a fuzzer times out because of an |
| unsolvable todo list, i.e. the netlist and resulting segdata generated from an |
| iteration keep segmatch from properly disambiguating the bits for some features. |
| |
| When the balance wire name regexp is specified it's guaranteed that all PIPs |
| with matching wire name (whether we want to match a src or dst wire has to be |
| specified with the --balance-wire-direction switch) will have at least the number |
| of entries specified with the --balance-wire-cnt switch in the final todo list. |
| """ |
| orig_todos, tile_type = load_pipfile(pipfile, verbose=verbose) |
| if balance_wire_re is not None: |
| todo_wires = {} |
| verbose and print("Start balancing the TODO list") |
| for todo in todos: |
| tile_type, dst, src = todo.split(".") |
| wire = src |
| other_wire = dst |
| if balance_wire_direction not in "src": |
| wire = dst |
| other_wire = src |
| balance_wire_match = re.match(balance_wire_re, wire) |
| if balance_wire_match is None: |
| continue |
| if wire not in todo_wires: |
| todo_wires[wire] = set() |
| todo_wires[wire].add(other_wire) |
| for wire, other_wires in todo_wires.items(): |
| if len(other_wires) >= balance_wire_cnt: |
| continue |
| else: |
| for todo in orig_todos: |
| tile_type, dst, src = todo.split(".") |
| if balance_wire_direction in "src": |
| if wire in src and dst not in todo_wires[wire]: |
| todo_wires[wire].add(dst) |
| else: |
| if wire in dst and src not in todo_wires[wire]: |
| todo_wires[wire].add(src) |
| if len(todo_wires[wire]) == balance_wire_cnt: |
| break |
| for wire, other_wires in todo_wires.items(): |
| if len(other_wires) < balance_wire_cnt: |
| verbose and print( |
| "Warning: failed to balance the todo list for wire {}, there are only {} PIPs which meet the requirement: {}" |
| .format(wire, len(other_wires), other_wires)) |
| for other_wire in other_wires: |
| line = tile_type + "." |
| if balance_wire_direction in "src": |
| line += other_wire + "." + wire |
| else: |
| line += wire + "." + other_wire |
| verbose and print("Adding {}".format(line)) |
| todos.add(line) |
| verbose and print("Finished balancing the TODO list") |
| |
| |
| def maketodo( |
| pipfile, |
| dbfile, |
| intre, |
| exclude_re=None, |
| balance_wire_re=None, |
| balance_wire_direction=None, |
| balance_wire_cnt=None, |
| not_endswith=None, |
| verbose=False): |
| ''' |
| db files start with INT., but pipfile lines start with INT_L |
| Normalize by removing before the first dot |
| 050-intpips doesn't care about contents, but most fuzzers use the tile type prefix |
| ''' |
| |
| todos, tile_type = load_pipfile(pipfile, verbose=verbose) |
| verbose and print('%s: %u entries' % (pipfile, len(todos))) |
| if not todos: |
| verbose and print('%s: %u entries, done!' % (pipfile, len(todos))) |
| return |
| |
| verbose and print("pipfile todo sample: %s" % list(todos)[0]) |
| |
| if 0 and verbose: |
| print("TODOs") |
| for todo in sorted(list(todos)): |
| print(' %s' % todo) |
| |
| verbose and print('Pre db %s: %u entries' % (dbfile, len(todos))) |
| # Allow against empty db |
| if os.path.exists(dbfile): |
| verbose and print("Loading %s" % dbfile) |
| with open(dbfile, "r") as f: |
| # INT.BYP_ALT0.BYP_BOUNCE_N3_3 !22_07 !23_07 !25_07 21_07 24_07 |
| for line in f: |
| tag, _bits, mode, _ = util.parse_db_line(line.strip()) |
| # Only count resolved entries |
| if mode: |
| continue |
| # INT.BLAH => INT_L.BLAH |
| tag = tile_type + '.' + noprefix(tag) |
| |
| # bipips works on a subset |
| if tag in todos: |
| todos.remove(tag) |
| else: |
| verbose and print( |
| "WARNING: couldnt remove %s (line %s)" % |
| (tag, line.strip())) |
| else: |
| verbose and print("WARNING: dbfile doesnt exist: %s" % dbfile) |
| verbose and print('Post db %s: %u entries' % (dbfile, len(todos))) |
| |
| drops = 0 |
| lines = 0 |
| filtered_todos = set() |
| for line in todos: |
| include = re.match(intre, line) is not None |
| |
| if include and not_endswith is not None: |
| include = not line.endswith(not_endswith) |
| |
| if include and exclude_re is not None: |
| include = re.match(exclude_re, line) is None |
| |
| if include: |
| filtered_todos.add(line) |
| else: |
| drops += 1 |
| lines += 1 |
| verbose and print('Print %u entries w/ %u drops' % (lines, drops)) |
| |
| balance_todo_list( |
| pipfile, filtered_todos, balance_wire_re, balance_wire_direction, |
| balance_wire_cnt, verbose) |
| for todo in filtered_todos: |
| print(todo) |
| |
| |
| def run( |
| build_dir, |
| db_dir, |
| pip_dir, |
| intre, |
| sides, |
| l, |
| r, |
| pip_type, |
| seg_type, |
| exclude_re=None, |
| balance_wire_re=None, |
| balance_wire_direction=None, |
| balance_wire_cnt=None, |
| not_endswith=None, |
| verbose=False): |
| if db_dir is None: |
| db_dir = "%s/%s" % ( |
| os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE")) |
| |
| if pip_dir is None: |
| pip_dir = "%s/piplist/build/%s" % ( |
| os.getenv("XRAY_FUZZERS_DIR"), pip_type) |
| |
| assert intre, "RE is required" |
| |
| for side in sides: |
| if side == "l" and not l: |
| continue |
| |
| if side == "r" and not r: |
| continue |
| |
| if side == "xl": |
| segfile = "l{}".format(seg_type) |
| pipfile = "l{}".format(pip_type) |
| elif side == "xr": |
| segfile = "r{}".format(seg_type) |
| pipfile = "r{}".format(pip_type) |
| elif side != "": |
| segfile = "{}_{}".format(seg_type, side) |
| pipfile = "{}_{}".format(pip_type, side) |
| else: |
| segfile = "{}".format(seg_type) |
| pipfile = "{}".format(pip_type) |
| |
| maketodo( |
| "%s/%s.txt" % (pip_dir, pipfile), |
| "%s/segbits_%s.db" % (db_dir, segfile), |
| intre, |
| exclude_re=exclude_re, |
| balance_wire_re=balance_wire_re, |
| balance_wire_direction=balance_wire_direction, |
| balance_wire_cnt=balance_wire_cnt, |
| not_endswith=not_endswith, |
| verbose=verbose) |
| |
| |
| def main(): |
| import argparse |
| |
| parser = argparse.ArgumentParser( |
| description="Print list of known but unsolved PIPs") |
| |
| parser.add_argument('--verbose', action='store_true', help='') |
| parser.add_argument('--build-dir', default="build", help='') |
| parser.add_argument('--db-dir', default=None, help='') |
| parser.add_argument('--pip-dir', default=None, help='') |
| parser.add_argument('--re', required=True, help='') |
| parser.add_argument('--exclude-re', required=False, default=None, help='') |
| parser.add_argument( |
| '--balance-wire-re', required=False, default=None, help='') |
| parser.add_argument( |
| '--balance-wire-direction', required=False, default="src", help='') |
| parser.add_argument( |
| '--balance-wire-cnt', required=False, default="1", help='') |
| parser.add_argument( |
| '--balance-re-wire', required=False, default="src", help='') |
| parser.add_argument('--pip-type', default="pips_int", help='') |
| parser.add_argument('--seg-type', default="int", help='') |
| parser.add_argument('--sides', default="l,r", help='') |
| util.add_bool_arg(parser, '--l', default=True, help='') |
| util.add_bool_arg(parser, '--r', default=True, help='') |
| parser.add_argument( |
| '--not-endswith', help='Drop lines if they end with this') |
| args = parser.parse_args() |
| |
| run( |
| build_dir=args.build_dir, |
| db_dir=args.db_dir, |
| pip_dir=args.pip_dir, |
| intre=args.re, |
| exclude_re=args.exclude_re, |
| balance_wire_re=args.balance_wire_re, |
| balance_wire_direction=args.balance_wire_direction, |
| balance_wire_cnt=int(args.balance_wire_cnt), |
| sides=args.sides.split(','), |
| l=args.l, |
| r=args.r, |
| pip_type=args.pip_type, |
| seg_type=args.seg_type, |
| not_endswith=args.not_endswith, |
| verbose=args.verbose) |
| |
| |
| if __name__ == '__main__': |
| main() |