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