crosstap 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. #!/usr/bin/env python3
  2. #
  3. # Build a systemtap script for a given image, kernel
  4. #
  5. # 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.
  8. #
  9. # 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.
  13. #
  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.
  20. # 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. import sys
  35. import re
  36. import subprocess
  37. import os
  38. import optparse
  39. class Stap(object):
  40. def __init__(self, script, module, remote):
  41. self.script = script
  42. self.module = module
  43. self.remote = remote
  44. self.stap = None
  45. self.sysroot = None
  46. self.runtime = None
  47. self.tapset = None
  48. self.arch = None
  49. self.cross_compile = None
  50. self.kernel_release = None
  51. self.target_path = None
  52. self.target_ld_library_path = None
  53. if not self.remote:
  54. if not self.module:
  55. # derive module name from script
  56. self.module = os.path.basename(self.script)
  57. if self.module[-4:] == ".stp":
  58. self.module = self.module[:-4]
  59. # replace - if any with _
  60. self.module = self.module.replace("-", "_")
  61. def command(self, args):
  62. ret = []
  63. ret.append(self.stap)
  64. if self.remote:
  65. ret.append("--remote")
  66. ret.append(self.remote)
  67. else:
  68. ret.append("-p4")
  69. ret.append("-m")
  70. ret.append(self.module)
  71. ret.append("-a")
  72. ret.append(self.arch)
  73. ret.append("-B")
  74. ret.append("CROSS_COMPILE=" + self.cross_compile)
  75. ret.append("-r")
  76. ret.append(self.kernel_release)
  77. ret.append("-I")
  78. ret.append(self.tapset)
  79. ret.append("-R")
  80. ret.append(self.runtime)
  81. if self.sysroot:
  82. ret.append("--sysroot")
  83. ret.append(self.sysroot)
  84. ret.append("--sysenv=PATH=" + self.target_path)
  85. ret.append("--sysenv=LD_LIBRARY_PATH=" + self.target_ld_library_path)
  86. ret = ret + args
  87. ret.append(self.script)
  88. return ret
  89. def additional_environment(self):
  90. ret = {}
  91. ret["SYSTEMTAP_DEBUGINFO_PATH"] = "+:.debug:build"
  92. return ret
  93. def environment(self):
  94. ret = os.environ.copy()
  95. additional = self.additional_environment()
  96. for e in additional:
  97. ret[e] = additional[e]
  98. return ret
  99. def display_command(self, args):
  100. additional_env = self.additional_environment()
  101. command = self.command(args)
  102. print("#!/bin/sh")
  103. for e in additional_env:
  104. print("export %s=\"%s\"" % (e, additional_env[e]))
  105. print(" ".join(command))
  106. class BitbakeEnvInvocationException(Exception):
  107. def __init__(self, message):
  108. self.message = message
  109. class BitbakeEnv(object):
  110. BITBAKE="bitbake"
  111. def __init__(self, package):
  112. self.package = package
  113. self.cmd = BitbakeEnv.BITBAKE + " -e " + self.package
  114. self.popen = subprocess.Popen(self.cmd, shell=True,
  115. stdout=subprocess.PIPE,
  116. stderr=subprocess.STDOUT)
  117. self.__lines = self.popen.stdout.readlines()
  118. self.popen.wait()
  119. self.lines = []
  120. for line in self.__lines:
  121. self.lines.append(line.decode('utf-8'))
  122. def get_vars(self, vars):
  123. if self.popen.returncode:
  124. raise BitbakeEnvInvocationException(
  125. "\nFailed to execute '" + self.cmd +
  126. "' with the following message:\n" +
  127. ''.join(self.lines))
  128. search_patterns = []
  129. retdict = {}
  130. for var in vars:
  131. # regular not exported variable
  132. rexpr = "^" + var + "=\"(.*)\""
  133. re_compiled = re.compile(rexpr)
  134. search_patterns.append((var, re_compiled))
  135. # exported variable
  136. rexpr = "^export " + var + "=\"(.*)\""
  137. re_compiled = re.compile(rexpr)
  138. search_patterns.append((var, re_compiled))
  139. for line in self.lines:
  140. for var, rexpr in search_patterns:
  141. m = rexpr.match(line)
  142. if m:
  143. value = m.group(1)
  144. retdict[var] = value
  145. # fill variables values in order how they were requested
  146. ret = []
  147. for var in vars:
  148. ret.append(retdict.get(var))
  149. # if it is single value list return it as scalar, not the list
  150. if len(ret) == 1:
  151. ret = ret[0]
  152. return ret
  153. class ParamDiscovery(object):
  154. SYMBOLS_CHECK_MESSAGE = """
  155. WARNING: image '%s' does not have dbg-pkgs IMAGE_FEATURES enabled and no
  156. "image-combined-dbg" in inherited classes is specified. As result the image
  157. does not have symbols for user-land processes DWARF based probes. Consider
  158. adding 'dbg-pkgs' to EXTRA_IMAGE_FEATURES or adding "image-combined-dbg" to
  159. USER_CLASSES. I.e add this line 'USER_CLASSES += "image-combined-dbg"' to
  160. local.conf file.
  161. Or you may use IMAGE_GEN_DEBUGFS="1" option, and then after build you need
  162. recombine/unpack image and image-dbg tarballs and pass resulting dir location
  163. with --sysroot option.
  164. """
  165. def __init__(self, image):
  166. self.image = image
  167. self.image_rootfs = None
  168. self.image_features = None
  169. self.image_gen_debugfs = None
  170. self.inherit = None
  171. self.base_bindir = None
  172. self.base_sbindir = None
  173. self.base_libdir = None
  174. self.bindir = None
  175. self.sbindir = None
  176. self.libdir = None
  177. self.staging_bindir_toolchain = None
  178. self.target_prefix = None
  179. self.target_arch = None
  180. self.target_kernel_builddir = None
  181. self.staging_dir_native = None
  182. self.image_combined_dbg = False
  183. def discover(self):
  184. if self.image:
  185. benv_image = BitbakeEnv(self.image)
  186. (self.image_rootfs,
  187. self.image_features,
  188. self.image_gen_debugfs,
  189. self.inherit,
  190. self.base_bindir,
  191. self.base_sbindir,
  192. self.base_libdir,
  193. self.bindir,
  194. self.sbindir,
  195. self.libdir
  196. ) = benv_image.get_vars(
  197. ("IMAGE_ROOTFS",
  198. "IMAGE_FEATURES",
  199. "IMAGE_GEN_DEBUGFS",
  200. "INHERIT",
  201. "base_bindir",
  202. "base_sbindir",
  203. "base_libdir",
  204. "bindir",
  205. "sbindir",
  206. "libdir"
  207. ))
  208. benv_kernel = BitbakeEnv("virtual/kernel")
  209. (self.staging_bindir_toolchain,
  210. self.target_prefix,
  211. self.target_arch,
  212. self.target_kernel_builddir
  213. ) = benv_kernel.get_vars(
  214. ("STAGING_BINDIR_TOOLCHAIN",
  215. "TARGET_PREFIX",
  216. "TRANSLATED_TARGET_ARCH",
  217. "B"
  218. ))
  219. benv_systemtap = BitbakeEnv("systemtap-native")
  220. (self.staging_dir_native
  221. ) = benv_systemtap.get_vars(["STAGING_DIR_NATIVE"])
  222. if self.inherit:
  223. if "image-combined-dbg" in self.inherit.split():
  224. self.image_combined_dbg = True
  225. def check(self, sysroot_option):
  226. ret = True
  227. if self.image_rootfs:
  228. sysroot = self.image_rootfs
  229. if not os.path.isdir(self.image_rootfs):
  230. print("ERROR: Cannot find '" + sysroot +
  231. "' directory. Was '" + self.image + "' image built?")
  232. ret = False
  233. stap = self.staging_dir_native + "/usr/bin/stap"
  234. if not os.path.isfile(stap):
  235. print("ERROR: Cannot find '" + stap +
  236. "'. Was 'systemtap-native' built?")
  237. ret = False
  238. if not os.path.isdir(self.target_kernel_builddir):
  239. print("ERROR: Cannot find '" + self.target_kernel_builddir +
  240. "' directory. Was 'kernel/virtual' built?")
  241. ret = False
  242. if not sysroot_option and self.image_rootfs:
  243. dbg_pkgs_found = False
  244. if self.image_features:
  245. image_features = self.image_features.split()
  246. if "dbg-pkgs" in image_features:
  247. dbg_pkgs_found = True
  248. if not dbg_pkgs_found \
  249. and not self.image_combined_dbg:
  250. print(ParamDiscovery.SYMBOLS_CHECK_MESSAGE % (self.image))
  251. if not ret:
  252. print("")
  253. return ret
  254. def __map_systemtap_arch(self):
  255. a = self.target_arch
  256. ret = a
  257. if re.match('(athlon|x86.64)$', a):
  258. ret = 'x86_64'
  259. elif re.match('i.86$', a):
  260. ret = 'i386'
  261. elif re.match('arm$', a):
  262. ret = 'arm'
  263. elif re.match('aarch64$', a):
  264. ret = 'arm64'
  265. elif re.match('mips(isa|)(32|64|)(r6|)(el|)$', a):
  266. ret = 'mips'
  267. elif re.match('p(pc|owerpc)(|64)', a):
  268. ret = 'powerpc'
  269. return ret
  270. def fill_stap(self, stap):
  271. stap.stap = self.staging_dir_native + "/usr/bin/stap"
  272. if not stap.sysroot:
  273. if self.image_rootfs:
  274. if self.image_combined_dbg:
  275. stap.sysroot = self.image_rootfs + "-dbg"
  276. else:
  277. stap.sysroot = self.image_rootfs
  278. stap.runtime = self.staging_dir_native + "/usr/share/systemtap/runtime"
  279. stap.tapset = self.staging_dir_native + "/usr/share/systemtap/tapset"
  280. stap.arch = self.__map_systemtap_arch()
  281. stap.cross_compile = self.staging_bindir_toolchain + "/" + \
  282. self.target_prefix
  283. stap.kernel_release = self.target_kernel_builddir
  284. # do we have standard that tells in which order these need to appear
  285. target_path = []
  286. if self.sbindir:
  287. target_path.append(self.sbindir)
  288. if self.bindir:
  289. target_path.append(self.bindir)
  290. if self.base_sbindir:
  291. target_path.append(self.base_sbindir)
  292. if self.base_bindir:
  293. target_path.append(self.base_bindir)
  294. stap.target_path = ":".join(target_path)
  295. target_ld_library_path = []
  296. if self.libdir:
  297. target_ld_library_path.append(self.libdir)
  298. if self.base_libdir:
  299. target_ld_library_path.append(self.base_libdir)
  300. stap.target_ld_library_path = ":".join(target_ld_library_path)
  301. def main():
  302. usage = """usage: %prog -s <systemtap-script> [options] [-- [systemtap options]]
  303. %prog cross compile given SystemTap script against given image, kernel
  304. It needs to run in environtment set for bitbake - it uses bitbake -e
  305. invocations to retrieve information to construct proper stap cross build
  306. invocation arguments. It assumes that systemtap-native is built in given
  307. bitbake workspace.
  308. Anything after -- option is passed directly to stap.
  309. Legacy script invocation style supported but depreciated:
  310. %prog <user@hostname> <sytemtap-script> [systemtap options]
  311. To enable most out of systemtap the following site.conf or local.conf
  312. configuration is recommended:
  313. # enables symbol + target binaries rootfs-dbg in workspace
  314. IMAGE_GEN_DEBUGFS = "1"
  315. IMAGE_FSTYPES_DEBUGFS = "tar.bz2"
  316. USER_CLASSES += "image-combined-dbg"
  317. # enables kernel debug symbols
  318. KERNEL_EXTRA_FEATURES_append = " features/debug/debug-kernel.scc"
  319. # minimal, just run-time systemtap configuration in target image
  320. PACKAGECONFIG_pn-systemtap = "monitor"
  321. # add systemtap run-time into target image if it is not there yet
  322. IMAGE_INSTALL_append = " systemtap"
  323. """
  324. option_parser = optparse.OptionParser(usage=usage)
  325. option_parser.add_option("-s", "--script", dest="script",
  326. help="specify input script FILE name",
  327. metavar="FILE")
  328. option_parser.add_option("-i", "--image", dest="image",
  329. help="specify image name for which script should be compiled")
  330. option_parser.add_option("-r", "--remote", dest="remote",
  331. help="specify username@hostname of remote target to run script "
  332. "optional, it assumes that remote target can be accessed through ssh")
  333. option_parser.add_option("-m", "--module", dest="module",
  334. help="specify module name, optional, has effect only if --remote is not used, "
  335. "if not specified module name will be derived from passed script name")
  336. option_parser.add_option("-y", "--sysroot", dest="sysroot",
  337. help="explicitely specify image sysroot location. May need to use it in case "
  338. "when IMAGE_GEN_DEBUGFS=\"1\" option is used and recombined with symbols "
  339. "in different location",
  340. metavar="DIR")
  341. option_parser.add_option("-o", "--out", dest="out",
  342. action="store_true",
  343. help="output shell script that equvivalent invocation of this script with "
  344. "given set of arguments, in given bitbake environment. It could be stored in "
  345. "separate shell script and could be repeated without incuring bitbake -e "
  346. "invocation overhead",
  347. default=False)
  348. option_parser.add_option("-d", "--debug", dest="debug",
  349. action="store_true",
  350. help="enable debug output. Use this option to see resulting stap invocation",
  351. default=False)
  352. # is invocation follow syntax from orignal crosstap shell script
  353. legacy_args = False
  354. # check if we called the legacy way
  355. if len(sys.argv) >= 3:
  356. if sys.argv[1].find("@") != -1 and os.path.exists(sys.argv[2]):
  357. legacy_args = True
  358. # fill options values for legacy invocation case
  359. options = optparse.Values
  360. options.script = sys.argv[2]
  361. options.remote = sys.argv[1]
  362. options.image = None
  363. options.module = None
  364. options.sysroot = None
  365. options.out = None
  366. options.debug = None
  367. remaining_args = sys.argv[3:]
  368. if not legacy_args:
  369. (options, remaining_args) = option_parser.parse_args()
  370. if not options.script or not os.path.exists(options.script):
  371. print("'-s FILE' option is missing\n")
  372. option_parser.print_help()
  373. else:
  374. stap = Stap(options.script, options.module, options.remote)
  375. discovery = ParamDiscovery(options.image)
  376. discovery.discover()
  377. if not discovery.check(options.sysroot):
  378. option_parser.print_help()
  379. else:
  380. stap.sysroot = options.sysroot
  381. discovery.fill_stap(stap)
  382. if options.out:
  383. stap.display_command(remaining_args)
  384. else:
  385. cmd = stap.command(remaining_args)
  386. env = stap.environment()
  387. if options.debug:
  388. print(" ".join(cmd))
  389. os.execve(cmd[0], cmd, env)
  390. main()