diff options
Diffstat (limited to 'import-layers/yocto-poky/scripts/crosstap')
-rwxr-xr-x | import-layers/yocto-poky/scripts/crosstap | 586 |
1 files changed, 450 insertions, 136 deletions
diff --git a/import-layers/yocto-poky/scripts/crosstap b/import-layers/yocto-poky/scripts/crosstap index 39739bba3..e33fa4ad4 100755 --- a/import-layers/yocto-poky/scripts/crosstap +++ b/import-layers/yocto-poky/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() |