qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

output_reproducer.py (5532B)


      1 #!/usr/bin/env python3
      2 # -*- coding: utf-8 -*-
      3 
      4 """
      5 Convert plain qtest traces to C or Bash reproducers
      6 
      7 Use this to help build bug-reports or create in-tree reproducers for bugs.
      8 Note: This will not format C code for you. Pipe the output through
      9 clang-format -style="{BasedOnStyle: llvm, IndentWidth: 4, ColumnLimit: 90}"
     10 or similar
     11 """
     12 
     13 import sys
     14 import os
     15 import argparse
     16 import textwrap
     17 from datetime import date
     18 
     19 __author__     = "Alexander Bulekov <alxndr@bu.edu>"
     20 __copyright__  = "Copyright (C) 2021, Red Hat, Inc."
     21 __license__    = "GPL version 2 or (at your option) any later version"
     22 
     23 __maintainer__ = "Alexander Bulekov"
     24 __email__      = "alxndr@bu.edu"
     25 
     26 
     27 def c_header(owner):
     28     return """/*
     29  * Autogenerated Fuzzer Test Case
     30  *
     31  * Copyright (c) {date} {owner}
     32  *
     33  * This work is licensed under the terms of the GNU GPL, version 2 or later.
     34  * See the COPYING file in the top-level directory.
     35  */
     36 
     37 #include "qemu/osdep.h"
     38 
     39 #include "libqtest.h"
     40 
     41     """.format(date=date.today().year, owner=owner)
     42 
     43 def c_comment(s):
     44     """ Return a multi-line C comment. Assume the text is already wrapped """
     45     return "/*\n * " + "\n * ".join(s.splitlines()) + "\n*/"
     46 
     47 def print_c_function(s):
     48     print("/* ")
     49     for l in s.splitlines():
     50         print(" * {}".format(l))
     51 
     52 def bash_reproducer(path, args, trace):
     53     result = '\\\n'.join(textwrap.wrap("cat << EOF | {} {}".format(path, args),
     54                                        72, break_on_hyphens=False,
     55                                        drop_whitespace=False))
     56     for l in trace.splitlines():
     57         result += "\n" + '\\\n'.join(textwrap.wrap(l,72,drop_whitespace=False))
     58     result += "\nEOF"
     59     return result
     60 
     61 def c_reproducer(name, args, trace):
     62     result = []
     63     result.append("""static void {}(void)\n{{""".format(name))
     64 
     65     # libqtest will add its own qtest args, so get rid of them
     66     args = args.replace("-accel qtest","")
     67     args = args.replace(",accel=qtest","")
     68     args = args.replace("-machine accel=qtest","")
     69     args = args.replace("-qtest stdio","")
     70     result.append("""QTestState *s = qtest_init("{}");""".format(args))
     71     for l in trace.splitlines():
     72         param = l.split()
     73         cmd = param[0]
     74         if cmd == "write":
     75             buf = param[3][2:] #Get the 0x... buffer and trim the "0x"
     76             assert len(buf)%2 == 0
     77             bufbytes = [buf[i:i+2] for i in range(0, len(buf), 2)]
     78             bufstring = '\\x'+'\\x'.join(bufbytes)
     79             addr = param[1]
     80             size = param[2]
     81             result.append("""qtest_bufwrite(s, {}, "{}", {});""".format(
     82                           addr, bufstring, size))
     83         elif cmd.startswith("in") or cmd.startswith("read"):
     84             result.append("qtest_{}(s, {});".format(
     85                           cmd, param[1]))
     86         elif cmd.startswith("out") or cmd.startswith("write"):
     87             result.append("qtest_{}(s, {}, {});".format(
     88                           cmd, param[1], param[2]))
     89         elif cmd == "clock_step":
     90             if len(param) ==1:
     91                 result.append("qtest_clock_step_next(s);")
     92             else:
     93                 result.append("qtest_clock_step(s, {});".format(param[1]))
     94     result.append("qtest_quit(s);\n}")
     95     return "\n".join(result)
     96 
     97 def c_main(name, arch):
     98     return """int main(int argc, char **argv)
     99 {{
    100     const char *arch = qtest_get_arch();
    101 
    102     g_test_init(&argc, &argv, NULL);
    103 
    104    if (strcmp(arch, "{arch}") == 0) {{
    105         qtest_add_func("fuzz/{name}",{name});
    106    }}
    107 
    108    return g_test_run();
    109 }}""".format(name=name, arch=arch)
    110 
    111 def main():
    112     parser = argparse.ArgumentParser()
    113     group = parser.add_mutually_exclusive_group()
    114     group.add_argument("-bash", help="Only output a copy-pastable bash command",
    115                         action="store_true")
    116     group.add_argument("-c", help="Only output a c function",
    117                         action="store_true")
    118     parser.add_argument('-owner', help="If generating complete C source code, \
    119                         this specifies the Copyright owner",
    120                         nargs='?', default="<name of author>")
    121     parser.add_argument("-no_comment", help="Don't include a bash reproducer \
    122                         as a comment in the C reproducers",
    123                         action="store_true")
    124     parser.add_argument('-name', help="The name of the c function",
    125                         nargs='?', default="test_fuzz")
    126     parser.add_argument('input_trace', help="input QTest command sequence \
    127                         (stdin by default)",
    128                         nargs='?', type=argparse.FileType('r'),
    129                         default=sys.stdin)
    130     args = parser.parse_args()
    131 
    132     qemu_path = os.getenv("QEMU_PATH")
    133     qemu_args = os.getenv("QEMU_ARGS")
    134     if not qemu_args or not qemu_path:
    135         print("Please set QEMU_PATH and QEMU_ARGS environment variables")
    136         sys.exit(1)
    137 
    138     bash_args = qemu_args
    139     if " -qtest stdio" not in  qemu_args:
    140         bash_args += " -qtest stdio"
    141 
    142     arch = qemu_path.split("-")[-1]
    143     trace = args.input_trace.read().strip()
    144 
    145     if args.bash :
    146         print(bash_reproducer(qemu_path, bash_args, trace))
    147     else:
    148         output = ""
    149         if not args.c:
    150             output += c_header(args.owner) + "\n"
    151         if not args.no_comment:
    152             output += c_comment(bash_reproducer(qemu_path, bash_args, trace))
    153         output += c_reproducer(args.name, qemu_args, trace)
    154         if not args.c:
    155             output += c_main(args.name, arch)
    156         print(output)
    157 
    158 
    159 if __name__ == '__main__':
    160     main()