197ef5f88SAlexander Bulekov#!/usr/bin/env python3
297ef5f88SAlexander Bulekov# -*- coding: utf-8 -*-
397ef5f88SAlexander Bulekov
497ef5f88SAlexander Bulekov"""
597ef5f88SAlexander BulekovConvert plain qtest traces to C or Bash reproducers
697ef5f88SAlexander Bulekov
797ef5f88SAlexander BulekovUse this to help build bug-reports or create in-tree reproducers for bugs.
897ef5f88SAlexander BulekovNote: This will not format C code for you. Pipe the output through
997ef5f88SAlexander Bulekovclang-format -style="{BasedOnStyle: llvm, IndentWidth: 4, ColumnLimit: 90}"
1097ef5f88SAlexander Bulekovor similar
1197ef5f88SAlexander Bulekov"""
1297ef5f88SAlexander Bulekov
1397ef5f88SAlexander Bulekovimport sys
1497ef5f88SAlexander Bulekovimport os
1597ef5f88SAlexander Bulekovimport argparse
1697ef5f88SAlexander Bulekovimport textwrap
1797ef5f88SAlexander Bulekovfrom datetime import date
1897ef5f88SAlexander Bulekov
1997ef5f88SAlexander Bulekov__author__     = "Alexander Bulekov <alxndr@bu.edu>"
2097ef5f88SAlexander Bulekov__copyright__  = "Copyright (C) 2021, Red Hat, Inc."
2197ef5f88SAlexander Bulekov__license__    = "GPL version 2 or (at your option) any later version"
2297ef5f88SAlexander Bulekov
2397ef5f88SAlexander Bulekov__maintainer__ = "Alexander Bulekov"
2497ef5f88SAlexander Bulekov__email__      = "alxndr@bu.edu"
2597ef5f88SAlexander Bulekov
2697ef5f88SAlexander Bulekov
2797ef5f88SAlexander Bulekovdef c_header(owner):
2897ef5f88SAlexander Bulekov    return """/*
2997ef5f88SAlexander Bulekov * Autogenerated Fuzzer Test Case
3097ef5f88SAlexander Bulekov *
3197ef5f88SAlexander Bulekov * Copyright (c) {date} {owner}
3297ef5f88SAlexander Bulekov *
3397ef5f88SAlexander Bulekov * This work is licensed under the terms of the GNU GPL, version 2 or later.
3497ef5f88SAlexander Bulekov * See the COPYING file in the top-level directory.
3597ef5f88SAlexander Bulekov */
3697ef5f88SAlexander Bulekov
3797ef5f88SAlexander Bulekov#include "qemu/osdep.h"
3897ef5f88SAlexander Bulekov
39*907b5105SMarc-André Lureau#include "libqtest.h"
4097ef5f88SAlexander Bulekov
4197ef5f88SAlexander Bulekov    """.format(date=date.today().year, owner=owner)
4297ef5f88SAlexander Bulekov
4397ef5f88SAlexander Bulekovdef c_comment(s):
4497ef5f88SAlexander Bulekov    """ Return a multi-line C comment. Assume the text is already wrapped """
4597ef5f88SAlexander Bulekov    return "/*\n * " + "\n * ".join(s.splitlines()) + "\n*/"
4697ef5f88SAlexander Bulekov
4797ef5f88SAlexander Bulekovdef print_c_function(s):
4897ef5f88SAlexander Bulekov    print("/* ")
4997ef5f88SAlexander Bulekov    for l in s.splitlines():
5097ef5f88SAlexander Bulekov        print(" * {}".format(l))
5197ef5f88SAlexander Bulekov
5297ef5f88SAlexander Bulekovdef bash_reproducer(path, args, trace):
5397ef5f88SAlexander Bulekov    result = '\\\n'.join(textwrap.wrap("cat << EOF | {} {}".format(path, args),
5497ef5f88SAlexander Bulekov                                       72, break_on_hyphens=False,
5597ef5f88SAlexander Bulekov                                       drop_whitespace=False))
5697ef5f88SAlexander Bulekov    for l in trace.splitlines():
5797ef5f88SAlexander Bulekov        result += "\n" + '\\\n'.join(textwrap.wrap(l,72,drop_whitespace=False))
5897ef5f88SAlexander Bulekov    result += "\nEOF"
5997ef5f88SAlexander Bulekov    return result
6097ef5f88SAlexander Bulekov
6197ef5f88SAlexander Bulekovdef c_reproducer(name, args, trace):
6297ef5f88SAlexander Bulekov    result = []
6397ef5f88SAlexander Bulekov    result.append("""static void {}(void)\n{{""".format(name))
6497ef5f88SAlexander Bulekov
6597ef5f88SAlexander Bulekov    # libqtest will add its own qtest args, so get rid of them
6697ef5f88SAlexander Bulekov    args = args.replace("-accel qtest","")
6797ef5f88SAlexander Bulekov    args = args.replace(",accel=qtest","")
6897ef5f88SAlexander Bulekov    args = args.replace("-machine accel=qtest","")
6997ef5f88SAlexander Bulekov    args = args.replace("-qtest stdio","")
7097ef5f88SAlexander Bulekov    result.append("""QTestState *s = qtest_init("{}");""".format(args))
7197ef5f88SAlexander Bulekov    for l in trace.splitlines():
7297ef5f88SAlexander Bulekov        param = l.split()
7397ef5f88SAlexander Bulekov        cmd = param[0]
7497ef5f88SAlexander Bulekov        if cmd == "write":
7597ef5f88SAlexander Bulekov            buf = param[3][2:] #Get the 0x... buffer and trim the "0x"
7697ef5f88SAlexander Bulekov            assert len(buf)%2 == 0
7797ef5f88SAlexander Bulekov            bufbytes = [buf[i:i+2] for i in range(0, len(buf), 2)]
7897ef5f88SAlexander Bulekov            bufstring = '\\x'+'\\x'.join(bufbytes)
7997ef5f88SAlexander Bulekov            addr = param[1]
8097ef5f88SAlexander Bulekov            size = param[2]
8197ef5f88SAlexander Bulekov            result.append("""qtest_bufwrite(s, {}, "{}", {});""".format(
8297ef5f88SAlexander Bulekov                          addr, bufstring, size))
8397ef5f88SAlexander Bulekov        elif cmd.startswith("in") or cmd.startswith("read"):
8497ef5f88SAlexander Bulekov            result.append("qtest_{}(s, {});".format(
8597ef5f88SAlexander Bulekov                          cmd, param[1]))
8697ef5f88SAlexander Bulekov        elif cmd.startswith("out") or cmd.startswith("write"):
8797ef5f88SAlexander Bulekov            result.append("qtest_{}(s, {}, {});".format(
8897ef5f88SAlexander Bulekov                          cmd, param[1], param[2]))
8997ef5f88SAlexander Bulekov        elif cmd == "clock_step":
9097ef5f88SAlexander Bulekov            if len(param) ==1:
9197ef5f88SAlexander Bulekov                result.append("qtest_clock_step_next(s);")
9297ef5f88SAlexander Bulekov            else:
9397ef5f88SAlexander Bulekov                result.append("qtest_clock_step(s, {});".format(param[1]))
9497ef5f88SAlexander Bulekov    result.append("qtest_quit(s);\n}")
9597ef5f88SAlexander Bulekov    return "\n".join(result)
9697ef5f88SAlexander Bulekov
9797ef5f88SAlexander Bulekovdef c_main(name, arch):
9897ef5f88SAlexander Bulekov    return """int main(int argc, char **argv)
9997ef5f88SAlexander Bulekov{{
10097ef5f88SAlexander Bulekov    const char *arch = qtest_get_arch();
10197ef5f88SAlexander Bulekov
10297ef5f88SAlexander Bulekov    g_test_init(&argc, &argv, NULL);
10397ef5f88SAlexander Bulekov
10497ef5f88SAlexander Bulekov   if (strcmp(arch, "{arch}") == 0) {{
10597ef5f88SAlexander Bulekov        qtest_add_func("fuzz/{name}",{name});
10697ef5f88SAlexander Bulekov   }}
10797ef5f88SAlexander Bulekov
10897ef5f88SAlexander Bulekov   return g_test_run();
10997ef5f88SAlexander Bulekov}}""".format(name=name, arch=arch)
11097ef5f88SAlexander Bulekov
11197ef5f88SAlexander Bulekovdef main():
11297ef5f88SAlexander Bulekov    parser = argparse.ArgumentParser()
11397ef5f88SAlexander Bulekov    group = parser.add_mutually_exclusive_group()
11497ef5f88SAlexander Bulekov    group.add_argument("-bash", help="Only output a copy-pastable bash command",
11597ef5f88SAlexander Bulekov                        action="store_true")
11697ef5f88SAlexander Bulekov    group.add_argument("-c", help="Only output a c function",
11797ef5f88SAlexander Bulekov                        action="store_true")
11897ef5f88SAlexander Bulekov    parser.add_argument('-owner', help="If generating complete C source code, \
11997ef5f88SAlexander Bulekov                        this specifies the Copyright owner",
12097ef5f88SAlexander Bulekov                        nargs='?', default="<name of author>")
12197ef5f88SAlexander Bulekov    parser.add_argument("-no_comment", help="Don't include a bash reproducer \
12297ef5f88SAlexander Bulekov                        as a comment in the C reproducers",
12397ef5f88SAlexander Bulekov                        action="store_true")
12497ef5f88SAlexander Bulekov    parser.add_argument('-name', help="The name of the c function",
12597ef5f88SAlexander Bulekov                        nargs='?', default="test_fuzz")
12697ef5f88SAlexander Bulekov    parser.add_argument('input_trace', help="input QTest command sequence \
12797ef5f88SAlexander Bulekov                        (stdin by default)",
12897ef5f88SAlexander Bulekov                        nargs='?', type=argparse.FileType('r'),
12997ef5f88SAlexander Bulekov                        default=sys.stdin)
13097ef5f88SAlexander Bulekov    args = parser.parse_args()
13197ef5f88SAlexander Bulekov
13297ef5f88SAlexander Bulekov    qemu_path = os.getenv("QEMU_PATH")
13397ef5f88SAlexander Bulekov    qemu_args = os.getenv("QEMU_ARGS")
13497ef5f88SAlexander Bulekov    if not qemu_args or not qemu_path:
13597ef5f88SAlexander Bulekov        print("Please set QEMU_PATH and QEMU_ARGS environment variables")
13697ef5f88SAlexander Bulekov        sys.exit(1)
13797ef5f88SAlexander Bulekov
13897ef5f88SAlexander Bulekov    bash_args = qemu_args
13997ef5f88SAlexander Bulekov    if " -qtest stdio" not in  qemu_args:
14097ef5f88SAlexander Bulekov        bash_args += " -qtest stdio"
14197ef5f88SAlexander Bulekov
14297ef5f88SAlexander Bulekov    arch = qemu_path.split("-")[-1]
14397ef5f88SAlexander Bulekov    trace = args.input_trace.read().strip()
14497ef5f88SAlexander Bulekov
14597ef5f88SAlexander Bulekov    if args.bash :
14697ef5f88SAlexander Bulekov        print(bash_reproducer(qemu_path, bash_args, trace))
14797ef5f88SAlexander Bulekov    else:
14897ef5f88SAlexander Bulekov        output = ""
14997ef5f88SAlexander Bulekov        if not args.c:
15097ef5f88SAlexander Bulekov            output += c_header(args.owner) + "\n"
15197ef5f88SAlexander Bulekov        if not args.no_comment:
15297ef5f88SAlexander Bulekov            output += c_comment(bash_reproducer(qemu_path, bash_args, trace))
15397ef5f88SAlexander Bulekov        output += c_reproducer(args.name, qemu_args, trace)
15497ef5f88SAlexander Bulekov        if not args.c:
15597ef5f88SAlexander Bulekov            output += c_main(args.name, arch)
15697ef5f88SAlexander Bulekov        print(output)
15797ef5f88SAlexander Bulekov
15897ef5f88SAlexander Bulekov
15997ef5f88SAlexander Bulekovif __name__ == '__main__':
16097ef5f88SAlexander Bulekov    main()
161