poky/scripts/crosstap
Robert P. J. Day eec3bbdd27 scripts: Various typo/grammar/punctuation fixes
Among other things, fix misspellings of:

  - absolute
  - deprecated
  - suitable
  - handle

and a bunch of other things.

(From OE-Core rev: c3773cd6c44dfe82be9ecd248120e7d6c753f891)

Signed-off-by: Robert P. J. Day <rpjday@crashcourse.ca>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2022-03-02 18:43:24 +00:00

15 KiB
Executable File

#!/usr/bin/env python3

Build a systemtap script for a given image, kernel

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.

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.

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.

SPDX-License-Identifier: GPL-2.0-only

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 [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 deprecated: %prog user@hostname [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()