blob: e33fa4ad469f0c89c240699dea942ef35339529c [file] [log] [blame]
Brad Bishop316dfdd2018-06-25 12:45:53 -04001#!/usr/bin/env python3
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002#
Brad Bishop316dfdd2018-06-25 12:45:53 -04003# Build a systemtap script for a given image, kernel
Patrick Williamsc124f4f2015-09-15 14:41:29 -05004#
Brad Bishop316dfdd2018-06-25 12:45:53 -04005# Effectively script extracts needed information from set of
6# 'bitbake -e' commands and contructs proper invocation of stap on
7# host to build systemtap script for a given target.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05008#
Brad Bishop316dfdd2018-06-25 12:45:53 -04009# By default script will compile scriptname.ko that could be copied
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.
Patrick Williamsc124f4f2015-09-15 14:41:29 -050013#
Brad Bishop316dfdd2018-06-25 12:45:53 -040014# 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.
Patrick Williamsc124f4f2015-09-15 14:41:29 -050020# All rights reserved.
21#
22# This program is free software; you can redistribute it and/or modify
23# it under the terms of the GNU General Public License version 2 as
24# published by the Free Software Foundation.
25#
26# This program is distributed in the hope that it will be useful,
27# but WITHOUT ANY WARRANTY; without even the implied warranty of
28# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
29# See the GNU General Public License for more details.
30#
31# You should have received a copy of the GNU General Public License
32# along with this program; if not, write to the Free Software
33# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
34
Brad Bishop316dfdd2018-06-25 12:45:53 -040035import sys
36import re
37import subprocess
38import os
39import optparse
Patrick Williamsc124f4f2015-09-15 14:41:29 -050040
Brad Bishop316dfdd2018-06-25 12:45:53 -040041class Stap(object):
42 def __init__(self, script, module, remote):
43 self.script = script
44 self.module = module
45 self.remote = remote
46 self.stap = None
47 self.sysroot = None
48 self.runtime = None
49 self.tapset = None
50 self.arch = None
51 self.cross_compile = None
52 self.kernel_release = None
53 self.target_path = None
54 self.target_ld_library_path = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -050055
Brad Bishop316dfdd2018-06-25 12:45:53 -040056 if not self.remote:
57 if not self.module:
58 # derive module name from script
59 self.module = os.path.basename(self.script)
60 if self.module[-4:] == ".stp":
61 self.module = self.module[:-4]
62 # replace - if any with _
63 self.module = self.module.replace("-", "_")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050064
Brad Bishop316dfdd2018-06-25 12:45:53 -040065 def command(self, args):
66 ret = []
67 ret.append(self.stap)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050068
Brad Bishop316dfdd2018-06-25 12:45:53 -040069 if self.remote:
70 ret.append("--remote")
71 ret.append(self.remote)
72 else:
73 ret.append("-p4")
74 ret.append("-m")
75 ret.append(self.module)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050076
Brad Bishop316dfdd2018-06-25 12:45:53 -040077 ret.append("-a")
78 ret.append(self.arch)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050079
Brad Bishop316dfdd2018-06-25 12:45:53 -040080 ret.append("-B")
81 ret.append("CROSS_COMPILE=" + self.cross_compile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050082
Brad Bishop316dfdd2018-06-25 12:45:53 -040083 ret.append("-r")
84 ret.append(self.kernel_release)
Brad Bishop6e60e8b2018-02-01 10:27:11 -050085
Brad Bishop316dfdd2018-06-25 12:45:53 -040086 ret.append("-I")
87 ret.append(self.tapset)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050088
Brad Bishop316dfdd2018-06-25 12:45:53 -040089 ret.append("-R")
90 ret.append(self.runtime)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050091
Brad Bishop316dfdd2018-06-25 12:45:53 -040092 if self.sysroot:
93 ret.append("--sysroot")
94 ret.append(self.sysroot)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050095
Brad Bishop316dfdd2018-06-25 12:45:53 -040096 ret.append("--sysenv=PATH=" + self.target_path)
97 ret.append("--sysenv=LD_LIBRARY_PATH=" + self.target_ld_library_path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050098
Brad Bishop316dfdd2018-06-25 12:45:53 -040099 ret = ret + args
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500100
Brad Bishop316dfdd2018-06-25 12:45:53 -0400101 ret.append(self.script)
102 return ret
103
104 def additional_environment(self):
105 ret = {}
106 ret["SYSTEMTAP_DEBUGINFO_PATH"] = "+:.debug:build"
107 return ret
108
109 def environment(self):
110 ret = os.environ.copy()
111 additional = self.additional_environment()
112 for e in additional:
113 ret[e] = additional[e]
114 return ret
115
116 def display_command(self, args):
117 additional_env = self.additional_environment()
118 command = self.command(args)
119
120 print("#!/bin/sh")
121 for e in additional_env:
122 print("export %s=\"%s\"" % (e, additional_env[e]))
123 print(" ".join(command))
124
125class BitbakeEnvInvocationException(Exception):
126 def __init__(self, message):
127 self.message = message
128
129class BitbakeEnv(object):
130 BITBAKE="bitbake"
131
132 def __init__(self, package):
133 self.package = package
134 self.cmd = BitbakeEnv.BITBAKE + " -e " + self.package
135 self.popen = subprocess.Popen(self.cmd, shell=True,
136 stdout=subprocess.PIPE,
137 stderr=subprocess.STDOUT)
138 self.__lines = self.popen.stdout.readlines()
139 self.popen.wait()
140
141 self.lines = []
142 for line in self.__lines:
143 self.lines.append(line.decode('utf-8'))
144
145 def get_vars(self, vars):
146 if self.popen.returncode:
147 raise BitbakeEnvInvocationException(
148 "\nFailed to execute '" + self.cmd +
149 "' with the following message:\n" +
150 ''.join(self.lines))
151
152 search_patterns = []
153 retdict = {}
154 for var in vars:
155 # regular not exported variable
156 rexpr = "^" + var + "=\"(.*)\""
157 re_compiled = re.compile(rexpr)
158 search_patterns.append((var, re_compiled))
159
160 # exported variable
161 rexpr = "^export " + var + "=\"(.*)\""
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()