1#!/usr/bin/env python3 2# 3# Run a gdbstub test case 4# 5# Copyright (c) 2019 Linaro 6# 7# Author: Alex Bennée <alex.bennee@linaro.org> 8# 9# This work is licensed under the terms of the GNU GPL, version 2 or later. 10# See the COPYING file in the top-level directory. 11# 12# SPDX-License-Identifier: GPL-2.0-or-later 13 14import argparse 15import subprocess 16import shutil 17import shlex 18import os 19from time import sleep 20from tempfile import TemporaryDirectory 21 22def get_args(): 23 parser = argparse.ArgumentParser(description="A gdbstub test runner") 24 parser.add_argument("--qemu", help="Qemu binary for test", 25 required=True) 26 parser.add_argument("--qargs", help="Qemu arguments for test") 27 parser.add_argument("--binary", help="Binary to debug", 28 required=True) 29 parser.add_argument("--test", help="GDB test script") 30 parser.add_argument('test_args', nargs='*', 31 help="Additional args for GDB test script. " 32 "The args should be preceded by -- to avoid confusion " 33 "with flags for runner script") 34 parser.add_argument("--gdb", help="The gdb binary to use", 35 default=None) 36 parser.add_argument("--gdb-args", help="Additional gdb arguments") 37 parser.add_argument("--output", help="A file to redirect output to") 38 parser.add_argument("--stderr", help="A file to redirect stderr to") 39 parser.add_argument("--no-suspend", action="store_true", 40 help="Ask the binary to not wait for GDB connection") 41 42 return parser.parse_args() 43 44 45def log(output, msg): 46 if output: 47 output.write(msg + "\n") 48 output.flush() 49 else: 50 print(msg) 51 52 53if __name__ == '__main__': 54 args = get_args() 55 56 # Search for a gdb we can use 57 if not args.gdb: 58 args.gdb = shutil.which("gdb-multiarch") 59 if not args.gdb: 60 args.gdb = shutil.which("gdb") 61 if not args.gdb: 62 print("We need gdb to run the test") 63 exit(-1) 64 if args.output: 65 output = open(args.output, "w") 66 else: 67 output = None 68 if args.stderr: 69 stderr = open(args.stderr, "w") 70 else: 71 stderr = None 72 73 socket_dir = TemporaryDirectory("qemu-gdbstub") 74 socket_name = os.path.join(socket_dir.name, "gdbstub.socket") 75 76 # Launch QEMU with binary 77 if "system" in args.qemu: 78 if args.no_suspend: 79 suspend = '' 80 else: 81 suspend = ' -S' 82 cmd = f'{args.qemu} {args.qargs} {args.binary}' \ 83 f'{suspend} -gdb unix:path={socket_name},server=on' 84 else: 85 if args.no_suspend: 86 suspend = ',suspend=n' 87 else: 88 suspend = '' 89 cmd = f'{args.qemu} {args.qargs} -g {socket_name}{suspend}' \ 90 f' {args.binary}' 91 92 log(output, "QEMU CMD: %s" % (cmd)) 93 inferior = subprocess.Popen(shlex.split(cmd)) 94 95 # Now launch gdb with our test and collect the result 96 gdb_cmd = "%s %s" % (args.gdb, args.binary) 97 if args.gdb_args: 98 gdb_cmd += " %s" % (args.gdb_args) 99 # run quietly and ignore .gdbinit 100 gdb_cmd += " -q -n -batch" 101 # disable pagination 102 gdb_cmd += " -ex 'set pagination off'" 103 # disable prompts in case of crash 104 gdb_cmd += " -ex 'set confirm off'" 105 # connect to remote 106 gdb_cmd += " -ex 'target remote %s'" % (socket_name) 107 # finally the test script itself 108 if args.test: 109 if args.test_args: 110 gdb_cmd += f" -ex \"py sys.argv={args.test_args}\"" 111 gdb_cmd += " -x %s" % (args.test) 112 113 114 sleep(1) 115 log(output, "GDB CMD: %s" % (gdb_cmd)) 116 117 gdb_env = dict(os.environ) 118 gdb_pythonpath = gdb_env.get("PYTHONPATH", "").split(os.pathsep) 119 gdb_pythonpath.append(os.path.dirname(os.path.realpath(__file__))) 120 gdb_env["PYTHONPATH"] = os.pathsep.join(gdb_pythonpath) 121 result = subprocess.call(gdb_cmd, shell=True, stdout=output, stderr=stderr, 122 env=gdb_env) 123 124 # A result of greater than 128 indicates a fatal signal (likely a 125 # crash due to gdb internal failure). That's a problem for GDB and 126 # not the test so we force a return of 0 so we don't fail the test on 127 # account of broken external tools. 128 if result > 128: 129 log(output, "GDB crashed? (%d, %d) SKIPPING" % (result, result - 128)) 130 exit(0) 131 132 try: 133 inferior.wait(2) 134 except subprocess.TimeoutExpired: 135 log(output, "GDB never connected? Killed guest") 136 inferior.kill() 137 138 exit(result) 139