mirror of
git://git.yoctoproject.org/poky.git
synced 2025-07-19 21:09:03 +02:00

New crosstap python implementation is total replacement for crosstap shell script, that has superseding capabilities. New script support cross compiling of SystemTap scripts for user-land, by using supplied image rootfs. Whereas old script could only deal with scripts against kernel. New script has more complex logic and additional capabilities. As invocation interface new script support old "legacy" mode and provides alternative new regular options interface to access additional functionality. (From OE-Core rev: 1cbbcf26e0a9ca6e0b34a89512bf75dbae8bfaf0) Signed-off-by: Victor Kamensky <kamensky@cisco.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
470 lines
16 KiB
Python
Executable File
470 lines
16 KiB
Python
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.
|
|
# All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License version 2 as
|
|
# published by the Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
# See the GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
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()
|