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