Since commit a2ce7dbd917 ("meson: convert tests/qtest to meson"),
libqtest.h is under libqos/ directory, while libqtest.c is still in
qtest/. Move back to its original location to avoid mixing with libqos/.
Suggested-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
		
	
			
		
			
				
	
	
		
			161 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			161 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/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()
 |