common.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. #!/usr/bin/env python3
  2. import argparse
  3. import base64
  4. import collections
  5. import copy
  6. import datetime
  7. import glob
  8. import imp
  9. import json
  10. import multiprocessing
  11. import os
  12. import re
  13. import shutil
  14. import signal
  15. import subprocess
  16. import sys
  17. import time
  18. import urllib
  19. import urllib.request
  20. import cli_function
  21. import shell_helpers
  22. common = sys.modules[__name__]
  23. # Fixed parameters that don't depend on CLI arguments.
  24. consts = {}
  25. consts['repo_short_id'] = 'lkmc'
  26. # https://stackoverflow.com/questions/20010199/how-to-determine-if-a-process-runs-inside-lxc-docker
  27. consts['in_docker'] = os.path.exists('/.dockerenv')
  28. consts['root_dir'] = os.path.dirname(os.path.abspath(__file__))
  29. consts['data_dir'] = os.path.join(consts['root_dir'], 'data')
  30. consts['p9_dir'] = os.path.join(consts['data_dir'], '9p')
  31. consts['gem5_non_default_src_root_dir'] = os.path.join(consts['data_dir'], 'gem5')
  32. if consts['in_docker']:
  33. consts['out_dir'] = os.path.join(consts['root_dir'], 'out.docker')
  34. else:
  35. consts['out_dir'] = os.path.join(consts['root_dir'], 'out')
  36. consts['gem5_out_dir'] = os.path.join(consts['out_dir'], 'gem5')
  37. consts['kernel_modules_build_base_dir'] = os.path.join(consts['out_dir'], 'kernel_modules')
  38. consts['buildroot_out_dir'] = os.path.join(consts['out_dir'], 'buildroot')
  39. consts['gem5_m5_build_dir'] = os.path.join(consts['out_dir'], 'util', 'm5')
  40. consts['run_dir_base'] = os.path.join(consts['out_dir'], 'run')
  41. consts['crosstool_ng_out_dir'] = os.path.join(consts['out_dir'], 'crosstool-ng')
  42. consts['bench_boot'] = os.path.join(consts['out_dir'], 'bench-boot.txt')
  43. consts['packages_dir'] = os.path.join(consts['root_dir'], 'buildroot_packages')
  44. consts['kernel_modules_subdir'] = 'kernel_modules'
  45. consts['kernel_modules_src_dir'] = os.path.join(consts['root_dir'], consts['kernel_modules_subdir'])
  46. consts['userland_subdir'] = 'userland'
  47. consts['userland_src_dir'] = os.path.join(consts['root_dir'], consts['userland_subdir'])
  48. consts['userland_build_ext'] = '.out'
  49. consts['include_subdir'] = 'include'
  50. consts['include_src_dir'] = os.path.join(consts['root_dir'], consts['include_subdir'])
  51. consts['submodules_dir'] = os.path.join(consts['root_dir'], 'submodules')
  52. consts['buildroot_src_dir'] = os.path.join(consts['submodules_dir'], 'buildroot')
  53. consts['crosstool_ng_src_dir'] = os.path.join(consts['submodules_dir'], 'crosstool-ng')
  54. consts['crosstool_ng_supported_archs'] = set(['arm', 'aarch64'])
  55. consts['linux_src_dir'] = os.path.join(consts['submodules_dir'], 'linux')
  56. consts['linux_config_dir'] = os.path.join(consts['root_dir'], 'linux_config')
  57. consts['gem5_default_src_dir'] = os.path.join(consts['submodules_dir'], 'gem5')
  58. consts['rootfs_overlay_dir'] = os.path.join(consts['root_dir'], 'rootfs_overlay')
  59. consts['extract_vmlinux'] = os.path.join(consts['linux_src_dir'], 'scripts', 'extract-vmlinux')
  60. consts['qemu_src_dir'] = os.path.join(consts['submodules_dir'], 'qemu')
  61. consts['parsec_benchmark_src_dir'] = os.path.join(consts['submodules_dir'], 'parsec-benchmark')
  62. consts['ccache_dir'] = os.path.join('/usr', 'lib', 'ccache')
  63. consts['default_build_id'] = 'default'
  64. consts['arch_short_to_long_dict'] = collections.OrderedDict([
  65. ('x', 'x86_64'),
  66. ('a', 'arm'),
  67. ('A', 'aarch64'),
  68. ])
  69. consts['all_archs'] = [consts['arch_short_to_long_dict'][k] for k in consts['arch_short_to_long_dict']]
  70. consts['arch_choices'] = []
  71. for key in consts['arch_short_to_long_dict']:
  72. consts['arch_choices'].append(key)
  73. consts['arch_choices'].append(consts['arch_short_to_long_dict'][key])
  74. consts['default_arch'] = 'x86_64'
  75. consts['gem5_cpt_prefix'] = '^cpt\.'
  76. consts['sha'] = subprocess.check_output(['git', '-C', consts['root_dir'], 'log', '-1', '--format=%H']).decode().rstrip()
  77. consts['release_dir'] = os.path.join(consts['out_dir'], 'release')
  78. consts['release_zip_file'] = os.path.join(consts['release_dir'], 'lkmc-{}.zip'.format(consts['sha']))
  79. consts['github_repo_id'] = 'cirosantilli/linux-kernel-module-cheat'
  80. consts['asm_ext'] = '.S'
  81. consts['c_ext'] = '.c'
  82. consts['header_ext'] = '.h'
  83. consts['kernel_module_ext'] = '.ko'
  84. consts['obj_ext'] = '.o'
  85. consts['config_file'] = os.path.join(consts['data_dir'], 'config.py')
  86. consts['magic_fail_string'] = b'lkmc_test_fail'
  87. consts['baremetal_lib_basename'] = 'lib'
  88. class LkmcCliFunction(cli_function.CliFunction):
  89. '''
  90. Common functionality shared across our CLI functions:
  91. * command timing
  92. * some common flags, e.g.: --arch, --dry-run, --verbose
  93. '''
  94. def __init__(self, *args, **kwargs):
  95. kwargs['config_file'] = consts['config_file']
  96. super().__init__(*args, **kwargs)
  97. # Args for all scripts.
  98. self.add_argument(
  99. '-a', '--arch', choices=consts['arch_choices'], default=consts['default_arch'],
  100. help='CPU architecture.'
  101. )
  102. self.add_argument(
  103. '--dry-run',
  104. default=False,
  105. help='''\
  106. Print the commands that would be run, but don't run them.
  107. We aim display every command that modifies the filesystem state, and generate
  108. Bash equivalents even for actions taken directly in Python without shelling out.
  109. mkdir are generally omitted since those are obvious
  110. '''
  111. )
  112. self.add_argument(
  113. '-v', '--verbose', default=False,
  114. help='Show full compilation commands when they are not shown by default.'
  115. )
  116. # Gem5 args.
  117. self.add_argument(
  118. '--gem5-build-dir',
  119. help='''\
  120. Use the given directory as the gem5 build directory.
  121. '''
  122. )
  123. self.add_argument(
  124. '-M', '--gem5-build-id', default='default',
  125. help='''\
  126. gem5 build ID. Allows you to keep multiple separate gem5 builds.
  127. '''
  128. )
  129. self.add_argument(
  130. '--gem5-build-type', default='opt',
  131. help='gem5 build type, most often used for "debug" builds.'
  132. )
  133. self.add_argument(
  134. '--gem5-source-dir',
  135. help='''\
  136. Use the given directory as the gem5 source tree. Ignore `--gem5-worktree`.
  137. '''
  138. )
  139. self.add_argument(
  140. '-N', '--gem5-worktree',
  141. help='''\
  142. Create and use a git worktree of the gem5 submodule.
  143. See: https://github.com/cirosantilli/linux-kernel-module-cheat#gem5-worktree
  144. '''
  145. )
  146. # Linux kernel.
  147. self.add_argument(
  148. '-L', '--linux-build-id', default=consts['default_build_id'],
  149. help='''\
  150. Linux build ID. Allows you to keep multiple separate Linux builds.
  151. '''
  152. )
  153. self.add_argument(
  154. '--initramfs', default=False,
  155. )
  156. self.add_argument(
  157. '--initrd', default=False,
  158. )
  159. # Baremetal.
  160. self.add_argument(
  161. '-b', '--baremetal',
  162. help='''\
  163. Use the given baremetal executable instead of the Linux kernel.
  164. If the path is absolute, it is used as is.
  165. If the path is relative, we assume that it points to a source code
  166. inside baremetal/ and then try to use corresponding executable.
  167. '''
  168. )
  169. # Buildroot.
  170. self.add_argument(
  171. '--buildroot-build-id',
  172. default=consts['default_build_id'],
  173. help='Buildroot build ID. Allows you to keep multiple separate gem5 builds.'
  174. )
  175. self.add_argument(
  176. '--buildroot-linux', default=False,
  177. help='Boot with the Buildroot Linux kernel instead of our custom built one. Mostly for sanity checks.'
  178. )
  179. # crosstool-ng
  180. self.add_argument(
  181. '--crosstool-ng-build-id', default=consts['default_build_id'],
  182. help='Crosstool-NG build ID. Allows you to keep multiple separate crosstool-NG builds.'
  183. )
  184. self.add_argument(
  185. '--docker', default=False,
  186. help='''\
  187. Use the docker download Ubuntu root filesystem instead of the default Buildroot one.
  188. '''
  189. )
  190. self.add_argument(
  191. '--machine',
  192. help='''Machine type.
  193. QEMU default: virt
  194. gem5 default: VExpress_GEM5_V1
  195. See the documentation for other values known to work.
  196. '''
  197. )
  198. # QEMU.
  199. self.add_argument(
  200. '-Q', '--qemu-build-id', default=consts['default_build_id'],
  201. help='QEMU build ID. Allows you to keep multiple separate QEMU builds.'
  202. )
  203. # Userland.
  204. self.add_argument(
  205. '--userland-build-id', default=None
  206. )
  207. # Run.
  208. self.add_argument(
  209. '-n', '--run-id', default='0',
  210. help='''\
  211. ID for run outputs such as gem5's m5out. Allows you to do multiple runs,
  212. and then inspect separate outputs later in different output directories.
  213. '''
  214. )
  215. self.add_argument(
  216. '-P', '--prebuilt', default=False,
  217. help='''\
  218. Use prebuilt packaged host utilities as much as possible instead
  219. of the ones we built ourselves. Saves build time, but decreases
  220. the likelihood of incompatibilities.
  221. '''
  222. )
  223. self.add_argument(
  224. '--port-offset', type=int,
  225. help='''\
  226. Increase the ports to be used such as for GDB by an offset to run multiple
  227. instances in parallel. Default: the run ID (-n) if that is an integer, otherwise 0.
  228. '''
  229. )
  230. # Misc.
  231. self.add_argument(
  232. '-g', '--gem5', default=False,
  233. help='Use gem5 instead of QEMU.'
  234. )
  235. self.add_argument(
  236. '--qemu', default=False,
  237. help='''\
  238. Use QEMU as the emulator. This option exists in addition to --gem5
  239. to allow overriding configs from the CLI.
  240. '''
  241. )
  242. def _init_env(self, env):
  243. '''
  244. Update the kwargs from the command line with derived arguments.
  245. '''
  246. def join(*paths):
  247. return os.path.join(*paths)
  248. if env['qemu'] or not env['gem5']:
  249. env['emulator'] = 'qemu'
  250. else:
  251. env['emulator'] = 'gem5'
  252. if env['arch'] in env['arch_short_to_long_dict']:
  253. env['arch'] = env['arch_short_to_long_dict'][env['arch']]
  254. if env['userland_build_id'] is None:
  255. env['userland_build_id'] = env['default_build_id']
  256. env['userland_build_id_given'] = False
  257. else:
  258. env['userland_build_id_given'] = True
  259. if env['gem5_worktree'] is not None and env['gem5_build_id'] is None:
  260. env['gem5_build_id'] = env['gem5_worktree']
  261. env['is_arm'] = False
  262. if env['arch'] == 'arm':
  263. env['armv'] = 7
  264. env['gem5_arch'] = 'ARM'
  265. env['mcpu'] = 'cortex-a15'
  266. env['buildroot_toolchain_prefix'] = 'arm-buildroot-linux-uclibcgnueabihf'
  267. env['crosstool_ng_toolchain_prefix'] = 'arm-unknown-eabi'
  268. env['ubuntu_toolchain_prefix'] = 'arm-linux-gnueabihf'
  269. if env['emulator'] == 'gem5':
  270. if env['machine'] is None:
  271. env['machine'] = 'VExpress_GEM5_V1'
  272. else:
  273. if env['machine'] is None:
  274. env['machine'] = 'virt'
  275. env['is_arm'] = True
  276. elif env['arch'] == 'aarch64':
  277. env['armv'] = 8
  278. env['gem5_arch'] = 'ARM'
  279. env['mcpu'] = 'cortex-a57'
  280. env['buildroot_toolchain_prefix'] = 'aarch64-buildroot-linux-uclibc'
  281. env['crosstool_ng_toolchain_prefix'] = 'aarch64-unknown-elf'
  282. env['ubuntu_toolchain_prefix'] = 'aarch64-linux-gnu'
  283. if env['emulator'] == 'gem5':
  284. if env['machine'] is None:
  285. env['machine'] = 'VExpress_GEM5_V1'
  286. else:
  287. if env['machine'] is None:
  288. env['machine'] = 'virt'
  289. env['is_arm'] = True
  290. elif env['arch'] == 'x86_64':
  291. env['crosstool_ng_toolchain_prefix'] = 'x86_64-unknown-elf'
  292. env['gem5_arch'] = 'X86'
  293. env['buildroot_toolchain_prefix'] = 'x86_64-buildroot-linux-uclibc'
  294. env['ubuntu_toolchain_prefix'] = 'x86_64-linux-gnu'
  295. if env['emulator'] == 'gem5':
  296. if env['machine'] is None:
  297. env['machine'] = 'TODO'
  298. else:
  299. if env['machine'] is None:
  300. env['machine'] = 'pc'
  301. # Buildroot
  302. env['buildroot_build_dir'] = join(env['buildroot_out_dir'], 'build', env['buildroot_build_id'], env['arch'])
  303. env['buildroot_download_dir'] = join(env['buildroot_out_dir'], 'download')
  304. env['buildroot_config_file'] = join(env['buildroot_build_dir'], '.config')
  305. env['buildroot_build_build_dir'] = join(env['buildroot_build_dir'], 'build')
  306. env['buildroot_linux_build_dir'] = join(env['buildroot_build_build_dir'], 'linux-custom')
  307. env['buildroot_vmlinux'] = join(env['buildroot_linux_build_dir'], "vmlinux")
  308. env['host_dir'] = join(env['buildroot_build_dir'], 'host')
  309. env['host_bin_dir'] = join(env['host_dir'], 'usr', 'bin')
  310. env['buildroot_pkg_config'] = join(env['host_bin_dir'], 'pkg-config')
  311. env['buildroot_images_dir'] = join(env['buildroot_build_dir'], 'images')
  312. env['buildroot_rootfs_raw_file'] = join(env['buildroot_images_dir'], 'rootfs.ext2')
  313. env['buildroot_qcow2_file'] = env['buildroot_rootfs_raw_file'] + '.qcow2'
  314. env['staging_dir'] = join(env['out_dir'], 'staging', env['arch'])
  315. env['buildroot_staging_dir'] = join(env['buildroot_build_dir'], 'staging')
  316. env['target_dir'] = join(env['buildroot_build_dir'], 'target')
  317. env['linux_buildroot_build_dir'] = join(env['buildroot_build_build_dir'], 'linux-custom')
  318. # QEMU
  319. env['qemu_build_dir'] = join(env['out_dir'], 'qemu', env['qemu_build_id'])
  320. env['qemu_executable_basename'] = 'qemu-system-{}'.format(env['arch'])
  321. env['qemu_executable'] = join(env['qemu_build_dir'], '{}-softmmu'.format(env['arch']), env['qemu_executable_basename'])
  322. env['qemu_img_basename'] = 'qemu-img'
  323. env['qemu_img_executable'] = join(env['qemu_build_dir'], env['qemu_img_basename'])
  324. # gem5
  325. if env['gem5_build_dir'] is None:
  326. env['gem5_build_dir'] = join(env['gem5_out_dir'], env['gem5_build_id'], env['gem5_build_type'])
  327. env['gem5_fake_iso'] = join(env['gem5_out_dir'], 'fake.iso')
  328. env['gem5_m5term'] = join(env['gem5_build_dir'], 'm5term')
  329. env['gem5_build_build_dir'] = join(env['gem5_build_dir'], 'build')
  330. env['gem5_executable'] = join(env['gem5_build_build_dir'], env['gem5_arch'], 'gem5.{}'.format(env['gem5_build_type']))
  331. env['gem5_system_dir'] = join(env['gem5_build_dir'], 'system')
  332. # gem5 source
  333. if env['gem5_source_dir'] is not None:
  334. assert os.path.exists(env['gem5_source_dir'])
  335. else:
  336. if env['gem5_worktree'] is not None:
  337. env['gem5_source_dir'] = join(env['gem5_non_default_src_root_dir'], env['gem5_worktree'])
  338. else:
  339. env['gem5_source_dir'] = env['gem5_default_src_dir']
  340. env['gem5_m5_source_dir'] = join(env['gem5_source_dir'], 'util', 'm5')
  341. env['gem5_config_dir'] = join(env['gem5_source_dir'], 'configs')
  342. env['gem5_se_file'] = join(env['gem5_config_dir'], 'example', 'se.py')
  343. env['gem5_fs_file'] = join(env['gem5_config_dir'], 'example', 'fs.py')
  344. # crosstool-ng
  345. env['crosstool_ng_buildid_dir'] = join(env['crosstool_ng_out_dir'], 'build', env['crosstool_ng_build_id'])
  346. env['crosstool_ng_install_dir'] = join(env['crosstool_ng_buildid_dir'], 'install', env['arch'])
  347. env['crosstool_ng_bin_dir'] = join(env['crosstool_ng_install_dir'], 'bin')
  348. env['crosstool_ng_util_dir'] = join(env['crosstool_ng_buildid_dir'], 'util')
  349. env['crosstool_ng_config'] = join(env['crosstool_ng_util_dir'], '.config')
  350. env['crosstool_ng_defconfig'] = join(env['crosstool_ng_util_dir'], 'defconfig')
  351. env['crosstool_ng_executable'] = join(env['crosstool_ng_util_dir'], 'ct-ng')
  352. env['crosstool_ng_build_dir'] = join(env['crosstool_ng_buildid_dir'], 'build')
  353. env['crosstool_ng_download_dir'] = join(env['crosstool_ng_out_dir'], 'download')
  354. # run
  355. env['gem5_run_dir'] = join(env['run_dir_base'], 'gem5', env['arch'], str(env['run_id']))
  356. env['m5out_dir'] = join(env['gem5_run_dir'], 'm5out')
  357. env['stats_file'] = join(env['m5out_dir'], 'stats.txt')
  358. env['gem5_trace_txt_file'] = join(env['m5out_dir'], 'trace.txt')
  359. env['gem5_guest_terminal_file'] = join(env['m5out_dir'], 'system.terminal')
  360. env['gem5_readfile'] = join(env['gem5_run_dir'], 'readfile')
  361. env['gem5_termout_file'] = join(env['gem5_run_dir'], 'termout.txt')
  362. env['qemu_run_dir'] = join(env['run_dir_base'], 'qemu', env['arch'], str(env['run_id']))
  363. env['qemu_termout_file'] = join(env['qemu_run_dir'], 'termout.txt')
  364. env['qemu_trace_basename'] = 'trace.bin'
  365. env['qemu_trace_file'] = join(env['qemu_run_dir'], 'trace.bin')
  366. env['qemu_trace_txt_file'] = join(env['qemu_run_dir'], 'trace.txt')
  367. env['qemu_rrfile'] = join(env['qemu_run_dir'], 'rrfile')
  368. env['gem5_out_dir'] = join(env['out_dir'], 'gem5')
  369. # Ports
  370. if env['port_offset'] is None:
  371. try:
  372. env['port_offset'] = int(env['run_id'])
  373. except ValueError:
  374. env['port_offset'] = 0
  375. if env['emulator'] == 'gem5':
  376. env['gem5_telnet_port'] = 3456 + env['port_offset']
  377. env['gdb_port'] = 7000 + env['port_offset']
  378. else:
  379. env['qemu_base_port'] = 45454 + 10 * env['port_offset']
  380. env['qemu_monitor_port'] = env['qemu_base_port'] + 0
  381. env['qemu_hostfwd_generic_port'] = env['qemu_base_port'] + 1
  382. env['qemu_hostfwd_ssh_port'] = env['qemu_base_port'] + 2
  383. env['qemu_gdb_port'] = env['qemu_base_port'] + 3
  384. env['extra_serial_port'] = env['qemu_base_port'] + 4
  385. env['gdb_port'] = env['qemu_gdb_port']
  386. env['qemu_background_serial_file'] = join(env['qemu_run_dir'], 'background.log')
  387. # gem5 QEMU polymorphism.
  388. if env['emulator'] == 'gem5':
  389. env['executable'] = env['gem5_executable']
  390. env['run_dir'] = env['gem5_run_dir']
  391. env['termout_file'] = env['gem5_termout_file']
  392. env['guest_terminal_file'] = env['gem5_guest_terminal_file']
  393. env['trace_txt_file'] = env['gem5_trace_txt_file']
  394. else:
  395. env['executable'] = env['qemu_executable']
  396. env['run_dir'] = env['qemu_run_dir']
  397. env['termout_file'] = env['qemu_termout_file']
  398. env['guest_terminal_file'] = env['qemu_termout_file']
  399. env['trace_txt_file'] = env['qemu_trace_txt_file']
  400. env['run_cmd_file'] = join(env['run_dir'], 'run.sh')
  401. # Linux kernl.
  402. if 'linux_build_id' in env:
  403. env['linux_build_dir'] = join(env['out_dir'], 'linux', env['linux_build_id'], env['arch'])
  404. env['lkmc_vmlinux'] = join(env['linux_build_dir'], "vmlinux")
  405. if env['arch'] == 'arm':
  406. env['linux_arch'] = 'arm'
  407. env['linux_image_prefix'] = join('arch', env['linux_arch'], 'boot', 'zImage')
  408. elif env['arch'] == 'aarch64':
  409. env['linux_arch'] = 'arm64'
  410. env['linux_image_prefix'] = join('arch', env['linux_arch'], 'boot', 'Image')
  411. elif env['arch'] == 'x86_64':
  412. env['linux_arch'] = 'x86'
  413. env['linux_image_prefix'] = join('arch', env['linux_arch'], 'boot', 'bzImage')
  414. env['lkmc_linux_image'] = join(env['linux_build_dir'], env['linux_image_prefix'])
  415. env['buildroot_linux_image'] = join(env['buildroot_linux_build_dir'], env['linux_image_prefix'])
  416. if env['buildroot_linux']:
  417. env['vmlinux'] = env['buildroot_vmlinux']
  418. env['linux_image'] = env['buildroot_linux_image']
  419. else:
  420. env['vmlinux'] = env['lkmc_vmlinux']
  421. env['linux_image'] = env['lkmc_linux_image']
  422. # Kernel modules.
  423. env['kernel_modules_build_dir'] = join(env['kernel_modules_build_base_dir'], env['arch'])
  424. env['kernel_modules_build_subdir'] = join(env['kernel_modules_build_dir'], env['kernel_modules_subdir'])
  425. env['kernel_modules_build_host_dir'] = join(env['kernel_modules_build_base_dir'], 'host')
  426. env['kernel_modules_build_host_subdir'] = join(env['kernel_modules_build_host_dir'], env['kernel_modules_subdir'])
  427. env['userland_build_dir'] = join(env['out_dir'], 'userland', env['userland_build_id'], env['arch'])
  428. env['out_rootfs_overlay_dir'] = join(env['out_dir'], 'rootfs_overlay', env['arch'])
  429. env['out_rootfs_overlay_bin_dir'] = join(env['out_rootfs_overlay_dir'], 'bin')
  430. # Baremetal.
  431. env['baremetal_src_dir'] = join(env['root_dir'], 'baremetal')
  432. env['baremetal_src_lib_dir'] = join(env['baremetal_src_dir'], env['baremetal_lib_basename'])
  433. if env['emulator'] == 'gem5':
  434. env['simulator_name'] = 'gem5'
  435. else:
  436. env['simulator_name'] = 'qemu'
  437. env['baremetal_build_dir'] = join(env['out_dir'], 'baremetal', env['arch'], env['simulator_name'], env['machine'])
  438. env['baremetal_build_lib_dir'] = join(env['baremetal_build_dir'], env['baremetal_lib_basename'])
  439. env['baremetal_build_ext'] = '.elf'
  440. # Docker
  441. env['docker_build_dir'] = join(env['out_dir'], 'docker', env['arch'])
  442. env['docker_tar_dir'] = join(env['docker_build_dir'], 'export')
  443. env['docker_tar_file'] = join(env['docker_build_dir'], 'export.tar')
  444. env['docker_rootfs_raw_file'] = join(env['docker_build_dir'], 'export.ext2')
  445. env['docker_qcow2_file'] = join(env['docker_rootfs_raw_file'] + '.qcow2')
  446. if env['docker']:
  447. env['rootfs_raw_file'] = env['docker_rootfs_raw_file']
  448. env['qcow2_file'] = env['docker_qcow2_file']
  449. else:
  450. env['rootfs_raw_file'] = env['buildroot_rootfs_raw_file']
  451. env['qcow2_file'] = env['buildroot_qcow2_file']
  452. # Image.
  453. if env['baremetal'] is None:
  454. if env['emulator'] == 'gem5':
  455. env['image'] = env['vmlinux']
  456. env['disk_image'] = env['rootfs_raw_file']
  457. else:
  458. env['image'] = env['linux_image']
  459. env['disk_image'] = env['qcow2_file']
  460. else:
  461. env['disk_image'] = env['gem5_fake_iso']
  462. if env['baremetal'] == 'all':
  463. path = env['baremetal']
  464. else:
  465. path = self.resolve_executable(
  466. env['baremetal'],
  467. env['baremetal_src_dir'],
  468. env['baremetal_build_dir'],
  469. env['baremetal_build_ext'],
  470. )
  471. source_path_noext = os.path.splitext(join(
  472. env['baremetal_src_dir'],
  473. os.path.relpath(path, env['baremetal_build_dir'])
  474. ))[0]
  475. for ext in [c_ext, asm_ext]:
  476. source_path = source_path_noext + ext
  477. if os.path.exists(source_path):
  478. env['source_path'] = source_path
  479. break
  480. env['image'] = path
  481. self.env = env
  482. def assert_crosstool_ng_supports_arch(self, arch):
  483. if arch not in self.env['crosstool_ng_supported_archs']:
  484. raise Exception('arch not yet supported: ' + arch)
  485. @staticmethod
  486. def base64_encode(string):
  487. return base64.b64encode(string.encode()).decode()
  488. def gem_list_checkpoint_dirs(self):
  489. '''
  490. List checkpoint directory, oldest first.
  491. '''
  492. prefix_re = re.compile(self.env['gem5_cpt_prefix'])
  493. files = list(filter(lambda x: os.path.isdir(os.path.join(self.env['m5out_dir'], x)) and prefix_re.search(x), os.listdir(self.env['m5out_dir'])))
  494. files.sort(key=lambda x: os.path.getmtime(os.path.join(self.env['m5out_dir'], x)))
  495. return files
  496. def get_elf_entry(self, elf_file_path):
  497. readelf_header = subprocess.check_output([
  498. self.get_toolchain_tool('readelf'),
  499. '-h',
  500. elf_file_path
  501. ])
  502. for line in readelf_header.decode().split('\n'):
  503. split = line.split()
  504. if line.startswith(' Entry point address:'):
  505. addr = line.split()[-1]
  506. break
  507. return int(addr, 0)
  508. def get_stats(self, stat_re=None, stats_file=None):
  509. if stat_re is None:
  510. stat_re = '^system.cpu[0-9]*.numCycles$'
  511. if stats_file is None:
  512. stats_file = self.env['stats_file']
  513. stat_re = re.compile(stat_re)
  514. ret = []
  515. with open(stats_file, 'r') as statfile:
  516. for line in statfile:
  517. if line[0] != '-':
  518. cols = line.split()
  519. if len(cols) > 1 and stat_re.search(cols[0]):
  520. ret.append(cols[1])
  521. return ret
  522. def get_toolchain_prefix(self, tool, allowed_toolchains=None):
  523. buildroot_full_prefix = os.path.join(self.env['host_bin_dir'], self.env['buildroot_toolchain_prefix'])
  524. buildroot_exists = os.path.exists('{}-{}'.format(buildroot_full_prefix, tool))
  525. crosstool_ng_full_prefix = os.path.join(self.env['crosstool_ng_bin_dir'], self.env['crosstool_ng_toolchain_prefix'])
  526. crosstool_ng_exists = os.path.exists('{}-{}'.format(crosstool_ng_full_prefix, tool))
  527. host_tool = '{}-{}'.format(self.env['ubuntu_toolchain_prefix'], tool)
  528. host_path = shutil.which(host_tool)
  529. if host_path is not None:
  530. host_exists = True
  531. host_full_prefix = host_path[:-(len(tool)+1)]
  532. else:
  533. host_exists = False
  534. host_full_prefix = None
  535. known_toolchains = {
  536. 'crosstool-ng': (crosstool_ng_exists, crosstool_ng_full_prefix),
  537. 'buildroot': (buildroot_exists, buildroot_full_prefix),
  538. 'host': (host_exists, host_full_prefix),
  539. }
  540. if allowed_toolchains is None:
  541. if self.env['baremetal'] is None:
  542. allowed_toolchains = ['buildroot', 'crosstool-ng', 'host']
  543. else:
  544. allowed_toolchains = ['crosstool-ng', 'buildroot', 'host']
  545. tried = []
  546. for toolchain in allowed_toolchains:
  547. exists, prefix = known_toolchains[toolchain]
  548. tried.append('{}-{}'.format(prefix, tool))
  549. if exists:
  550. return prefix
  551. raise Exception('Tool not found. Tried:\n' + '\n'.join(tried))
  552. def get_toolchain_tool(self, tool, allowed_toolchains=None):
  553. return '{}-{}'.format(self.get_toolchain_prefix(tool, allowed_toolchains), tool)
  554. @staticmethod
  555. def github_make_request(
  556. authenticate=False,
  557. data=None,
  558. extra_headers=None,
  559. path='',
  560. subdomain='api',
  561. url_params=None,
  562. **extra_request_args
  563. ):
  564. if extra_headers is None:
  565. extra_headers = {}
  566. headers = {'Accept': 'application/vnd.github.v3+json'}
  567. headers.update(extra_headers)
  568. if authenticate:
  569. headers['Authorization'] = 'token ' + os.environ['LKMC_GITHUB_TOKEN']
  570. if url_params is not None:
  571. path += '?' + urllib.parse.urlencode(url_params)
  572. request = urllib.request.Request(
  573. 'https://' + subdomain + '.github.com/repos/' + github_repo_id + path,
  574. headers=headers,
  575. data=data,
  576. **extra_request_args
  577. )
  578. response_body = urllib.request.urlopen(request).read().decode()
  579. if response_body:
  580. _json = json.loads(response_body)
  581. else:
  582. _json = {}
  583. return _json
  584. @staticmethod
  585. def log_error(msg):
  586. print('error: {}'.format(msg), file=sys.stderr)
  587. def main(self, **kwargs):
  588. '''
  589. Time the main of the derived class.
  590. '''
  591. if not kwargs['dry_run']:
  592. start_time = time.time()
  593. kwargs.update(consts)
  594. self._init_env(kwargs)
  595. self.sh = shell_helpers.ShellHelpers(dry_run=self.env['dry_run'])
  596. ret = self.timed_main()
  597. if not kwargs['dry_run']:
  598. end_time = time.time()
  599. self.print_time(end_time - start_time)
  600. return ret
  601. def make_build_dirs(self):
  602. os.makedirs(self.env['buildroot_build_build_dir'], exist_ok=True)
  603. os.makedirs(self.env['gem5_build_dir'], exist_ok=True)
  604. os.makedirs(self.env['out_rootfs_overlay_dir'], exist_ok=True)
  605. def make_run_dirs(self):
  606. '''
  607. Make directories required for the run.
  608. The user could nuke those anytime between runs to try and clean things up.
  609. '''
  610. os.makedirs(self.env['gem5_run_dir'], exist_ok=True)
  611. os.makedirs(self.env['p9_dir'], exist_ok=True)
  612. os.makedirs(self.env['qemu_run_dir'], exist_ok=True)
  613. @staticmethod
  614. def need_rebuild(srcs, dst):
  615. if not os.path.exists(dst):
  616. return True
  617. for src in srcs:
  618. if os.path.getmtime(src) > os.path.getmtime(dst):
  619. return True
  620. return False
  621. @staticmethod
  622. def print_time(ellapsed_seconds):
  623. hours, rem = divmod(ellapsed_seconds, 3600)
  624. minutes, seconds = divmod(rem, 60)
  625. print("time {:02}:{:02}:{:02}".format(int(hours), int(minutes), int(seconds)))
  626. def raw_to_qcow2(eslf, prebuilt=False, reverse=False):
  627. if prebuilt or not os.path.exists(self.env['qemu_img_executable']):
  628. disable_trace = []
  629. qemu_img_executable = self.env['qemu_img_basename']
  630. else:
  631. # Prevent qemu-img from generating trace files like QEMU. Disgusting.
  632. disable_trace = ['-T', 'pr_manager_run,file=/dev/null', LF,]
  633. qemu_img_executable = self.env['qemu_img_executable']
  634. infmt = 'raw'
  635. outfmt = 'qcow2'
  636. infile = self.env['rootfs_raw_file']
  637. outfile = self.env['qcow2_file']
  638. if reverse:
  639. tmp = infmt
  640. infmt = outfmt
  641. outfmt = tmp
  642. tmp = infile
  643. infile = outfile
  644. outfile = tmp
  645. self.sh.run_cmd(
  646. [
  647. qemu_img_executable, LF,
  648. ] +
  649. disable_trace +
  650. [
  651. 'convert', LF,
  652. '-f', infmt, LF,
  653. '-O', outfmt, LF,
  654. infile, LF,
  655. outfile, LF,
  656. ]
  657. )
  658. @staticmethod
  659. def resolve_args(defaults, args, extra_args):
  660. if extra_args is None:
  661. extra_args = {}
  662. argcopy = copy.copy(args)
  663. argcopy.__dict__ = dict(list(defaults.items()) + list(argcopy.__dict__.items()) + list(extra_args.items()))
  664. return argcopy
  665. @staticmethod
  666. def resolve_executable(in_path, magic_in_dir, magic_out_dir, out_ext):
  667. if os.path.isabs(in_path):
  668. return in_path
  669. else:
  670. paths = [
  671. os.path.join(magic_out_dir, in_path),
  672. os.path.join(
  673. magic_out_dir,
  674. os.path.relpath(in_path, magic_in_dir),
  675. )
  676. ]
  677. paths[:] = [os.path.splitext(path)[0] + out_ext for path in paths]
  678. for path in paths:
  679. if os.path.exists(path):
  680. return path
  681. raise Exception('Executable file not found. Tried:\n' + '\n'.join(paths))
  682. def resolve_userland(self, path):
  683. return self.resolve_executable(
  684. path,
  685. self.env['userland_src_dir'],
  686. self.env['userland_build_dir'],
  687. self.env['userland_build_ext'],
  688. )
  689. def timed_main(self):
  690. '''
  691. Main action of the derived class.
  692. '''
  693. raise NotImplementedError()
  694. class BuildCliFunction(LkmcCliFunction):
  695. '''
  696. A CLI function with common facilities to build stuff, e.g.:
  697. * `--clean` to clean the build directory
  698. * `--nproc` to set he number of build threads
  699. '''
  700. def __init__(self, *args, **kwargs):
  701. super().__init__(*args, **kwargs)
  702. self.add_argument(
  703. '--clean',
  704. default=False,
  705. help='Clean the build instead of building.',
  706. ),
  707. self.add_argument(
  708. '-j',
  709. '--nproc',
  710. default=multiprocessing.cpu_count(),
  711. type=int,
  712. help='Number of processors to use for the build.',
  713. )
  714. def clean(self):
  715. build_dir = self.get_build_dir()
  716. if build_dir is not None:
  717. self.sh.rmrf(build_dir)
  718. def build(self):
  719. '''
  720. Do the actual main build work.
  721. '''
  722. raise NotImplementedError()
  723. def get_build_dir(self):
  724. return None
  725. def timed_main(self):
  726. '''
  727. Parse CLI, and to the build based on it.
  728. The actual build work is done by do_build in implementing classes.
  729. '''
  730. if self.env['clean']:
  731. return self.clean()
  732. else:
  733. return self.build()