|  | #!/usr/bin/env python3 | 
|  | # -*- coding: utf-8 -*- | 
|  |  | 
|  | """ | 
|  | Convert plain qtest traces to C or Bash reproducers | 
|  |  | 
|  | Use this to help build bug-reports or create in-tree reproducers for bugs. | 
|  | Note: This will not format C code for you. Pipe the output through | 
|  | clang-format -style="{BasedOnStyle: llvm, IndentWidth: 4, ColumnLimit: 90}" | 
|  | or similar | 
|  | """ | 
|  |  | 
|  | import sys | 
|  | import os | 
|  | import argparse | 
|  | import textwrap | 
|  | from datetime import date | 
|  |  | 
|  | __author__     = "Alexander Bulekov <alxndr@bu.edu>" | 
|  | __copyright__  = "Copyright (C) 2021, Red Hat, Inc." | 
|  | __license__    = "GPL version 2 or (at your option) any later version" | 
|  |  | 
|  | __maintainer__ = "Alexander Bulekov" | 
|  | __email__      = "alxndr@bu.edu" | 
|  |  | 
|  |  | 
|  | def c_header(owner): | 
|  | return """/* | 
|  | * Autogenerated Fuzzer Test Case | 
|  | * | 
|  | * Copyright (c) {date} {owner} | 
|  | * | 
|  | * This work is licensed under the terms of the GNU GPL, version 2 or later. | 
|  | * See the COPYING file in the top-level directory. | 
|  | */ | 
|  |  | 
|  | #include "qemu/osdep.h" | 
|  |  | 
|  | #include "libqtest.h" | 
|  |  | 
|  | """.format(date=date.today().year, owner=owner) | 
|  |  | 
|  | def c_comment(s): | 
|  | """ Return a multi-line C comment. Assume the text is already wrapped """ | 
|  | return "/*\n * " + "\n * ".join(s.splitlines()) + "\n*/" | 
|  |  | 
|  | def print_c_function(s): | 
|  | print("/* ") | 
|  | for l in s.splitlines(): | 
|  | print(" * {}".format(l)) | 
|  |  | 
|  | def bash_reproducer(path, args, trace): | 
|  | result = '\\\n'.join(textwrap.wrap("cat << EOF | {} {}".format(path, args), | 
|  | 72, break_on_hyphens=False, | 
|  | drop_whitespace=False)) | 
|  | for l in trace.splitlines(): | 
|  | result += "\n" + '\\\n'.join(textwrap.wrap(l,72,drop_whitespace=False)) | 
|  | result += "\nEOF" | 
|  | return result | 
|  |  | 
|  | def c_reproducer(name, args, trace): | 
|  | result = [] | 
|  | result.append("""static void {}(void)\n{{""".format(name)) | 
|  |  | 
|  | # libqtest will add its own qtest args, so get rid of them | 
|  | args = args.replace("-accel qtest","") | 
|  | args = args.replace(",accel=qtest","") | 
|  | args = args.replace("-machine accel=qtest","") | 
|  | args = args.replace("-qtest stdio","") | 
|  | result.append("""QTestState *s = qtest_init("{}");""".format(args)) | 
|  | for l in trace.splitlines(): | 
|  | param = l.split() | 
|  | cmd = param[0] | 
|  | if cmd == "write": | 
|  | buf = param[3][2:] #Get the 0x... buffer and trim the "0x" | 
|  | assert len(buf)%2 == 0 | 
|  | bufbytes = [buf[i:i+2] for i in range(0, len(buf), 2)] | 
|  | bufstring = '\\x'+'\\x'.join(bufbytes) | 
|  | addr = param[1] | 
|  | size = param[2] | 
|  | result.append("""qtest_bufwrite(s, {}, "{}", {});""".format( | 
|  | addr, bufstring, size)) | 
|  | elif cmd.startswith("in") or cmd.startswith("read"): | 
|  | result.append("qtest_{}(s, {});".format( | 
|  | cmd, param[1])) | 
|  | elif cmd.startswith("out") or cmd.startswith("write"): | 
|  | result.append("qtest_{}(s, {}, {});".format( | 
|  | cmd, param[1], param[2])) | 
|  | elif cmd == "clock_step": | 
|  | if len(param) ==1: | 
|  | result.append("qtest_clock_step_next(s);") | 
|  | else: | 
|  | result.append("qtest_clock_step(s, {});".format(param[1])) | 
|  | result.append("qtest_quit(s);\n}") | 
|  | return "\n".join(result) | 
|  |  | 
|  | def c_main(name, arch): | 
|  | return """int main(int argc, char **argv) | 
|  | {{ | 
|  | const char *arch = qtest_get_arch(); | 
|  |  | 
|  | g_test_init(&argc, &argv, NULL); | 
|  |  | 
|  | if (strcmp(arch, "{arch}") == 0) {{ | 
|  | qtest_add_func("fuzz/{name}",{name}); | 
|  | }} | 
|  |  | 
|  | return g_test_run(); | 
|  | }}""".format(name=name, arch=arch) | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser() | 
|  | group = parser.add_mutually_exclusive_group() | 
|  | group.add_argument("-bash", help="Only output a copy-pastable bash command", | 
|  | action="store_true") | 
|  | group.add_argument("-c", help="Only output a c function", | 
|  | action="store_true") | 
|  | parser.add_argument('-owner', help="If generating complete C source code, \ | 
|  | this specifies the Copyright owner", | 
|  | nargs='?', default="<name of author>") | 
|  | parser.add_argument("-no_comment", help="Don't include a bash reproducer \ | 
|  | as a comment in the C reproducers", | 
|  | action="store_true") | 
|  | parser.add_argument('-name', help="The name of the c function", | 
|  | nargs='?', default="test_fuzz") | 
|  | parser.add_argument('input_trace', help="input QTest command sequence \ | 
|  | (stdin by default)", | 
|  | nargs='?', type=argparse.FileType('r'), | 
|  | default=sys.stdin) | 
|  | args = parser.parse_args() | 
|  |  | 
|  | qemu_path = os.getenv("QEMU_PATH") | 
|  | qemu_args = os.getenv("QEMU_ARGS") | 
|  | if not qemu_args or not qemu_path: | 
|  | print("Please set QEMU_PATH and QEMU_ARGS environment variables") | 
|  | sys.exit(1) | 
|  |  | 
|  | bash_args = qemu_args | 
|  | if " -qtest stdio" not in  qemu_args: | 
|  | bash_args += " -qtest stdio" | 
|  |  | 
|  | arch = qemu_path.split("-")[-1] | 
|  | trace = args.input_trace.read().strip() | 
|  |  | 
|  | if args.bash : | 
|  | print(bash_reproducer(qemu_path, bash_args, trace)) | 
|  | else: | 
|  | output = "" | 
|  | if not args.c: | 
|  | output += c_header(args.owner) + "\n" | 
|  | if not args.no_comment: | 
|  | output += c_comment(bash_reproducer(qemu_path, bash_args, trace)) | 
|  | output += c_reproducer(args.name, qemu_args, trace) | 
|  | if not args.c: | 
|  | output += c_main(args.name, arch) | 
|  | print(output) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | main() |