summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Kamensky <kamensky@cisco.com>2018-04-05 11:25:30 -0700
committerRichard Purdie <richard.purdie@linuxfoundation.org>2018-04-07 11:44:50 +0100
commit7a39dc3996dc445c83ac1665e9099e1244cf396d (patch)
tree076ae949b741aa98758cd686dc320cd48cbca2ff
parent77f69397e02826d3ac9ad69285bca6bc2b56d85c (diff)
downloadpoky-7a39dc3996dc445c83ac1665e9099e1244cf396d.tar.gz
crosstap: replace script with new python based implementation
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>
-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 @@
1#!/bin/bash 1#!/usr/bin/env python3
2# 2#
3# Run a systemtap script on remote target 3# Build a systemtap script for a given image, kernel
4# 4#
5# Examples (run on build host, target is 192.168.1.xxx): 5# Effectively script extracts needed information from set of
6# $ source oe-init-build-env" 6# 'bitbake -e' commands and contructs proper invocation of stap on
7# $ cd ~/my/systemtap/scripts" 7# host to build systemtap script for a given target.
8# 8#
9# $ crosstap root@192.168.1.xxx myscript.stp" 9# By default script will compile scriptname.ko that could be copied
10# $ crosstap root@192.168.1.xxx myscript-with-args.stp 99 ninetynine" 10# to taget and activated with 'staprun scriptname.ko' command. Or if
11# --remote user@hostname option is specified script will build, load
12# execute script on target.
11# 13#
12# Copyright (c) 2012, Intel Corporation. 14# This script is very similar and inspired by crosstap shell script.
15# The major difference that this script supports user-land related
16# systemtap script, whereas crosstap could deal only with scripts
17# related to kernel.
18#
19# Copyright (c) 2018, Cisco Systems.
13# All rights reserved. 20# All rights reserved.
14# 21#
15# This program is free software; you can redistribute it and/or modify 22# This program is free software; you can redistribute it and/or modify
@@ -25,131 +32,438 @@
25# along with this program; if not, write to the Free Software 32# along with this program; if not, write to the Free Software
26# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 33# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 34
28function usage() { 35import sys
29 echo "Usage: $0 <user@hostname> <sytemtap-script> [additional systemtap-script args]" 36import re
30} 37import subprocess
31 38import os
32function setup_usage() { 39import optparse
33 echo "" 40
34 echo "'crosstap' requires a local sdk build of the target system" 41class Stap(object):
35 echo "(or a build that includes 'tools-profile') in order to build" 42 def __init__(self, script, module, remote):
36 echo "kernel modules that can probe the target system." 43 self.script = script
37 echo "" 44 self.module = module
38 echo "Practically speaking, that means you need to do the following:" 45 self.remote = remote
39 echo " - If you're running a pre-built image, download the release" 46 self.stap = None
40 echo " and/or BSP tarballs used to build the image." 47 self.sysroot = None
41 echo " - If you're working from git sources, just clone the metadata" 48 self.runtime = None
42 echo " and BSP layers needed to build the image you'll be booting." 49 self.tapset = None
43 echo " - Make sure you're properly set up to build a new image (see" 50 self.arch = None
44 echo " the BSP README and/or the widely available basic documentation" 51 self.cross_compile = None
45 echo " that discusses how to build images)." 52 self.kernel_release = None
46 echo " - Build an -sdk version of the image e.g.:" 53 self.target_path = None
47 echo " $ bitbake core-image-sato-sdk" 54 self.target_ld_library_path = None
48 echo " OR" 55
49 echo " - Build a non-sdk image but include the profiling tools:" 56 if not self.remote:
50 echo " [ edit local.conf and add 'tools-profile' to the end of" 57 if not self.module:
51 echo " the EXTRA_IMAGE_FEATURES variable ]" 58 # derive module name from script
52 echo " $ bitbake core-image-sato" 59 self.module = os.path.basename(self.script)
53 echo "" 60 if self.module[-4:] == ".stp":
54 echo " [ NOTE that 'crosstap' needs to be able to ssh into the target" 61 self.module = self.module[:-4]
55 echo " system, which isn't enabled by default in -minimal images. ]" 62 # replace - if any with _
56 echo "" 63 self.module = self.module.replace("-", "_")
57 echo "Once you've build the image on the host system, you're ready to" 64
58 echo "boot it (or the equivalent pre-built image) and use 'crosstap'" 65 def command(self, args):
59 echo "to probe it (you need to source the environment as usual first):" 66 ret = []
60 echo "" 67 ret.append(self.stap)
61 echo " $ source oe-init-build-env" 68
62 echo " $ cd ~/my/systemtap/scripts" 69 if self.remote:
63 echo " $ crosstap root@192.168.1.xxx myscript.stp" 70 ret.append("--remote")
64 echo "" 71 ret.append(self.remote)
65} 72 else:
66 73 ret.append("-p4")
67function systemtap_target_arch() { 74 ret.append("-m")
68 SYSTEMTAP_TARGET_ARCH=$1 75 ret.append(self.module)
69 case $SYSTEMTAP_TARGET_ARCH in 76
70 i?86) 77 ret.append("-a")
71 SYSTEMTAP_TARGET_ARCH="i386" 78 ret.append(self.arch)
72 ;; 79
73 x86?64*) 80 ret.append("-B")
74 SYSTEMTAP_TARGET_ARCH="x86_64" 81 ret.append("CROSS_COMPILE=" + self.cross_compile)
75 ;; 82
76 arm*) 83 ret.append("-r")
77 SYSTEMTAP_TARGET_ARCH="arm" 84 ret.append(self.kernel_release)
78 ;; 85
79 powerpc*) 86 ret.append("-I")
80 SYSTEMTAP_TARGET_ARCH="powerpc" 87 ret.append(self.tapset)
81 ;; 88
82 *) 89 ret.append("-R")
83 ;; 90 ret.append(self.runtime)
84 esac 91
85} 92 if self.sysroot:
86 93 ret.append("--sysroot")
87if [ $# -lt 2 ]; then 94 ret.append(self.sysroot)
88 usage 95
89 exit 1 96 ret.append("--sysenv=PATH=" + self.target_path)
90fi 97 ret.append("--sysenv=LD_LIBRARY_PATH=" + self.target_ld_library_path)
91 98
92if [ -z "$BUILDDIR" ]; then 99 ret = ret + args
93 echo "Error: Unable to find the BUILDDIR environment variable." 100
94 echo "Did you forget to source your build system environment setup script?" 101 ret.append(self.script)
95 exit 1 102 return ret
96fi 103
97 104 def additional_environment(self):
98pushd $PWD 105 ret = {}
99cd $BUILDDIR 106 ret["SYSTEMTAP_DEBUGINFO_PATH"] = "+:.debug:build"
100BITBAKE_VARS=`bitbake -e virtual/kernel` 107 return ret
101popd 108
102 109 def environment(self):
103STAGING_BINDIR_TOOLCHAIN=$(echo "$BITBAKE_VARS" | grep ^STAGING_BINDIR_TOOLCHAIN \ 110 ret = os.environ.copy()
104 | cut -d '=' -f2 | cut -d '"' -f2) 111 additional = self.additional_environment()
105STAGING_BINDIR_TOOLPREFIX=$(echo "$BITBAKE_VARS" | grep ^TARGET_PREFIX \ 112 for e in additional:
106 | cut -d '=' -f2 | cut -d '"' -f2) 113 ret[e] = additional[e]
107TARGET_ARCH=$(echo "$BITBAKE_VARS" | grep ^TRANSLATED_TARGET_ARCH \ 114 return ret
108 | cut -d '=' -f2 | cut -d '"' -f2) 115
109TARGET_KERNEL_BUILDDIR=$(echo "$BITBAKE_VARS" | grep ^B= \ 116 def display_command(self, args):
110 | cut -d '=' -f2 | cut -d '"' -f2) 117 additional_env = self.additional_environment()
111 118 command = self.command(args)
112# Build and populate the recipe-sysroot-native with systemtap-native 119
113pushd $PWD 120 print("#!/bin/sh")
114cd $BUILDDIR 121 for e in additional_env:
115BITBAKE_VARS=`bitbake -e systemtap-native` 122 print("export %s=\"%s\"" % (e, additional_env[e]))
116popd 123 print(" ".join(command))
117SYSTEMTAP_HOST_INSTALLDIR=$(echo "$BITBAKE_VARS" | grep ^STAGING_DIR_NATIVE \ 124
118 | cut -d '=' -f2 | cut -d '"' -f2) 125class BitbakeEnvInvocationException(Exception):
119 126 def __init__(self, message):
120systemtap_target_arch "$TARGET_ARCH" 127 self.message = message
121 128
122if [ ! -d $TARGET_KERNEL_BUILDDIR ] || 129class BitbakeEnv(object):
123 [ ! -f $TARGET_KERNEL_BUILDDIR/vmlinux ]; then 130 BITBAKE="bitbake"
124 echo -e "\nError: No target kernel build found." 131
125 echo -e "Did you forget to create a local build of your image?" 132 def __init__(self, package):
126 setup_usage 133 self.package = package
127 exit 1 134 self.cmd = BitbakeEnv.BITBAKE + " -e " + self.package
128fi 135 self.popen = subprocess.Popen(self.cmd, shell=True,
129 136 stdout=subprocess.PIPE,
130if [ ! -f $SYSTEMTAP_HOST_INSTALLDIR/usr/bin/stap ]; then 137 stderr=subprocess.STDOUT)
131 echo -e "\nError: Native (host) systemtap not found." 138 self.__lines = self.popen.stdout.readlines()
132 echo -e "Did you accidentally build a local non-sdk image? (or forget to" 139 self.popen.wait()
133 echo -e "add 'tools-profile' to EXTRA_IMAGE_FEATURES in your local.conf)?" 140
134 echo -e "You can also: bitbake -c addto_recipe_sysroot systemtap-native" 141 self.lines = []
135 setup_usage 142 for line in self.__lines:
136 exit 1 143 self.lines.append(line.decode('utf-8'))
137fi 144
138 145 def get_vars(self, vars):
139target_user_hostname="$1" 146 if self.popen.returncode:
140full_script_name="$2" 147 raise BitbakeEnvInvocationException(
141script_name=$(basename "$2") 148 "\nFailed to execute '" + self.cmd +
142script_base=${script_name%.*} 149 "' with the following message:\n" +
143shift 2 150 ''.join(self.lines))
144 151
145${SYSTEMTAP_HOST_INSTALLDIR}/usr/bin/stap \ 152 search_patterns = []
146 -a ${SYSTEMTAP_TARGET_ARCH} \ 153 retdict = {}
147 -B CROSS_COMPILE="${STAGING_BINDIR_TOOLCHAIN}/${STAGING_BINDIR_TOOLPREFIX}" \ 154 for var in vars:
148 -r ${TARGET_KERNEL_BUILDDIR} \ 155 # regular not exported variable
149 -I ${SYSTEMTAP_HOST_INSTALLDIR}/usr/share/systemtap/tapset \ 156 rexpr = "^" + var + "=\"(.*)\""
150 -R ${SYSTEMTAP_HOST_INSTALLDIR}/usr/share/systemtap/runtime \ 157 re_compiled = re.compile(rexpr)
151 --remote=$target_user_hostname \ 158 search_patterns.append((var, re_compiled))
152 -m $script_base \ 159
153 $full_script_name "$@" 160 # exported variable
154 161 rexpr = "^export " + var + "=\"(.*)\""
155exit 0 162 re_compiled = re.compile(rexpr)
163 search_patterns.append((var, re_compiled))
164
165 for line in self.lines:
166 for var, rexpr in search_patterns:
167 m = rexpr.match(line)
168 if m:
169 value = m.group(1)
170 retdict[var] = value
171
172 # fill variables values in order how they were requested
173 ret = []
174 for var in vars:
175 ret.append(retdict.get(var))
176
177 # if it is single value list return it as scalar, not the list
178 if len(ret) == 1:
179 ret = ret[0]
180
181 return ret
182
183class ParamDiscovery(object):
184 SYMBOLS_CHECK_MESSAGE = """
185WARNING: image '%s' does not have dbg-pkgs IMAGE_FEATURES enabled and no
186"image-combined-dbg" in inherited classes is specified. As result the image
187does not have symbols for user-land processes DWARF based probes. Consider
188adding 'dbg-pkgs' to EXTRA_IMAGE_FEATURES or adding "image-combined-dbg" to
189USER_CLASSES. I.e add this line 'USER_CLASSES += "image-combined-dbg"' to
190local.conf file.
191
192Or you may use IMAGE_GEN_DEBUGFS="1" option, and then after build you need
193recombine/unpack image and image-dbg tarballs and pass resulting dir location
194with --sysroot option.
195"""
196
197 def __init__(self, image):
198 self.image = image
199
200 self.image_rootfs = None
201 self.image_features = None
202 self.image_gen_debugfs = None
203 self.inherit = None
204 self.base_bindir = None
205 self.base_sbindir = None
206 self.base_libdir = None
207 self.bindir = None
208 self.sbindir = None
209 self.libdir = None
210
211 self.staging_bindir_toolchain = None
212 self.target_prefix = None
213 self.target_arch = None
214 self.target_kernel_builddir = None
215
216 self.staging_dir_native = None
217
218 self.image_combined_dbg = False
219
220 def discover(self):
221 if self.image:
222 benv_image = BitbakeEnv(self.image)
223 (self.image_rootfs,
224 self.image_features,
225 self.image_gen_debugfs,
226 self.inherit,
227 self.base_bindir,
228 self.base_sbindir,
229 self.base_libdir,
230 self.bindir,
231 self.sbindir,
232 self.libdir
233 ) = benv_image.get_vars(
234 ("IMAGE_ROOTFS",
235 "IMAGE_FEATURES",
236 "IMAGE_GEN_DEBUGFS",
237 "INHERIT",
238 "base_bindir",
239 "base_sbindir",
240 "base_libdir",
241 "bindir",
242 "sbindir",
243 "libdir"
244 ))
245
246 benv_kernel = BitbakeEnv("virtual/kernel")
247 (self.staging_bindir_toolchain,
248 self.target_prefix,
249 self.target_arch,
250 self.target_kernel_builddir
251 ) = benv_kernel.get_vars(
252 ("STAGING_BINDIR_TOOLCHAIN",
253 "TARGET_PREFIX",
254 "TRANSLATED_TARGET_ARCH",
255 "B"
256 ))
257
258 benv_systemtap = BitbakeEnv("systemtap-native")
259 (self.staging_dir_native
260 ) = benv_systemtap.get_vars(["STAGING_DIR_NATIVE"])
261
262 if self.inherit:
263 if "image-combined-dbg" in self.inherit.split():
264 self.image_combined_dbg = True
265
266 def check(self, sysroot_option):
267 ret = True
268 if self.image_rootfs:
269 sysroot = self.image_rootfs
270 if not os.path.isdir(self.image_rootfs):
271 print("ERROR: Cannot find '" + sysroot +
272 "' directory. Was '" + self.image + "' image built?")
273 ret = False
274
275 stap = self.staging_dir_native + "/usr/bin/stap"
276 if not os.path.isfile(stap):
277 print("ERROR: Cannot find '" + stap +
278 "'. Was 'systemtap-native' built?")
279 ret = False
280
281 if not os.path.isdir(self.target_kernel_builddir):
282 print("ERROR: Cannot find '" + self.target_kernel_builddir +
283 "' directory. Was 'kernel/virtual' built?")
284 ret = False
285
286 if not sysroot_option and self.image_rootfs:
287 dbg_pkgs_found = False
288
289 if self.image_features:
290 image_features = self.image_features.split()
291 if "dbg-pkgs" in image_features:
292 dbg_pkgs_found = True
293
294 if not dbg_pkgs_found \
295 and not self.image_combined_dbg:
296 print(ParamDiscovery.SYMBOLS_CHECK_MESSAGE % (self.image))
297
298 if not ret:
299 print("")
300
301 return ret
302
303 def __map_systemtap_arch(self):
304 a = self.target_arch
305 ret = a
306 if re.match('(athlon|x86.64)$', a):
307 ret = 'x86_64'
308 elif re.match('i.86$', a):
309 ret = 'i386'
310 elif re.match('arm$', a):
311 ret = 'arm'
312 elif re.match('aarch64$', a):
313 ret = 'arm64'
314 elif re.match('mips(isa|)(32|64|)(r6|)(el|)$', a):
315 ret = 'mips'
316 elif re.match('p(pc|owerpc)(|64)', a):
317 ret = 'powerpc'
318 return ret
319
320 def fill_stap(self, stap):
321 stap.stap = self.staging_dir_native + "/usr/bin/stap"
322 if not stap.sysroot:
323 if self.image_rootfs:
324 if self.image_combined_dbg:
325 stap.sysroot = self.image_rootfs + "-dbg"
326 else:
327 stap.sysroot = self.image_rootfs
328 stap.runtime = self.staging_dir_native + "/usr/share/systemtap/runtime"
329 stap.tapset = self.staging_dir_native + "/usr/share/systemtap/tapset"
330 stap.arch = self.__map_systemtap_arch()
331 stap.cross_compile = self.staging_bindir_toolchain + "/" + \
332 self.target_prefix
333 stap.kernel_release = self.target_kernel_builddir
334
335 # do we have standard that tells in which order these need to appear
336 target_path = []
337 if self.sbindir:
338 target_path.append(self.sbindir)
339 if self.bindir:
340 target_path.append(self.bindir)
341 if self.base_sbindir:
342 target_path.append(self.base_sbindir)
343 if self.base_bindir:
344 target_path.append(self.base_bindir)
345 stap.target_path = ":".join(target_path)
346
347 target_ld_library_path = []
348 if self.libdir:
349 target_ld_library_path.append(self.libdir)
350 if self.base_libdir:
351 target_ld_library_path.append(self.base_libdir)
352 stap.target_ld_library_path = ":".join(target_ld_library_path)
353
354
355def main():
356 usage = """usage: %prog -s <systemtap-script> [options] [-- [systemtap options]]
357
358%prog cross compile given SystemTap script against given image, kernel
359
360It needs to run in environtment set for bitbake - it uses bitbake -e
361invocations to retrieve information to construct proper stap cross build
362invocation arguments. It assumes that systemtap-native is built in given
363bitbake workspace.
364
365Anything after -- option is passed directly to stap.
366
367Legacy script invocation style supported but depreciated:
368 %prog <user@hostname> <sytemtap-script> [systemtap options]
369
370To enable most out of systemtap the following site.conf or local.conf
371configuration is recommended:
372
373# enables symbol + target binaries rootfs-dbg in workspace
374IMAGE_GEN_DEBUGFS = "1"
375IMAGE_FSTYPES_DEBUGFS = "tar.bz2"
376USER_CLASSES += "image-combined-dbg"
377
378# enables kernel debug symbols
379KERNEL_EXTRA_FEATURES_append = " features/debug/debug-kernel.scc"
380
381# minimal, just run-time systemtap configuration in target image
382PACKAGECONFIG_pn-systemtap = "monitor"
383
384# add systemtap run-time into target image if it is not there yet
385IMAGE_INSTALL_append = " systemtap"
386"""
387 option_parser = optparse.OptionParser(usage=usage)
388
389 option_parser.add_option("-s", "--script", dest="script",
390 help="specify input script FILE name",
391 metavar="FILE")
392
393 option_parser.add_option("-i", "--image", dest="image",
394 help="specify image name for which script should be compiled")
395
396 option_parser.add_option("-r", "--remote", dest="remote",
397 help="specify username@hostname of remote target to run script "
398 "optional, it assumes that remote target can be accessed through ssh")
399
400 option_parser.add_option("-m", "--module", dest="module",
401 help="specify module name, optional, has effect only if --remote is not used, "
402 "if not specified module name will be derived from passed script name")
403
404 option_parser.add_option("-y", "--sysroot", dest="sysroot",
405 help="explicitely specify image sysroot location. May need to use it in case "
406 "when IMAGE_GEN_DEBUGFS=\"1\" option is used and recombined with symbols "
407 "in different location",
408 metavar="DIR")
409
410 option_parser.add_option("-o", "--out", dest="out",
411 action="store_true",
412 help="output shell script that equvivalent invocation of this script with "
413 "given set of arguments, in given bitbake environment. It could be stored in "
414 "separate shell script and could be repeated without incuring bitbake -e "
415 "invocation overhead",
416 default=False)
417
418 option_parser.add_option("-d", "--debug", dest="debug",
419 action="store_true",
420 help="enable debug output. Use this option to see resulting stap invocation",
421 default=False)
422
423 # is invocation follow syntax from orignal crosstap shell script
424 legacy_args = False
425
426 # check if we called the legacy way
427 if len(sys.argv) >= 3:
428 if sys.argv[1].find("@") != -1 and os.path.exists(sys.argv[2]):
429 legacy_args = True
430
431 # fill options values for legacy invocation case
432 options = optparse.Values
433 options.script = sys.argv[2]
434 options.remote = sys.argv[1]
435 options.image = None
436 options.module = None
437 options.sysroot = None
438 options.out = None
439 options.debug = None
440 remaining_args = sys.argv[3:]
441
442 if not legacy_args:
443 (options, remaining_args) = option_parser.parse_args()
444
445 if not options.script or not os.path.exists(options.script):
446 print("'-s FILE' option is missing\n")
447 option_parser.print_help()
448 else:
449 stap = Stap(options.script, options.module, options.remote)
450 discovery = ParamDiscovery(options.image)
451 discovery.discover()
452 if not discovery.check(options.sysroot):
453 option_parser.print_help()
454 else:
455 stap.sysroot = options.sysroot
456 discovery.fill_stap(stap)
457
458 if options.out:
459 stap.display_command(remaining_args)
460 else:
461 cmd = stap.command(remaining_args)
462 env = stap.environment()
463
464 if options.debug:
465 print(" ".join(cmd))
466
467 os.execve(cmd[0], cmd, env)
468
469main()