1#!/usr/bin/env python3 2# -*- python -*- 3# 4# Copyright (C) 2019 Red Hat, Inc 5# 6# QEMU SystemTap Trace Tool 7# 8# This program is free software; you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation; either version 2 of the License, or 11# (at your option) any later version. 12# 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with this program; if not, see <http://www.gnu.org/licenses/>. 20 21import argparse 22import copy 23import os.path 24import re 25import subprocess 26import sys 27 28 29def probe_prefix(binary): 30 dirname, filename = os.path.split(binary) 31 return re.sub("-", ".", filename) + ".log" 32 33 34def which(binary): 35 for path in os.environ["PATH"].split(os.pathsep): 36 if os.path.exists(os.path.join(path, binary)): 37 return os.path.join(path, binary) 38 39 print("Unable to find '%s' in $PATH" % binary) 40 sys.exit(1) 41 42 43def tapset_dir(binary): 44 dirname, filename = os.path.split(binary) 45 if dirname == '': 46 thisfile = which(binary) 47 else: 48 thisfile = os.path.realpath(binary) 49 if not os.path.exists(thisfile): 50 print("Unable to find '%s'" % thisfile) 51 sys.exit(1) 52 53 basedir = os.path.split(thisfile)[0] 54 tapset = os.path.join(basedir, "..", "share", "systemtap", "tapset") 55 return os.path.realpath(tapset) 56 57 58def cmd_run(args): 59 stap = which("stap") 60 prefix = probe_prefix(args.binary) 61 tapsets = tapset_dir(args.binary) 62 63 if args.verbose: 64 print("Using tapset dir '%s' for binary '%s'" % (tapsets, args.binary)) 65 66 probes = [] 67 for probe in args.probes: 68 probes.append("probe %s.%s {}" % (prefix, probe)) 69 if len(probes) == 0: 70 print("At least one probe pattern must be specified") 71 sys.exit(1) 72 73 script = " ".join(probes) 74 if args.verbose: 75 print("Compiling script '%s'" % script) 76 script = """probe begin { print("Running script, <Ctrl>-c to quit\\n") } """ + script 77 78 # We request an 8MB buffer, since the stap default 1MB buffer 79 # can be easily overflowed by frequently firing QEMU traces 80 stapargs = [stap, "-s", "8", "-I", tapsets ] 81 if args.pid is not None: 82 stapargs.extend(["-x", args.pid]) 83 stapargs.extend(["-e", script]) 84 subprocess.call(stapargs) 85 86 87def cmd_list(args): 88 stap = which("stap") 89 tapsets = tapset_dir(args.binary) 90 91 if args.verbose: 92 print("Using tapset dir '%s' for binary '%s'" % (tapsets, args.binary)) 93 94 def print_probes(verbose, name): 95 prefix = probe_prefix(args.binary) 96 offset = len(prefix) + 1 97 script = prefix + "." + name 98 99 if verbose: 100 print("Listing probes with name '%s'" % script) 101 proc = subprocess.Popen([stap, "-I", tapsets, "-l", script], 102 stdout=subprocess.PIPE, 103 universal_newlines=True) 104 out, err = proc.communicate() 105 if proc.returncode != 0: 106 print("No probes found, are the tapsets installed in %s" % tapset_dir(args.binary)) 107 sys.exit(1) 108 109 for line in out.splitlines(): 110 if line.startswith(prefix): 111 print("%s" % line[offset:]) 112 113 if len(args.probes) == 0: 114 print_probes(args.verbose, "*") 115 else: 116 for probe in args.probes: 117 print_probes(args.verbose, probe) 118 119 120def main(): 121 parser = argparse.ArgumentParser(description="QEMU SystemTap trace tool") 122 parser.add_argument("-v", "--verbose", help="Print verbose progress info", 123 action='store_true') 124 125 subparser = parser.add_subparsers(help="commands") 126 subparser.required = True 127 subparser.dest = "command" 128 129 runparser = subparser.add_parser("run", help="Run a trace session", 130 formatter_class=argparse.RawDescriptionHelpFormatter, 131 epilog=""" 132 133To watch all trace points on the qemu-system-x86_64 binary: 134 135 %(argv0)s run qemu-system-x86_64 136 137To only watch the trace points matching the qio* and qcrypto* patterns 138 139 %(argv0)s run qemu-system-x86_64 'qio*' 'qcrypto*' 140""" % {"argv0": sys.argv[0]}) 141 runparser.set_defaults(func=cmd_run) 142 runparser.add_argument("--pid", "-p", dest="pid", 143 help="Restrict tracing to a specific process ID") 144 runparser.add_argument("binary", help="QEMU system or user emulator binary") 145 runparser.add_argument("probes", help="Probe names or wildcards", 146 nargs=argparse.REMAINDER) 147 148 listparser = subparser.add_parser("list", help="List probe points", 149 formatter_class=argparse.RawDescriptionHelpFormatter, 150 epilog=""" 151 152To list all trace points on the qemu-system-x86_64 binary: 153 154 %(argv0)s list qemu-system-x86_64 155 156To only list the trace points matching the qio* and qcrypto* patterns 157 158 %(argv0)s list qemu-system-x86_64 'qio*' 'qcrypto*' 159""" % {"argv0": sys.argv[0]}) 160 listparser.set_defaults(func=cmd_list) 161 listparser.add_argument("binary", help="QEMU system or user emulator binary") 162 listparser.add_argument("probes", help="Probe names or wildcards", 163 nargs=argparse.REMAINDER) 164 165 args = parser.parse_args() 166 167 args.func(args) 168 sys.exit(0) 169 170if __name__ == '__main__': 171 main() 172