1#!/usr/bin/python 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 21from __future__ import print_function 22 23import argparse 24import copy 25import os.path 26import re 27import subprocess 28import sys 29 30 31def probe_prefix(binary): 32 dirname, filename = os.path.split(binary) 33 return re.sub("-", ".", filename) + ".log" 34 35 36def which(binary): 37 for path in os.environ["PATH"].split(os.pathsep): 38 if os.path.exists(os.path.join(path, binary)): 39 return os.path.join(path, binary) 40 41 print("Unable to find '%s' in $PATH" % binary) 42 sys.exit(1) 43 44 45def tapset_dir(binary): 46 dirname, filename = os.path.split(binary) 47 if dirname == '': 48 thisfile = which(binary) 49 else: 50 thisfile = os.path.realpath(binary) 51 if not os.path.exists(thisfile): 52 print("Unable to find '%s'" % thisfile) 53 sys.exit(1) 54 55 basedir = os.path.split(thisfile)[0] 56 tapset = os.path.join(basedir, "..", "share", "systemtap", "tapset") 57 return os.path.realpath(tapset) 58 59 60def tapset_env(tapset_dir): 61 tenv = copy.copy(os.environ) 62 tenv["SYSTEMTAP_TAPSET"] = tapset_dir 63 return tenv 64 65def cmd_run(args): 66 prefix = probe_prefix(args.binary) 67 tapsets = tapset_dir(args.binary) 68 69 if args.verbose: 70 print("Using tapset dir '%s' for binary '%s'" % (tapsets, args.binary)) 71 72 probes = [] 73 for probe in args.probes: 74 probes.append("probe %s.%s {}" % (prefix, probe)) 75 if len(probes) == 0: 76 print("At least one probe pattern must be specified") 77 sys.exit(1) 78 79 script = " ".join(probes) 80 if args.verbose: 81 print("Compiling script '%s'" % script) 82 script = """probe begin { print("Running script, <Ctrl>-c to quit\\n") } """ + script 83 84 # We request an 8MB buffer, since the stap default 1MB buffer 85 # can be easily overflowed by frequently firing QEMU traces 86 stapargs = ["stap", "-s", "8"] 87 if args.pid is not None: 88 stapargs.extend(["-x", args.pid]) 89 stapargs.extend(["-e", script]) 90 subprocess.call(stapargs, env=tapset_env(tapsets)) 91 92 93def cmd_list(args): 94 tapsets = tapset_dir(args.binary) 95 96 if args.verbose: 97 print("Using tapset dir '%s' for binary '%s'" % (tapsets, args.binary)) 98 99 def print_probes(verbose, name): 100 prefix = probe_prefix(args.binary) 101 offset = len(prefix) + 1 102 script = prefix + "." + name 103 104 if verbose: 105 print("Listing probes with name '%s'" % script) 106 proc = subprocess.Popen(["stap", "-l", script], 107 stdout=subprocess.PIPE, env=tapset_env(tapsets)) 108 out, err = proc.communicate() 109 if proc.returncode != 0: 110 print("No probes found, are the tapsets installed in %s" % tapset_dir(args.binary)) 111 sys.exit(1) 112 113 for line in out.splitlines(): 114 if line.startswith(prefix): 115 print("%s" % line[offset:]) 116 117 if len(args.probes) == 0: 118 print_probes(args.verbose, "*") 119 else: 120 for probe in args.probes: 121 print_probes(args.verbose, probe) 122 123 124def main(): 125 parser = argparse.ArgumentParser(description="QEMU SystemTap trace tool") 126 parser.add_argument("-v", "--verbose", help="Print verbose progress info", 127 action='store_true') 128 129 subparser = parser.add_subparsers(help="commands") 130 subparser.required = True 131 subparser.dest = "command" 132 133 runparser = subparser.add_parser("run", help="Run a trace session", 134 formatter_class=argparse.RawDescriptionHelpFormatter, 135 epilog=""" 136 137To watch all trace points on the qemu-system-x86_64 binary: 138 139 %(argv0)s run qemu-system-x86_64 140 141To only watch the trace points matching the qio* and qcrypto* patterns 142 143 %(argv0)s run qemu-system-x86_64 'qio*' 'qcrypto*' 144""" % {"argv0": sys.argv[0]}) 145 runparser.set_defaults(func=cmd_run) 146 runparser.add_argument("--pid", "-p", dest="pid", 147 help="Restrict tracing to a specific process ID") 148 runparser.add_argument("binary", help="QEMU system or user emulator binary") 149 runparser.add_argument("probes", help="Probe names or wildcards", 150 nargs=argparse.REMAINDER) 151 152 listparser = subparser.add_parser("list", help="List probe points", 153 formatter_class=argparse.RawDescriptionHelpFormatter, 154 epilog=""" 155 156To list all trace points on the qemu-system-x86_64 binary: 157 158 %(argv0)s list qemu-system-x86_64 159 160To only list the trace points matching the qio* and qcrypto* patterns 161 162 %(argv0)s list qemu-system-x86_64 'qio*' 'qcrypto*' 163""" % {"argv0": sys.argv[0]}) 164 listparser.set_defaults(func=cmd_list) 165 listparser.add_argument("binary", help="QEMU system or user emulator binary") 166 listparser.add_argument("probes", help="Probe names or wildcards", 167 nargs=argparse.REMAINDER) 168 169 args = parser.parse_args() 170 171 args.func(args) 172 sys.exit(0) 173 174if __name__ == '__main__': 175 main() 176