2
0

output_reproducer.py 5.4 KB

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