summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xscripts/crosstap586
1 files changed, 450 insertions, 136 deletions
diff --git a/scripts/crosstap b/scripts/crosstap
index 39739bba3a..e33fa4ad46 100755
--- a/scripts/crosstap
+++ b/scripts/crosstap
@@ -1,15 +1,22 @@
-#!/bin/bash
+#!/usr/bin/env python3
#
-# Run a systemtap script on remote target
+# Build a systemtap script for a given image, kernel
#
-# Examples (run on build host, target is 192.168.1.xxx):
-# $ source oe-init-build-env"
-# $ cd ~/my/systemtap/scripts"
+# Effectively script extracts needed information from set of
+# 'bitbake -e' commands and contructs proper invocation of stap on
+# host to build systemtap script for a given target.
#
-# $ crosstap root@192.168.1.xxx myscript.stp"
-# $ crosstap root@192.168.1.xxx myscript-with-args.stp 99 ninetynine"
+# By default script will compile scriptname.ko that could be copied
+# to taget and activated with 'staprun scriptname.ko' command. Or if
+# --remote user@hostname option is specified script will build, load
+# execute script on target.
#
-# Copyright (c) 2012, Intel Corporation.
+# This script is very similar and inspired by crosstap shell script.
+# The major difference that this script supports user-land related
+# systemtap script, whereas crosstap could deal only with scripts
+# related to kernel.
+#
+# Copyright (c) 2018, Cisco Systems.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
@@ -25,131 +32,438 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-function usage() {
- echo "Usage: $0 <user@hostname> <sytemtap-script> [additional systemtap-script args]"
-}
-
-function setup_usage() {
- echo ""
- echo "'crosstap' requires a local sdk build of the target system"
- echo "(or a build that includes 'tools-profile') in order to build"
- echo "kernel modules that can probe the target system."
- echo ""
- echo "Practically speaking, that means you need to do the following:"
- echo " - If you're running a pre-built image, download the release"
- echo " and/or BSP tarballs used to build the image."
- echo " - If you're working from git sources, just clone the metadata"
- echo " and BSP layers needed to build the image you'll be booting."
- echo " - Make sure you're properly set up to build a new image (see"
- echo " the BSP README and/or the widely available basic documentation"
- echo " that discusses how to build images)."
- echo " - Build an -sdk version of the image e.g.:"
- echo " $ bitbake core-image-sato-sdk"
- echo " OR"
- echo " - Build a non-sdk image but include the profiling tools:"
- echo " [ edit local.conf and add 'tools-profile' to the end of"
- echo " the EXTRA_IMAGE_FEATURES variable ]"
- echo " $ bitbake core-image-sato"
- echo ""
- echo " [ NOTE that 'crosstap' needs to be able to ssh into the target"
- echo " system, which isn't enabled by default in -minimal images. ]"
- echo ""
- echo "Once you've build the image on the host system, you're ready to"
- echo "boot it (or the equivalent pre-built image) and use 'crosstap'"
- echo "to probe it (you need to source the environment as usual first):"
- echo ""
- echo " $ source oe-init-build-env"
- echo " $ cd ~/my/systemtap/scripts"
- echo " $ crosstap root@192.168.1.xxx myscript.stp"
- echo ""
-}
-
-function systemtap_target_arch() {
- SYSTEMTAP_TARGET_ARCH=$1
- case $SYSTEMTAP_TARGET_ARCH in
- i?86)
- SYSTEMTAP_TARGET_ARCH="i386"
- ;;
- x86?64*)
- SYSTEMTAP_TARGET_ARCH="x86_64"
- ;;
- arm*)
- SYSTEMTAP_TARGET_ARCH="arm"
- ;;
- powerpc*)
- SYSTEMTAP_TARGET_ARCH="powerpc"
- ;;
- *)
- ;;
- esac
-}
-
-if [ $# -lt 2 ]; then
- usage
- exit 1
-fi
-
-if [ -z "$BUILDDIR" ]; then
- echo "Error: Unable to find the BUILDDIR environment variable."
- echo "Did you forget to source your build system environment setup script?"
- exit 1
-fi
-
-pushd $PWD
-cd $BUILDDIR
-BITBAKE_VARS=`bitbake -e virtual/kernel`
-popd
-
-STAGING_BINDIR_TOOLCHAIN=$(echo "$BITBAKE_VARS" | grep ^STAGING_BINDIR_TOOLCHAIN \
- | cut -d '=' -f2 | cut -d '"' -f2)
-STAGING_BINDIR_TOOLPREFIX=$(echo "$BITBAKE_VARS" | grep ^TARGET_PREFIX \
- | cut -d '=' -f2 | cut -d '"' -f2)
-TARGET_ARCH=$(echo "$BITBAKE_VARS" | grep ^TRANSLATED_TARGET_ARCH \
- | cut -d '=' -f2 | cut -d '"' -f2)
-TARGET_KERNEL_BUILDDIR=$(echo "$BITBAKE_VARS" | grep ^B= \
- | cut -d '=' -f2 | cut -d '"' -f2)
-
-# Build and populate the recipe-sysroot-native with systemtap-native
-pushd $PWD
-cd $BUILDDIR
-BITBAKE_VARS=`bitbake -e systemtap-native`
-popd
-SYSTEMTAP_HOST_INSTALLDIR=$(echo "$BITBAKE_VARS" | grep ^STAGING_DIR_NATIVE \
- | cut -d '=' -f2 | cut -d '"' -f2)
-
-systemtap_target_arch "$TARGET_ARCH"
-
-if [ ! -d $TARGET_KERNEL_BUILDDIR ] ||
- [ ! -f $TARGET_KERNEL_BUILDDIR/vmlinux ]; then
- echo -e "\nError: No target kernel build found."
- echo -e "Did you forget to create a local build of your image?"
- setup_usage
- exit 1
-fi
-
-if [ ! -f $SYSTEMTAP_HOST_INSTALLDIR/usr/bin/stap ]; then
- echo -e "\nError: Native (host) systemtap not found."
- echo -e "Did you accidentally build a local non-sdk image? (or forget to"
- echo -e "add 'tools-profile' to EXTRA_IMAGE_FEATURES in your local.conf)?"
- echo -e "You can also: bitbake -c addto_recipe_sysroot systemtap-native"
- setup_usage
- exit 1
-fi
-
-target_user_hostname="$1"
-full_script_name="$2"
-script_name=$(basename "$2")
-script_base=${script_name%.*}
-shift 2
-
-${SYSTEMTAP_HOST_INSTALLDIR}/usr/bin/stap \
- -a ${SYSTEMTAP_TARGET_ARCH} \
- -B CROSS_COMPILE="${STAGING_BINDIR_TOOLCHAIN}/${STAGING_BINDIR_TOOLPREFIX}" \
- -r ${TARGET_KERNEL_BUILDDIR} \
- -I ${SYSTEMTAP_HOST_INSTALLDIR}/usr/share/systemtap/tapset \
- -R ${SYSTEMTAP_HOST_INSTALLDIR}/usr/share/systemtap/runtime \
- --remote=$target_user_hostname \
- -m $script_base \
- $full_script_name "$@"
-
-exit 0
+import sys
+import re
+import subprocess
+import os
+import optparse
+
+class Stap(object):
+ def __init__(self, script, module, remote):
+ self.script = script
+ self.module = module
+ self.remote = remote
+ self.stap = None
+ self.sysroot = None
+ self.runtime = None
+ self.tapset = None
+ self.arch = None
+ self.cross_compile = None
+ self.kernel_release = None
+ self.target_path = None
+ self.target_ld_library_path = None
+
+ if not self.remote:
+ if not self.module:
+ # derive module name from script
+ self.module = os.path.basename(self.script)
+ if self.module[-4:] == ".stp":
+ self.module = self.module[:-4]
+ # replace - if any with _
+ self.module = self.module.replace("-", "_")
+
+ def command(self, args):
+ ret = []
+ ret.append(self.stap)
+
+ if self.remote:
+ ret.append("--remote")
+ ret.append(self.remote)
+ else:
+ ret.append("-p4")
+ ret.append("-m")
+ ret.append(self.module)
+
+ ret.append("-a")
+ ret.append(self.arch)
+
+ ret.append("-B")
+ ret.append("CROSS_COMPILE=" + self.cross_compile)
+
+ ret.append("-r")
+ ret.append(self.kernel_release)
+
+ ret.append("-I")
+ ret.append(self.tapset)
+
+ ret.append("-R")
+ ret.append(self.runtime)
+
+ if self.sysroot:
+ ret.append("--sysroot")
+ ret.append(self.sysroot)
+
+ ret.append("--sysenv=PATH=" + self.target_path)
+ ret.append("--sysenv=LD_LIBRARY_PATH=" + self.target_ld_library_path)
+
+ ret = ret + args
+
+ ret.append(self.script)
+ return ret
+
+ def additional_environment(self):
+ ret = {}
+ ret["SYSTEMTAP_DEBUGINFO_PATH"] = "+:.debug:build"
+ return ret
+
+ def environment(self):
+ ret = os.environ.copy()
+ additional = self.additional_environment()
+ for e in additional:
+ ret[e] = additional[e]
+ return ret
+
+ def display_command(self, args):
+ additional_env = self.additional_environment()
+ command = self.command(args)
+
+ print("#!/bin/sh")
+ for e in additional_env:
+ print("export %s=\"%s\"" % (e, additional_env[e]))
+ print(" ".join(command))
+
+class BitbakeEnvInvocationException(Exception):
+ def __init__(self, message):
+ self.message = message
+
+class BitbakeEnv(object):
+ BITBAKE="bitbake"
+
+ def __init__(self, package):
+ self.package = package
+ self.cmd = BitbakeEnv.BITBAKE + " -e " + self.package
+ self.popen = subprocess.Popen(self.cmd, shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ self.__lines = self.popen.stdout.readlines()
+ self.popen.wait()
+
+ self.lines = []
+ for line in self.__lines:
+ self.lines.append(line.decode('utf-8'))
+
+ def get_vars(self, vars):
+ if self.popen.returncode:
+ raise BitbakeEnvInvocationException(
+ "\nFailed to execute '" + self.cmd +
+ "' with the following message:\n" +
+ ''.join(self.lines))
+
+ search_patterns = []
+ retdict = {}
+ for var in vars:
+ # regular not exported variable
+ rexpr = "^" + var + "=\"(.*)\""
+ re_compiled = re.compile(rexpr)
+ search_patterns.append((var, re_compiled))
+
+ # exported variable
+ rexpr = "^export " + var + "=\"(.*)\""
+ re_compiled = re.compile(rexpr)
+ search_patterns.append((var, re_compiled))
+
+ for line in self.lines:
+ for var, rexpr in search_patterns:
+ m = rexpr.match(line)
+ if m:
+ value = m.group(1)
+ retdict[var] = value
+
+ # fill variables values in order how they were requested
+ ret = []
+ for var in vars:
+ ret.append(retdict.get(var))
+
+ # if it is single value list return it as scalar, not the list
+ if len(ret) == 1:
+ ret = ret[0]
+
+ return ret
+
+class ParamDiscovery(object):
+ SYMBOLS_CHECK_MESSAGE = """
+WARNING: image '%s' does not have dbg-pkgs IMAGE_FEATURES enabled and no
+"image-combined-dbg" in inherited classes is specified. As result the image
+does not have symbols for user-land processes DWARF based probes. Consider
+adding 'dbg-pkgs' to EXTRA_IMAGE_FEATURES or adding "image-combined-dbg" to
+USER_CLASSES. I.e add this line 'USER_CLASSES += "image-combined-dbg"' to
+local.conf file.
+
+Or you may use IMAGE_GEN_DEBUGFS="1" option, and then after build you need
+recombine/unpack image and image-dbg tarballs and pass resulting dir location
+with --sysroot option.
+"""
+
+ def __init__(self, image):
+ self.image = image
+
+ self.image_rootfs = None
+ self.image_features = None
+ self.image_gen_debugfs = None
+ self.inherit = None
+ self.base_bindir = None
+ self.base_sbindir = None
+ self.base_libdir = None
+ self.bindir = None
+ self.sbindir = None
+ self.libdir = None
+
+ self.staging_bindir_toolchain = None
+ self.target_prefix = None
+ self.target_arch = None
+ self.target_kernel_builddir = None
+
+ self.staging_dir_native = None
+
+ self.image_combined_dbg = False
+
+ def discover(self):
+ if self.image:
+ benv_image = BitbakeEnv(self.image)
+ (self.image_rootfs,
+ self.image_features,
+ self.image_gen_debugfs,
+ self.inherit,
+ self.base_bindir,
+ self.base_sbindir,
+ self.base_libdir,
+ self.bindir,
+ self.sbindir,
+ self.libdir
+ ) = benv_image.get_vars(
+ ("IMAGE_ROOTFS",
+ "IMAGE_FEATURES",
+ "IMAGE_GEN_DEBUGFS",
+ "INHERIT",
+ "base_bindir",
+ "base_sbindir",
+ "base_libdir",
+ "bindir",
+ "sbindir",
+ "libdir"
+ ))
+
+ benv_kernel = BitbakeEnv("virtual/kernel")
+ (self.staging_bindir_toolchain,
+ self.target_prefix,
+ self.target_arch,
+ self.target_kernel_builddir
+ ) = benv_kernel.get_vars(
+ ("STAGING_BINDIR_TOOLCHAIN",
+ "TARGET_PREFIX",
+ "TRANSLATED_TARGET_ARCH",
+ "B"
+ ))
+
+ benv_systemtap = BitbakeEnv("systemtap-native")
+ (self.staging_dir_native
+ ) = benv_systemtap.get_vars(["STAGING_DIR_NATIVE"])
+
+ if self.inherit:
+ if "image-combined-dbg" in self.inherit.split():
+ self.image_combined_dbg = True
+
+ def check(self, sysroot_option):
+ ret = True
+ if self.image_rootfs:
+ sysroot = self.image_rootfs
+ if not os.path.isdir(self.image_rootfs):
+ print("ERROR: Cannot find '" + sysroot +
+ "' directory. Was '" + self.image + "' image built?")
+ ret = False
+
+ stap = self.staging_dir_native + "/usr/bin/stap"
+ if not os.path.isfile(stap):
+ print("ERROR: Cannot find '" + stap +
+ "'. Was 'systemtap-native' built?")
+ ret = False
+
+ if not os.path.isdir(self.target_kernel_builddir):
+ print("ERROR: Cannot find '" + self.target_kernel_builddir +
+ "' directory. Was 'kernel/virtual' built?")
+ ret = False
+
+ if not sysroot_option and self.image_rootfs:
+ dbg_pkgs_found = False
+
+ if self.image_features:
+ image_features = self.image_features.split()
+ if "dbg-pkgs" in image_features:
+ dbg_pkgs_found = True
+
+ if not dbg_pkgs_found \
+ and not self.image_combined_dbg:
+ print(ParamDiscovery.SYMBOLS_CHECK_MESSAGE % (self.image))
+
+ if not ret:
+ print("")
+
+ return ret
+
+ def __map_systemtap_arch(self):
+ a = self.target_arch
+ ret = a
+ if re.match('(athlon|x86.64)$', a):
+ ret = 'x86_64'
+ elif re.match('i.86$', a):
+ ret = 'i386'
+ elif re.match('arm$', a):
+ ret = 'arm'
+ elif re.match('aarch64$', a):
+ ret = 'arm64'
+ elif re.match('mips(isa|)(32|64|)(r6|)(el|)$', a):
+ ret = 'mips'
+ elif re.match('p(pc|owerpc)(|64)', a):
+ ret = 'powerpc'
+ return ret
+
+ def fill_stap(self, stap):
+ stap.stap = self.staging_dir_native + "/usr/bin/stap"
+ if not stap.sysroot:
+ if self.image_rootfs:
+ if self.image_combined_dbg:
+ stap.sysroot = self.image_rootfs + "-dbg"
+ else:
+ stap.sysroot = self.image_rootfs
+ stap.runtime = self.staging_dir_native + "/usr/share/systemtap/runtime"
+ stap.tapset = self.staging_dir_native + "/usr/share/systemtap/tapset"
+ stap.arch = self.__map_systemtap_arch()
+ stap.cross_compile = self.staging_bindir_toolchain + "/" + \
+ self.target_prefix
+ stap.kernel_release = self.target_kernel_builddir
+
+ # do we have standard that tells in which order these need to appear
+ target_path = []
+ if self.sbindir:
+ target_path.append(self.sbindir)
+ if self.bindir:
+ target_path.append(self.bindir)
+ if self.base_sbindir:
+ target_path.append(self.base_sbindir)
+ if self.base_bindir:
+ target_path.append(self.base_bindir)
+ stap.target_path = ":".join(target_path)
+
+ target_ld_library_path = []
+ if self.libdir:
+ target_ld_library_path.append(self.libdir)
+ if self.base_libdir:
+ target_ld_library_path.append(self.base_libdir)
+ stap.target_ld_library_path = ":".join(target_ld_library_path)
+
+
+def main():
+ usage = """usage: %prog -s <systemtap-script> [options] [-- [systemtap options]]
+
+%prog cross compile given SystemTap script against given image, kernel
+
+It needs to run in environtment set for bitbake - it uses bitbake -e
+invocations to retrieve information to construct proper stap cross build
+invocation arguments. It assumes that systemtap-native is built in given
+bitbake workspace.
+
+Anything after -- option is passed directly to stap.
+
+Legacy script invocation style supported but depreciated:
+ %prog <user@hostname> <sytemtap-script> [systemtap options]
+
+To enable most out of systemtap the following site.conf or local.conf
+configuration is recommended:
+
+# enables symbol + target binaries rootfs-dbg in workspace
+IMAGE_GEN_DEBUGFS = "1"
+IMAGE_FSTYPES_DEBUGFS = "tar.bz2"
+USER_CLASSES += "image-combined-dbg"
+
+# enables kernel debug symbols
+KERNEL_EXTRA_FEATURES_append = " features/debug/debug-kernel.scc"
+
+# minimal, just run-time systemtap configuration in target image
+PACKAGECONFIG_pn-systemtap = "monitor"
+
+# add systemtap run-time into target image if it is not there yet
+IMAGE_INSTALL_append = " systemtap"
+"""
+ option_parser = optparse.OptionParser(usage=usage)
+
+ option_parser.add_option("-s", "--script", dest="script",
+ help="specify input script FILE name",
+ metavar="FILE")
+
+ option_parser.add_option("-i", "--image", dest="image",
+ help="specify image name for which script should be compiled")
+
+ option_parser.add_option("-r", "--remote", dest="remote",
+ help="specify username@hostname of remote target to run script "
+ "optional, it assumes that remote target can be accessed through ssh")
+
+ option_parser.add_option("-m", "--module", dest="module",
+ help="specify module name, optional, has effect only if --remote is not used, "
+ "if not specified module name will be derived from passed script name")
+
+ option_parser.add_option("-y", "--sysroot", dest="sysroot",
+ help="explicitely specify image sysroot location. May need to use it in case "
+ "when IMAGE_GEN_DEBUGFS=\"1\" option is used and recombined with symbols "
+ "in different location",
+ metavar="DIR")
+
+ option_parser.add_option("-o", "--out", dest="out",
+ action="store_true",
+ help="output shell script that equvivalent invocation of this script with "
+ "given set of arguments, in given bitbake environment. It could be stored in "
+ "separate shell script and could be repeated without incuring bitbake -e "
+ "invocation overhead",
+ default=False)
+
+ option_parser.add_option("-d", "--debug", dest="debug",
+ action="store_true",
+ help="enable debug output. Use this option to see resulting stap invocation",
+ default=False)
+
+ # is invocation follow syntax from orignal crosstap shell script
+ legacy_args = False
+
+ # check if we called the legacy way
+ if len(sys.argv) >= 3:
+ if sys.argv[1].find("@") != -1 and os.path.exists(sys.argv[2]):
+ legacy_args = True
+
+ # fill options values for legacy invocation case
+ options = optparse.Values
+ options.script = sys.argv[2]
+ options.remote = sys.argv[1]
+ options.image = None
+ options.module = None
+ options.sysroot = None
+ options.out = None
+ options.debug = None
+ remaining_args = sys.argv[3:]
+
+ if not legacy_args:
+ (options, remaining_args) = option_parser.parse_args()
+
+ if not options.script or not os.path.exists(options.script):
+ print("'-s FILE' option is missing\n")
+ option_parser.print_help()
+ else:
+ stap = Stap(options.script, options.module, options.remote)
+ discovery = ParamDiscovery(options.image)
+ discovery.discover()
+ if not discovery.check(options.sysroot):
+ option_parser.print_help()
+ else:
+ stap.sysroot = options.sysroot
+ discovery.fill_stap(stap)
+
+ if options.out:
+ stap.display_command(remaining_args)
+ else:
+ cmd = stap.command(remaining_args)
+ env = stap.environment()
+
+ if options.debug:
+ print(" ".join(cmd))
+
+ os.execve(cmd[0], cmd, env)
+
+main()