run 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014
  1. #!/usr/bin/env python3
  2. import os
  3. import re
  4. import shlex
  5. import shutil
  6. import struct
  7. import subprocess
  8. import sys
  9. import time
  10. import common
  11. from shell_helpers import LF
  12. class Main(common.LkmcCliFunction):
  13. def __init__(self):
  14. super().__init__(
  15. description='''\
  16. Run some content on an emulator.
  17. '''
  18. )
  19. self.add_argument(
  20. '-c',
  21. '--cpus',
  22. default=1,
  23. type=int,
  24. help='Number of guest CPUs to emulate. Default: %(default)s'
  25. )
  26. self.add_argument(
  27. '--ctrl-c-host',
  28. default=False,
  29. help='''\
  30. Ctrl + C kills the QEMU simulator instead of being passed to the guest.
  31. '''
  32. )
  33. self.add_argument(
  34. '-D',
  35. '--debug-vm',
  36. default=False,
  37. help='''\
  38. Run GDB on the emulator itself.
  39. For --emulator native, this debugs the target program.
  40. See also: https://cirosantilli.com/linux-kernel-module-cheat#debug-the-emulator
  41. '''
  42. )
  43. self.add_argument(
  44. '--debug-vm-args',
  45. default='',
  46. help='''\
  47. Pass arguments to GDB. Implies --debug-vm. If --debug-vm-rr is used,
  48. pass the given arguments to GDB on the replay.
  49. '''
  50. )
  51. self.add_argument(
  52. '--debug-vm-file',
  53. help='''\
  54. Like --debug-vm, but also source this file. Equivalent to
  55. --debug-vm-args='-ex "source $file"'.
  56. '''
  57. )
  58. self.add_argument(
  59. '--debug-vm-ex',
  60. default='',
  61. help='''\
  62. Like --debug-vm-args, but takes as argument a semicolon separated list of
  63. -ex commands.
  64. '''
  65. )
  66. self.add_argument(
  67. '--debug-vm-rr',
  68. default=False,
  69. help='''
  70. Run the emulator through Mozilla RR, and then start replay with reverse debugging enabled:
  71. https://cirosantilli.com/linux-kernel-module-cheat#reverse-debug-the-emulator
  72. '''
  73. )
  74. self.add_argument(
  75. '--dtb',
  76. help='''\
  77. Use the specified DTB file. If not given, let the emulator generate a DTB for us,
  78. which is what you usually want.
  79. '''
  80. )
  81. self.add_argument(
  82. '-E',
  83. '--eval',
  84. help='''\
  85. Replace the normal init with a minimal init that just evals the given sh string.
  86. See: https://cirosantilli.com/linux-kernel-module-cheat#replace-init
  87. chdir into lkmc_home before running the command:
  88. https://cirosantilli.com/linux-kernel-module-cheat#lkmc-home
  89. '''
  90. )
  91. self.add_argument(
  92. '-F',
  93. '--eval-after',
  94. help='''\
  95. Similar to --eval, but the string gets evaled at the last init script,
  96. after the normal init finished. After this string is evaled, you are left
  97. inside a shell. See: https://cirosantilli.com/linux-kernel-module-cheat#init-busybox
  98. '''
  99. )
  100. self.add_argument(
  101. '-G',
  102. '--gem5-exe-args',
  103. default='',
  104. help='''\
  105. Pass extra options to the gem5 executable.
  106. Do not confuse with the arguments passed to config scripts,
  107. like `fs.py`. Example:
  108. ./run --emulator gem5 --gem5-exe-args '--debug-flags=Exec --debug' -- --cpu-type=HPI --caches
  109. will run:
  110. gem.op5 --debug-flags=Exec fs.py --cpu-type=HPI --caches
  111. '''
  112. )
  113. self.add_argument(
  114. '--gdb',
  115. default=False,
  116. help='''\
  117. Shortcut for the most common GDB options that you want most of the time. Implies:
  118. * --gdb-wait
  119. * --tmux-args <main> where <main> is:
  120. ** start_kernel in full system
  121. ** main in user mode
  122. * --tmux-program gdb
  123. '''
  124. )
  125. self.add_argument(
  126. '--gdb-wait',
  127. default=False,
  128. help='''\
  129. Wait for GDB to connect before starting execution
  130. See: https://cirosantilli.com/linux-kernel-module-cheat#gdb
  131. '''
  132. )
  133. self.add_argument(
  134. '--gem5-script',
  135. default='fs',
  136. choices=['fs', 'biglittle'],
  137. help='''\
  138. Which gem5 script to use.
  139. See e.g.: https://cirosantilli.com/linux-kernel-module-cheat#gem5-fs-biglittle
  140. '''
  141. )
  142. self.add_argument(
  143. '--gem5-readfile',
  144. default='',
  145. help='''\
  146. Set the contents of m5 readfile to this string.
  147. https://cirosantilli.com/linux-kernel-module-cheat#gem5-restore-new-script
  148. '''
  149. )
  150. self.add_argument(
  151. '--gem5-restore',
  152. type=int,
  153. help='''\
  154. Restore the nth most recently taken gem5 checkpoint according to directory
  155. timestamps.
  156. '''
  157. )
  158. self.add_argument(
  159. '--graphic',
  160. default=False,
  161. help='''\
  162. Run in graphic mode.
  163. See: http://github.com/cirosantilli/linux-kernel-module-cheat#graphics
  164. '''
  165. )
  166. self.add_argument(
  167. '--kdb',
  168. default=False,
  169. help='''\
  170. Setup KDB kernel CLI options.
  171. See: http://github.com/cirosantilli/linux-kernel-module-cheat#kdb
  172. '''
  173. )
  174. self.add_argument(
  175. '--kernel-cli',
  176. help='''\
  177. Pass an extra Linux kernel command line options, and place them before
  178. the dash separator `-`. Only options that come before the `-`, i.e.
  179. "standard" options, should be passed with this option.
  180. Example: `./run --arch arm --kernel-cli 'init=/lkmc/poweroff.out'`
  181. See also: https://cirosantilli.com/linux-kernel-module-cheat#init-environment
  182. '''
  183. )
  184. self.add_argument(
  185. '--kernel-cli-after-dash',
  186. help='''\
  187. Pass an extra Linux kernel command line options, add a dash `-`
  188. separator, and place the options after the dash. Intended for custom
  189. options understood by our `init` scripts, most of which are prefixed
  190. by `lkmc_`.
  191. Example: `./run --kernel-cli-after-dash 'lkmc_eval="wget google.com" lkmc_lala=y'`
  192. See also: https://cirosantilli.com/linux-kernel-module-cheat#init-environment
  193. '''
  194. )
  195. self.add_argument(
  196. '--kernel-version',
  197. default=common.consts['linux_kernel_version'],
  198. help='''\
  199. Pass a base64 encoded command line parameter that gets evalled at the end of
  200. the normal init.
  201. See: https://cirosantilli.com/linux-kernel-module-cheat#init-busybox
  202. chdir into lkmc_home before running the command:
  203. https://cirosantilli.com/linux-kernel-module-cheat#lkmc-home
  204. Specify the Linux kernel version to be reported by syscall emulation.
  205. Defaults to the same kernel version as our default Buildroot build.
  206. Currently only works for QEMU.
  207. See: http://github.com/cirosantilli/linux-kernel-module-cheat#fatal-kernel-too-old
  208. '''
  209. )
  210. self.add_argument(
  211. '--kgdb',
  212. default=False,
  213. help='''\
  214. Setup KGDB kernel CLI options.
  215. See: http://github.com/cirosantilli/linux-kernel-module-cheat#kgdb
  216. '''
  217. )
  218. self.add_argument(
  219. '-K',
  220. '--kvm',
  221. default=False,
  222. help='''\
  223. Use KVM. Only works if guest arch == host arch.
  224. See: http://github.com/cirosantilli/linux-kernel-module-cheat#kvm
  225. '''
  226. )
  227. self.add_argument(
  228. '-m',
  229. '--memory',
  230. default='256M',
  231. help='''\
  232. Set the memory size of the guest. E.g.: `--memory 512M`. We try to keep the default
  233. at the minimal ammount amount that boots all archs. Anything lower could lead
  234. some arch to fail to boot.
  235. Default: %(default)s
  236. '''
  237. )
  238. self.add_argument(
  239. '--quit-after-boot',
  240. default=False,
  241. help='''\
  242. Setup a kernel init parameter that makes the emulator quit immediately after boot.
  243. '''
  244. )
  245. self.add_argument(
  246. '--replay',
  247. default=False,
  248. help='Replay a QEMU run record deterministically'
  249. )
  250. self.add_argument(
  251. '--record',
  252. default=False,
  253. help='Record a QEMU run record for later replay with `-R`'
  254. )
  255. self.add_argument(
  256. '--show-stdout',
  257. default=True,
  258. help='''Show emulator stdout and stderr on the host terminal.'''
  259. )
  260. self.add_argument(
  261. '--stdin-file',
  262. help='''\
  263. Set the given file as the stdin source of the emulator. QEMU and gem5 then
  264. forward this to the guest in user mode simulation.
  265. https://cirosantilli.com/linux-kernel-module-cheat#syscall-emulation-mode-program-stdin
  266. '''
  267. )
  268. self.add_argument(
  269. '--terminal',
  270. default=False,
  271. help='''\
  272. Output directly to the terminal, don't pipe to tee as the default.
  273. With this, we don't not save the output to a file as is done by default,
  274. but we are able to do things that require not having a pipe such as
  275. using debuggers. This option is set automatically by --debug-vm, but you
  276. still need it to debug gem5 Python scripts with pdb.
  277. '''
  278. )
  279. self.add_argument(
  280. '-T',
  281. '--trace',
  282. help='''\
  283. Set trace events to be enabled. If not given, gem5 tracing is completely
  284. disabled, while QEMU tracing is enabled but uses default traces that are very
  285. rare and don't affect performance, because `./configure
  286. --enable-trace-backends=simple` seems to enable some traces by default, e.g.
  287. `pr_manager_run`, and I don't know how to get rid of them.
  288. See: http://github.com/cirosantilli/linux-kernel-module-cheat#tracing
  289. '''
  290. )
  291. self.add_argument(
  292. '--trace-stdout',
  293. default=False,
  294. help='''\
  295. Output trace to stdout instead of a file. Only works for gem5 currently.
  296. '''
  297. )
  298. self.add_argument(
  299. '--trace-insts-stdout',
  300. default=False,
  301. help='''\
  302. Trace instructions run to stdout. Shortcut for --trace --trace-stdout.
  303. '''
  304. )
  305. self.add_argument(
  306. '-t',
  307. '--tmux',
  308. default=False,
  309. help='''\
  310. Create a tmux split the window. You must already be inside of a `tmux` session
  311. to use this option:
  312. * on the main window, run the emulator as usual
  313. * on the split:
  314. ** if on QEMU and `-d` is given, GDB
  315. ** if on gem5, the gem5 terminal
  316. See: https://cirosantilli.com/linux-kernel-module-cheat#tmux
  317. '''
  318. )
  319. self.add_argument(
  320. '--tmux-args',
  321. help='''\
  322. Parameters to pass to the program running on the tmux split. Implies --tmux.
  323. '''
  324. )
  325. self.add_argument(
  326. '--tmux-program',
  327. choices=('gdb', 'shell'),
  328. help='''\
  329. Which program to run in tmux. Implies --tmux. Defaults:
  330. * 'gdb' in qemu
  331. * 'shell' in gem5. 'shell' is only supported in gem5 currently.
  332. '''
  333. )
  334. self.add_argument(
  335. '--vnc',
  336. default=False,
  337. help='''\
  338. Run QEMU with VNC instead of the default SDL. Connect to it with:
  339. `vinagre localhost:5900`.
  340. '''
  341. )
  342. self.add_argument(
  343. 'extra_emulator_args',
  344. nargs='*',
  345. default=[],
  346. help='''\
  347. Extra options to append at the end of the emulator command line.
  348. '''
  349. )
  350. def timed_main(self):
  351. show_stdout = self.env['show_stdout']
  352. # Common qemu / gem5 logic.
  353. # nokaslr:
  354. # * https://unix.stackexchange.com/questions/397939/turning-off-kaslr-to-debug-linux-kernel-using-qemu-and-gdb
  355. # * https://stackoverflow.com/questions/44612822/unable-to-debug-kernel-with-qemu-gdb/49840927#49840927
  356. # Turned on by default since v4.12
  357. kernel_cli = 'console_msg_format=syslog nokaslr norandmaps panic=-1 printk.devkmsg=on printk.time=y rw'
  358. if self.env['kernel_cli'] is not None:
  359. kernel_cli += ' {}'.format(self.env['kernel_cli'])
  360. if self.env['quit_after_boot']:
  361. kernel_cli += ' {}'.format(self.env['quit_init'])
  362. kernel_cli_after_dash = ' lkmc_home={}'.format(self.env['guest_lkmc_home'])
  363. extra_emulator_args = []
  364. extra_qemu_args = []
  365. if not self.env['_args_given']['tmux_program']:
  366. if self.env['emulator'] == 'qemu':
  367. self.env['tmux_program'] = 'gdb'
  368. elif self.env['emulator'] == 'gem5':
  369. self.env['tmux_program'] = 'shell'
  370. if self.env['gdb']:
  371. if not self.env['_args_given']['gdb_wait']:
  372. self.env['gdb_wait'] = True
  373. if not self.env['_args_given']['tmux_args']:
  374. if not self.env['userland'] and not self.env['baremetal']:
  375. self.env['tmux_args'] = 'start_kernel'
  376. else:
  377. self.env['tmux_args'] = 'main'
  378. if not self.env['_args_given']['tmux_program']:
  379. self.env['tmux_program'] = 'gdb'
  380. if self.env['tmux_args'] is not None or self.env['_args_given']['tmux_program']:
  381. self.env['tmux'] = True
  382. if self.env['debug_vm_rr']:
  383. debug_vm = ['rr', 'record']
  384. elif self.env['debug_vm'] or self.env['debug_vm_args'] or self.env['debug_vm_file'] or self.env['debug_vm_ex']:
  385. debug_vm = ['gdb', LF, '-q', LF] + self.sh.shlex_split(self.env['debug_vm_args'])
  386. for cmd in self.env['debug_vm_ex'].split(';'):
  387. debug_vm.extend(['-ex', cmd, LF]);
  388. if self.env['debug_vm_file'] is not None:
  389. debug_vm.extend(['-ex', 'source {}'.format(self.env['debug_vm_file']), LF])
  390. debug_vm.extend(['--args', LF])
  391. else:
  392. debug_vm = []
  393. if self.env['gdb_wait']:
  394. extra_qemu_args.extend(['-S', LF])
  395. if self.env['eval_after'] is not None:
  396. kernel_cli_after_dash += ' lkmc_eval_base64="{}"'.format(self.sh.base64_encode(self.env['eval_after']))
  397. if self.env['kernel_cli_after_dash'] is not None:
  398. kernel_cli_after_dash += ' {}'.format(self.env['kernel_cli_after_dash'])
  399. if self.env['vnc']:
  400. vnc = ['-vnc', ':0', LF]
  401. else:
  402. vnc = []
  403. if self.env['eval'] is not None:
  404. kernel_cli += ' {}=/lkmc/eval_base64.sh'.format(self.env['initarg'])
  405. kernel_cli_after_dash += ' lkmc_eval="{}"'.format(self.sh.base64_encode(self.env['eval']))
  406. if not self.env['graphic']:
  407. extra_qemu_args.extend(['-nographic', LF])
  408. console = None
  409. console_type = None
  410. console_count = 0
  411. if self.env['arch'] == 'x86_64':
  412. console_type = 'ttyS'
  413. elif self.env['is_arm']:
  414. console_type = 'ttyAMA'
  415. console = '{}{}'.format(console_type, console_count)
  416. console_count += 1
  417. if not (self.env['arch'] == 'x86_64' and self.env['graphic']):
  418. kernel_cli += ' console={}'.format(console)
  419. extra_console = '{}{}'.format(console_type, console_count)
  420. console_count += 1
  421. if self.env['kdb'] or self.env['kgdb']:
  422. kernel_cli += ' kgdbwait'
  423. if self.env['kdb']:
  424. if self.env['graphic']:
  425. kdb_cmd = 'kbd,'
  426. else:
  427. kdb_cmd = ''
  428. kernel_cli += ' kgdboc={}{},115200'.format(kdb_cmd, console)
  429. if self.env['kgdb']:
  430. kernel_cli += ' kgdboc={},115200'.format(extra_console)
  431. if kernel_cli_after_dash:
  432. kernel_cli += " -{}".format(kernel_cli_after_dash)
  433. extra_env = {}
  434. if self.env['trace_insts_stdout']:
  435. if self.env['emulator'] == 'qemu':
  436. extra_emulator_args.extend(['-d', 'in_asm', LF])
  437. elif self.env['emulator'] == 'gem5':
  438. self.env['trace_stdout'] = True
  439. self.env['trace'] = 'ExecAll'
  440. if self.env['trace'] is None:
  441. do_trace = False
  442. # A dummy value that is already turned on by default and does not produce large output,
  443. # just to prevent QEMU from emitting a warning that '' is not valid.
  444. trace_type = 'load_file'
  445. else:
  446. do_trace = True
  447. trace_type = self.env['trace']
  448. def raise_rootfs_not_found():
  449. if not self.env['dry_run']:
  450. raise Exception('Root filesystem not found. Did you build it? ' \
  451. 'Tried to use: ' + self.env['disk_image'])
  452. def raise_image_not_found():
  453. if not self.env['dry_run']:
  454. raise Exception('Executable image not found. Did you build it? ' \
  455. 'Tried to use: ' + self.env['image'])
  456. cmd = debug_vm.copy()
  457. if not os.path.exists(self.env['image']):
  458. if self.env['emulator'] == 'gem5':
  459. if (
  460. not self.env['baremetal'] and
  461. not self.env['userland']
  462. ):
  463. # This is an attempte to run gem5 from a prebuilt download
  464. # but it is not working:
  465. # https://github.com/cirosantilli/linux-kernel-module-cheat/issues/79
  466. self.sh.check_output(
  467. [
  468. self.env['extract_vmlinux'],
  469. self.env['linux_image']
  470. ],
  471. out_file=self.env['image'],
  472. show_cmd=True,
  473. show_stdout=False
  474. )
  475. else:
  476. raise_image_not_found()
  477. else:
  478. raise_image_not_found()
  479. if self.env['baremetal']:
  480. # Setup CLI arguments into a single raw binary file to be loaded into memory.
  481. # The memory setup of that file is:
  482. # argc
  483. # argv[0] pointer
  484. # argv[1] pointer
  485. # ...
  486. # argv[N] pointer
  487. # argv[0][0] data
  488. # argv[0][1] data
  489. # ...
  490. # argv[1][0] data
  491. # argv[1][1] data
  492. # ...
  493. cli_args = [os.path.basename(self.env['image'])]
  494. if self.env['cli_args'] is not None:
  495. cli_args.extend(shlex.split(self.env['cli_args']))
  496. argc_addr = self.env['entry_address'] + self.env['baremetal_max_text_size'] + self.env['baremetal_memory_size']
  497. argv_addr = argc_addr + self.env['int_size']
  498. argv_data_addr = argv_addr + len(cli_args) * self.env['address_size']
  499. argv_addr_data = []
  500. argv_addr_cur = argv_data_addr
  501. for arg in cli_args:
  502. argv_addr_data.append(struct.pack(
  503. '<{}'.format(
  504. self.python_struct_int_format(self.env['address_size'])
  505. ),
  506. argv_addr_cur
  507. ))
  508. argv_addr_cur += len(arg) + 1
  509. baremetal_cli_path = os.path.join(self.env['run_dir'], 'baremetal_cli.raw')
  510. if self.env['emulator'] == 'qemu':
  511. self.make_run_dirs()
  512. with open(baremetal_cli_path, 'wb') as f:
  513. f.write(struct.pack('<{}'.format(self.python_struct_int_format(self.env['int_size'])), len(cli_args)))
  514. f.write(b''.join(argv_addr_data))
  515. f.write(b'\0'.join(arg.encode() for arg in cli_args) + b'\0')
  516. use_disk_image = self.env['disk_image'] is not None and \
  517. os.path.exists(self.env['disk_image']) or \
  518. not self.env['baremetal']
  519. if self.env['_args_given']['disk_image'] and not os.path.exists(self.env['disk_image']) :
  520. raise_rootfs_not_found()
  521. if self.env['emulator'] == 'gem5':
  522. if self.env['quiet']:
  523. show_stdout = False
  524. if not self.env['baremetal'] and not self.env['userland']:
  525. if not os.path.exists(self.env['rootfs_raw_file']):
  526. if not os.path.exists(self.env['qcow2_file']):
  527. raise_rootfs_not_found()
  528. self.raw_to_qcow2(qemu_which=self.env['qemu_which'], reverse=True)
  529. os.makedirs(os.path.dirname(self.env['gem5_readfile_file']), exist_ok=True)
  530. self.sh.write_string_to_file(self.env['gem5_readfile_file'], self.env['gem5_readfile'])
  531. memory = '{}B'.format(self.env['memory'])
  532. gem5_exe_args = self.sh.shlex_split(self.env['gem5_exe_args'])
  533. if do_trace:
  534. gem5_exe_args.extend(['--debug-flags', trace_type, LF])
  535. # https://cirosantilli.com/linux-kernel-module-cheat#m5-override-py-source
  536. extra_env['M5_OVERRIDE_PY_SOURCE'] = 'true'
  537. if self.env['trace_stdout']:
  538. debug_file = 'cout'
  539. else:
  540. debug_file = 'trace.txt'
  541. cmd.extend(
  542. [
  543. self.env['executable'], LF,
  544. '--debug-file', debug_file, LF,
  545. '--listener-mode', 'on', LF,
  546. '--outdir', self.env['m5out_dir'], LF,
  547. ] +
  548. gem5_exe_args
  549. )
  550. if self.env['gem5_restore'] is not None:
  551. # https://cirosantilli.com/linux-kernel-module-cheat#gem5-checkpoint-internals
  552. cpt_dirs = self.gem5_list_checkpoint_dirs()
  553. cpt_dir = cpt_dirs[-self.env['gem5_restore']]
  554. cpt_dirs_sorted_by_tick = sorted(cpt_dirs, key=lambda x: int(x.split('.')[1]))
  555. extra_emulator_args.extend(['-r', str(cpt_dirs_sorted_by_tick.index(cpt_dir) + 1)])
  556. if self.env['userland']:
  557. cmd_opt = self.env['image']
  558. for u in self.env['userland'][1:]:
  559. cmd_opt += ';' + self.resolve_userland_executable(u)
  560. if len(self.env['userland']) > 1:
  561. workload_cpus = ':'
  562. else:
  563. workload_cpus = '0'
  564. cmd.extend([
  565. self.env['gem5_se_file'], LF,
  566. '--cmd', cmd_opt, LF,
  567. '--num-cpus', str(self.env['cpus']), LF,
  568. # We have to use cpu[0] here because on multi-cpu workloads,
  569. # cpu[1] and higher use workload as a proxy to cpu[0].workload.
  570. # as can be seen from the config.ini.
  571. # If system.cpu[:].workload[:] were used instead, we would get the error:
  572. # "KeyError: 'workload'"
  573. '--param', 'system.cpu[{}].workload[:].release = "{}"'.format(workload_cpus,self.env['kernel_version']), LF,
  574. ])
  575. if self.env['cli_args'] is not None:
  576. cmd.extend(['--options', self.env['cli_args'], LF])
  577. if not self.env['static']:
  578. for path in self.env['userland_library_redirects']:
  579. cmd.extend([
  580. '--redirects',
  581. '{}={}'.format(
  582. os.sep + path,
  583. os.path.join(self.env['userland_library_dir'], path)
  584. ),
  585. LF
  586. ])
  587. cmd.extend(['--interp-dir', self.env['userland_library_dir'], LF])
  588. else:
  589. if self.env['is_arm']:
  590. arm_kernel_cli = 'earlycon=pl011,0x1c090000 earlyprintk=pl011,0x1c090000 lpj=19988480 rw loglevel=8 mem={}'.format(memory)
  591. if self.env['gem5_script'] == 'fs':
  592. cmd.extend([
  593. self.env['gem5_fs_file'], LF,
  594. '--kernel', self.env['image'], LF,
  595. '--num-cpus', str(self.env['cpus']), LF,
  596. '--script', self.env['gem5_readfile_file'], LF,
  597. ])
  598. if use_disk_image:
  599. cmd.extend(['--disk-image', self.env['disk_image'], LF])
  600. if os.path.exists(self.env['disk_image_2']):
  601. cmd.extend(['--disk-image', self.env['disk_image_2'], LF])
  602. if self.env['baremetal']:
  603. cmd.extend([
  604. '--param', 'system.workload.extras = "{}"'.format(self.python_escape_double_quotes(baremetal_cli_path)), LF,
  605. '--param', 'system.workload.extras_addrs = {}'.format(hex(argc_addr)), LF,
  606. ])
  607. if self.env['arch'] == 'x86_64':
  608. if self.env['kvm']:
  609. cmd.extend(['--cpu-type', 'X86KvmCPU', LF])
  610. if not self.env['baremetal']:
  611. cmd.extend(['--command-line', 'earlycon={} earlyprintk={} lpj=7999923 root=/dev/sda {}'.format(console, console, kernel_cli), LF])
  612. elif self.env['is_arm']:
  613. if self.env['kvm']:
  614. cmd.extend(['--cpu-type', 'ArmV8KvmCPU', LF])
  615. if self.env['dp650']:
  616. dp650_cmd = 'dpu_'
  617. else:
  618. dp650_cmd = ''
  619. cmd.extend([
  620. '--machine-type', self.env['machine'], LF,
  621. ])
  622. if not self.env['baremetal']:
  623. cmd.extend([
  624. '--command-line',
  625. # TODO why is it mandatory to pass mem= here? Not true for QEMU.
  626. # Anything smaller than physical blows up as expected, but why can't it auto-detect the right value?
  627. 'root=/dev/sda {} {}'.format(arm_kernel_cli, kernel_cli), LF
  628. ])
  629. cmd.extend(['--param', 'system.workload.panic_on_panic = True', LF])
  630. dtb = None
  631. if self.env['dtb'] is not None:
  632. dtb = self.env['dtb']
  633. elif self.env['dp650']:
  634. dtb = os.path.join(
  635. self.env['gem5_system_dir'],
  636. 'arm',
  637. 'dt',
  638. 'armv{}_gem5_v1_{}{}cpu.dtb'.format(
  639. self.env['armv'],
  640. dp650_cmd,
  641. self.env['cpus']
  642. )
  643. )
  644. if dtb is not None:
  645. cmd.extend(['--dtb-filename', dtb, LF])
  646. if self.env['baremetal']:
  647. cmd.extend([
  648. '--bare-metal', LF,
  649. '--param', 'system.auto_reset_addr = True', LF,
  650. ])
  651. if self.env['arch'] == 'aarch64':
  652. # https://stackoverflow.com/questions/43682311/uart-communication-in-gem5-with-arm-bare-metal/50983650#50983650
  653. cmd.extend(['--param', 'system.highest_el_is_64 = True', LF])
  654. elif self.env['gem5_script'] == 'biglittle':
  655. if self.env['kvm']:
  656. cpu_type = 'kvm'
  657. else:
  658. cpu_type = 'atomic'
  659. if self.env['gem5_restore'] is not None:
  660. cpt_dir = self.gem5_list_checkpoint_dirs()[-self.env['gem5_restore']]
  661. extra_emulator_args.extend(['--restore-from', os.path.join(self.env['m5out_dir'], cpt_dir), LF])
  662. cmd.extend([
  663. os.path.join(
  664. self.env['gem5_source_dir'],
  665. 'configs',
  666. 'example',
  667. 'arm',
  668. 'fs_bigLITTLE.py'
  669. ), LF,
  670. '--bootscript', self.env['gem5_readfile_file'], LF,
  671. '--big-cpus', str((self.env['cpus'] + 1) // 2), LF,
  672. '--cpu-type', cpu_type, LF,
  673. '--kernel', self.env['image'], LF,
  674. '--kernel-cmd', 'root=/dev/vda {} {}'.format(arm_kernel_cli, kernel_cli), LF,
  675. '--little-cpus', str(self.env['cpus'] // 2), LF,
  676. ])
  677. if use_disk_image:
  678. cmd.extend(['--disk', self.env['disk_image'], LF])
  679. if os.path.exists(self.env['disk_image_2']):
  680. cmd.extend(['--disk', self.env['disk_image_2'], LF])
  681. if self.env['dtb']:
  682. cmd.extend([
  683. '--dtb',
  684. os.path.join(self.env['gem5_system_dir'],
  685. 'arm',
  686. 'dt',
  687. 'armv8_gem5_v1_big_little_2_2.dtb'
  688. ),
  689. LF
  690. ])
  691. if self.env['gem5_script'] == 'fs' or self.env['gem5_script'] == 'biglittle':
  692. if self.env['gem5_bootloader'] is not None:
  693. cmd.extend(['--bootloader', self.env['gem5_bootloader'], LF])
  694. cmd.extend(['--mem-size', memory, LF])
  695. if self.env['gdb_wait']:
  696. # https://stackoverflow.com/questions/49296092/how-to-make-gem5-wait-for-gdb-to-connect-to-reliably-break-at-start-kernel-of-th
  697. cmd.extend(['--param', 'system.cpu[0].wait_for_remote_gdb = True', LF])
  698. elif self.env['emulator'] == 'qemu':
  699. qemu_user_and_system_options = [
  700. '-trace', 'enable={},file={}'.format(trace_type, self.env['qemu_trace_file']), LF,
  701. ]
  702. if self.env['userland']:
  703. if self.env['gdb_wait']:
  704. debug_args = ['-g', str(self.env['gdb_port']), LF]
  705. else:
  706. debug_args = []
  707. cmd.extend(
  708. [
  709. self.env['qemu_executable'], LF,
  710. '-L', self.env['userland_library_dir'], LF,
  711. '-r', self.env['kernel_version'], LF,
  712. '-seed', '0', LF,
  713. ] +
  714. qemu_user_and_system_options +
  715. debug_args
  716. )
  717. cpu = 'max'
  718. else:
  719. extra_emulator_args.extend(extra_qemu_args)
  720. if debug_vm:
  721. serial_monitor = []
  722. else:
  723. if self.env['background']:
  724. serial_monitor = ['-serial', 'file:{}'.format(self.env['guest_terminal_file']), LF]
  725. if self.env['quiet']:
  726. show_stdout = False
  727. else:
  728. if self.env['ctrl_c_host']:
  729. serial = 'stdio'
  730. else:
  731. serial = 'mon:stdio'
  732. serial_monitor = ['-serial', serial, LF]
  733. if self.env['kvm']:
  734. extra_emulator_args.extend([
  735. '-enable-kvm', LF,
  736. ])
  737. cpu = 'host'
  738. else:
  739. cpu = 'max'
  740. extra_emulator_args.extend([
  741. '-serial',
  742. 'tcp::{},server,nowait'.format(self.env['extra_serial_port']), LF
  743. ])
  744. virtfs_data = [
  745. (self.env['p9_dir'], 'host_data'),
  746. (self.env['out_dir'], 'host_out'),
  747. (self.env['out_rootfs_overlay_dir'], 'host_out_rootfs_overlay'),
  748. (self.env['rootfs_overlay_dir'], 'host_rootfs_overlay'),
  749. ]
  750. virtfs_cmd = []
  751. for virtfs_dir, virtfs_tag in virtfs_data:
  752. if os.path.exists(virtfs_dir):
  753. virtfs_cmd.extend([
  754. '-virtfs',
  755. 'local,path={virtfs_dir},mount_tag={virtfs_tag},security_model=mapped,id={virtfs_tag}' \
  756. .format(virtfs_dir=virtfs_dir, virtfs_tag=virtfs_tag),
  757. LF,
  758. ])
  759. machines = [self.env['machine']]
  760. if self.env['arch'] == 'arm':
  761. # Needed since v3.0.0 due to:
  762. # http://lists.nongnu.org/archive/html/qemu-discuss/2018-08/msg00034.html
  763. machines.append('highmem=off')
  764. if self.env['is_arm'] and self.env['kvm']:
  765. # Otherwise "PMU: KVM_SET_DEVICE_ATTR: Invalid argument"
  766. # https://bugzilla.redhat.com/show_bug.cgi?format=multiple&id=1661976
  767. machines.append('gic-version=host')
  768. machines_cli = []
  769. for machine in machines:
  770. machines_cli.extend(['-machine', machine, LF])
  771. cmd.extend(
  772. [
  773. self.env['qemu_executable'], LF,
  774. ] +
  775. machines_cli +
  776. [
  777. '-device', 'rtl8139,netdev=net0', LF,
  778. '-gdb', 'tcp::{}'.format(self.env['gdb_port']), LF,
  779. '-kernel', self.env['image'], LF,
  780. '-m', self.env['memory'], LF,
  781. '-monitor', 'telnet::{},server,nowait'.format(self.env['qemu_monitor_port']), LF,
  782. '-netdev', 'user,hostfwd=tcp::{}-:{},hostfwd=tcp::{}-:22,id=net0'.format(
  783. self.env['qemu_hostfwd_generic_port'],
  784. self.env['qemu_hostfwd_generic_port'],
  785. self.env['qemu_hostfwd_ssh_port']
  786. ), LF,
  787. '-no-reboot', LF,
  788. '-smp', str(self.env['cpus']), LF,
  789. ] +
  790. virtfs_cmd +
  791. serial_monitor +
  792. vnc
  793. )
  794. if self.env['dtb'] is not None:
  795. cmd.extend(['-dtb', self.env['dtb'], LF])
  796. if self.env['baremetal']:
  797. cmd.extend([
  798. '-device', 'loader,addr={},file={},force-raw=on'.format(
  799. hex(argc_addr),
  800. baremetal_cli_path,
  801. ), LF,
  802. ])
  803. if not self.env['qemu_which'] == 'host':
  804. cmd.extend(qemu_user_and_system_options)
  805. if self.env['initrd']:
  806. extra_emulator_args.extend(['-initrd', self.env['buildroot_cpio'], LF])
  807. rr = self.env['record'] or self.env['replay']
  808. if self.env['ramfs']:
  809. # TODO why is this needed, and why any string works.
  810. root = 'root=/dev/anything'
  811. else:
  812. if rr:
  813. driveif = 'none'
  814. rrid = ',id=img-direct'
  815. rrid2 = ',id=img-direct2'
  816. if self.env['is_arm']:
  817. root = 'root=/dev/vda'
  818. else:
  819. root = 'root=/dev/sda'
  820. snapshot = ',snapshot'
  821. if self.env['is_arm']:
  822. hd_dev = 'virtio-blk-device'
  823. else:
  824. hd_dev = 'ide-hd'
  825. else:
  826. driveif = 'virtio'
  827. root = 'root=/dev/vda'
  828. rrid = ''
  829. rrid2 = ''
  830. snapshot = ',snapshot'
  831. if not self.env['baremetal']:
  832. if not os.path.exists(self.env['qcow2_file']):
  833. if not os.path.exists(self.env['rootfs_raw_file']):
  834. raise_rootfs_not_found()
  835. self.raw_to_qcow2(qemu_which=self.env['qemu_which'])
  836. if use_disk_image:
  837. if os.path.splitext(self.env['disk_image'])[1] == '.qcow2':
  838. disk_format = 'qcow2'
  839. else:
  840. disk_format = 'raw'
  841. extra_emulator_args.extend([
  842. '-drive',
  843. 'file={},format={},if={}{}{}'.format(
  844. self.env['disk_image'],
  845. disk_format,
  846. driveif,
  847. snapshot,
  848. rrid
  849. ),
  850. LF,
  851. ])
  852. if rr:
  853. extra_emulator_args.extend([
  854. '-drive', 'driver=blkreplay,if=none,image=img-direct,id=img-blkreplay', LF,
  855. '-device', '{},drive=img-blkreplay'.format(hd_dev), LF,
  856. ])
  857. if os.path.exists(self.env['disk_image_2']):
  858. extra_emulator_args.extend([
  859. '-drive',
  860. 'file={},format={},if={}{}{}'.format(
  861. self.env['disk_image_2'],
  862. 'raw',
  863. driveif,
  864. snapshot,
  865. rrid2
  866. ),
  867. LF,
  868. ])
  869. if rr:
  870. extra_emulator_args.extend([
  871. '-drive', 'driver=blkreplay,if=none,image=img-direct2,id=img-blkreplay', LF,
  872. '-device', '{},drive=img-blkreplay2'.format(hd_dev), LF,
  873. ])
  874. if rr:
  875. extra_emulator_args.extend([
  876. '-object', 'filter-replay,id=replay,netdev=net0', LF,
  877. '-icount', 'shift=7,rr={},rrfile={}'.format(
  878. 'record' if self.env['record'] else 'replay',
  879. self.env['qemu_rrfile']
  880. ), LF,
  881. ])
  882. virtio_gpu_pci = []
  883. else:
  884. virtio_gpu_pci = ['-device', 'virtio-gpu-pci', LF]
  885. if self.env['arch'] == 'x86_64':
  886. append = ['-append', '{} nopat {}'.format(root, kernel_cli), LF]
  887. cmd.extend([
  888. '-device', 'edu', LF,
  889. ])
  890. elif self.env['is_arm']:
  891. extra_emulator_args.extend(['-semihosting', LF])
  892. append = ['-append', '{} {}'.format(root, kernel_cli), LF]
  893. cmd.extend(
  894. virtio_gpu_pci
  895. )
  896. if not self.env['baremetal']:
  897. cmd.extend(append)
  898. extra_emulator_args.extend([
  899. '-cpu', cpu, LF,
  900. ])
  901. if self.env['tmux']:
  902. tmux_args = "--gem5-build-id '{}' --run-id {}".format(
  903. self.env['gem5_build_id'],
  904. self.env['run_id']
  905. )
  906. if self.env['tmux_program'] == 'shell':
  907. if self.env['emulator'] == 'gem5':
  908. tmux_cmd = os.path.join(self.env['root_dir'], 'gem5-shell')
  909. else:
  910. raise Exception('--tmux-program is only supported in gem5 currently.')
  911. elif self.env['tmux_program'] == 'gdb':
  912. tmux_cmd = os.path.join(self.env['root_dir'], 'run-gdb')
  913. # TODO find a nicer way to forward all those args automatically.
  914. # Part of me wants to: https://github.com/jonathanslenders/pymux
  915. # but it cannot be used as a library properly it seems, and it is
  916. # slower than tmux.
  917. tmux_args += " --arch {} --emulator '{}' --gcc-which '{}' --linux-build-id '{}' --userland-build-id '{}'".format(
  918. self.env['arch'],
  919. self.env['emulator'],
  920. self.env['gcc_which'],
  921. self.env['linux_build_id'],
  922. self.env['userland_build_id'],
  923. )
  924. if self.env['baremetal']:
  925. tmux_args += " --baremetal '{}'".format(self.env['baremetal'][0])
  926. if self.env['userland']:
  927. tmux_args += " --userland '{}'".format(self.env['userland'][0])
  928. if self.env['in_tree']:
  929. tmux_args += ' --in-tree'
  930. if self.env['tmux_args'] is not None:
  931. tmux_args += ' {}'.format(self.env['tmux_args'])
  932. tmux_cmd = [
  933. os.path.join(self.env['root_dir'], 'tmux-split'),
  934. "sleep 2;{} {}".format(tmux_cmd, tmux_args)
  935. ]
  936. self.log_info(self.sh.cmd_to_string(tmux_cmd))
  937. subprocess.Popen(tmux_cmd)
  938. cmd.extend(extra_emulator_args)
  939. cmd.extend(self.env['extra_emulator_args'])
  940. if self.env['userland'] and self.env['emulator'] in ('qemu', 'native'):
  941. if len(self.env['userland']) > 1:
  942. raise Exception('qemu and native machines only support a single executable')
  943. # The program and arguments must come at the every end of the CLI.
  944. cmd.extend([self.env['image'], LF])
  945. if self.env['cli_args'] is not None:
  946. cmd.extend(self.sh.shlex_split(self.env['cli_args']))
  947. if debug_vm or self.env['terminal']:
  948. out_file = None
  949. else:
  950. out_file = self.env['termout_file']
  951. exit_status = self.sh.run_cmd(
  952. cmd,
  953. cmd_files=[self.env['run_cmd_file'], os.path.join(self.env['out_dir'], self.env['run_cmd_file_basename'])],
  954. extra_env=extra_env,
  955. out_file=out_file,
  956. raise_on_failure=False,
  957. show_stdout=show_stdout,
  958. stdin_path=self.env['stdin_file'],
  959. )
  960. if self.env['debug_vm_rr']:
  961. rr_cmd = ['rr', 'replay', LF, '-o', '-q', LF]
  962. for arg in shlex.split(self.env['debug_vm_args']):
  963. rr_cmd.extend(['-o', arg, LF]);
  964. exit_status = self.sh.run_cmd(
  965. rr_cmd,
  966. raise_on_failure=False,
  967. show_stdout=show_stdout,
  968. )
  969. if exit_status == 0:
  970. error_string_found = False
  971. exit_status = 0
  972. if out_file is not None and not self.env['dry_run']:
  973. if self.env['emulator'] == 'gem5':
  974. with open(self.env['termout_file'], 'br') as logfile:
  975. # We have to do some parsing here because gem5 exits with status 0 even when panic happens.
  976. # Grepping for '^panic: ' does not work because some errors don't show that message...
  977. gem5_panic_re = re.compile(b'--- BEGIN LIBC BACKTRACE ---$')
  978. line = None
  979. for line in logfile:
  980. line = line.rstrip()
  981. if gem5_panic_re.search(line):
  982. exit_status = 1
  983. last_line = line
  984. if last_line is not None:
  985. if self.env['userland']:
  986. match = re.search(b'Simulated exit code not 0! Exit code is (\d+)', last_line)
  987. if match is not None:
  988. exit_status = int(match.group(1))
  989. if re.search(b'Exiting @ tick \d+ because simulate\(\) limit reached', last_line) is not None:
  990. exit_status = 1
  991. if not self.env['userland']:
  992. if os.path.exists(self.env['guest_terminal_file']):
  993. with open(self.env['guest_terminal_file'], 'br') as logfile:
  994. linux_panic_re = re.compile(b'Kernel panic - not syncing')
  995. serial_magic_exit_status_regexp = re.compile(self.env['serial_magic_exit_status_regexp_string'])
  996. for line in logfile.readlines():
  997. line = line.rstrip()
  998. if not self.env['baremetal'] and linux_panic_re.search(line):
  999. exit_status = 1
  1000. match = serial_magic_exit_status_regexp.match(line)
  1001. if match:
  1002. exit_status = int(match.group(1))
  1003. if exit_status != 0 and self.env['show_stdout']:
  1004. self.log_error('simulation error detected by parsing logs')
  1005. return exit_status
  1006. if __name__ == '__main__':
  1007. Main().cli()