123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810 |
- #!/usr/bin/env python3
- import os
- import re
- import shutil
- import subprocess
- import sys
- import time
- import common
- from shell_helpers import LF
- class Main(common.LkmcCliFunction):
- def __init__(self):
- super().__init__(
- description='''\
- Run some content on an emulator.
- '''
- )
- self.add_argument(
- '-c',
- '--cpus',
- default=1,
- type=int,
- help='Number of guest CPUs to emulate. Default: %(default)s'
- )
- self.add_argument(
- '--ctrl-c-host',
- default=False,
- help='''\
- Ctrl +C kills the QEMU simulator instead of being passed to the guest.
- '''
- )
- self.add_argument(
- '-D',
- '--debug-vm',
- default=False,
- help='''\
- Run GDB on the emulator itself.
- For --emulator native, this debugs the target program.
- '''
- )
- self.add_argument(
- '--debug-vm-args',
- default='',
- help='Pass arguments to GDB. Implies --debug-vm.'
- )
- self.add_argument(
- '--dtb',
- help='''\
- Use the specified DTB file. If not given, let the emulator generate a DTB for us,
- which is what you usually want.
- '''
- )
- self.add_argument(
- '-E',
- '--eval',
- help='''\
- Replace the normal init with a minimal init that just evals the given sh string.
- See: https://github.com/cirosantilli/linux-kernel-module-cheat#replace-init
- chdir into lkmc_home before running the command:
- https://github.com/cirosantilli/linux-kernel-module-cheat#lkmc_home
- '''
- )
- self.add_argument(
- '-F',
- '--eval-after',
- help='''\
- Similar to --eval, but the string gets evaled at the last init script,
- after the normal init finished.
- See: https://github.com/cirosantilli/linux-kernel-module-cheat#init-busybox
- '''
- )
- self.add_argument(
- '-G',
- '--gem5-exe-args',
- default='',
- help='''\
- Pass extra options to the gem5 executable.
- Do not confuse with the arguments passed to config scripts,
- like `fs.py`. Example:
- ./run --emulator gem5 --gem5-exe-args '--debug-flags=Exec --debug' -- --cpu-type=HPI --caches
- will run:
- gem.op5 --debug-flags=Exec fs.py --cpu-type=HPI --caches
- '''
- )
- self.add_argument(
- '--gdb',
- default=False,
- help='''\
- Shortcut for the most common GDB options that you want most of the time. Implies:
- * --gdb-wait
- * --tmux-args <main> where <main> is:
- ** start_kernel in full system
- ** main in user mode
- * --tmux-program gdb
- '''
- )
- self.add_argument(
- '--gdb-wait',
- default=False,
- help='''\
- Wait for GDB to connect before starting execution
- See: https://github.com/cirosantilli/linux-kernel-module-cheat#gdb
- '''
- )
- self.add_argument(
- '--gem5-script',
- default='fs',
- choices=['fs', 'biglittle'],
- help='Which gem5 script to use'
- )
- self.add_argument(
- '--gem5-readfile',
- default='',
- help='Set the contents of m5 readfile to this string.'
- )
- self.add_argument(
- '--gem5-restore',
- type=int,
- help='''\
- Restore the nth most recently taken gem5 checkpoint according to directory
- timestamps.
- '''
- )
- self.add_argument(
- '--graphic',
- default=False,
- help='''\
- Run in graphic mode.
- See: http://github.com/cirosantilli/linux-kernel-module-cheat#graphics
- '''
- )
- self.add_argument(
- '--kdb',
- default=False,
- help='''\
- Setup KDB kernel CLI options.
- See: http://github.com/cirosantilli/linux-kernel-module-cheat#kdb
- '''
- )
- self.add_argument(
- '--kernel-cli',
- help='''\
- Pass an extra Linux kernel command line options, and place them before
- the dash separator `-`. Only options that come before the `-`, i.e.
- "standard" options, should be passed with this option.
- Example: `./run --arch arm --kernel-cli 'init=/lkmc/poweroff.out'`
- '''
- )
- self.add_argument(
- '--kernel-cli-after-dash',
- help='''\
- Pass an extra Linux kernel command line options, add a dash `-`
- separator, and place the options after the dash. Intended for custom
- options understood by our `init` scripts, most of which are prefixed
- by `lkmc_`.
- Example: `./run --kernel-cli-after-dash 'lkmc_eval="wget google.com" lkmc_lala=y'`
- '''
- )
- self.add_argument(
- '--kernel-version',
- default=common.consts['linux_kernel_version'],
- help='''\
- Pass a base64 encoded command line parameter that gets evalled at the end of
- the normal init.
- See: https://github.com/cirosantilli/linux-kernel-module-cheat#init-busybox
- chdir into lkmc_home before running the command:
- https://github.com/cirosantilli/linux-kernel-module-cheat#lkmc_home
- Specify the Linux kernel version to be reported by syscall emulation.
- Defaults to the same kernel version as our default Buildroot build.
- Currently only works for QEMU.
- See: http://github.com/cirosantilli/linux-kernel-module-cheat#fatal-kernel-too-old
- '''
- )
- self.add_argument(
- '--kgdb',
- default=False,
- help='''\
- Setup KGDB kernel CLI options.
- See: http://github.com/cirosantilli/linux-kernel-module-cheat#kgdb
- '''
- )
- self.add_argument(
- '-K',
- '--kvm',
- default=False,
- help='''\
- Use KVM. Only works if guest arch == host arch.
- See: http://github.com/cirosantilli/linux-kernel-module-cheat#kvm
- '''
- )
- self.add_argument(
- '-m',
- '--memory',
- default='256M',
- help='''\
- Set the memory size of the guest. E.g.: `-m 512M`. We try to keep the default
- at the minimal ammount amount that boots all archs. Anything lower could lead
- some arch to fail to boot.
- Default: %(default)s
- '''
- )
- self.add_argument(
- '--quit-after-boot',
- default=False,
- help='''\
- Setup a kernel init parameter that makes the emulator quit immediately after boot.
- '''
- )
- self.add_argument(
- '-R',
- '--replay',
- default=False,
- help='Replay a QEMU run record deterministically'
- )
- self.add_argument(
- '-r',
- '--record',
- default=False,
- help='Record a QEMU run record for later replay with `-R`'
- )
- self.add_argument(
- '--show-stdout',
- default=True,
- help='''Show emulator stdout and stderr on the host terminal.'''
- )
- self.add_argument(
- '--terminal',
- default=False,
- help='''\
- Output directly to the terminal, don't pipe to tee as the default.
- With this, we don't not save the output to a file as is done by default,
- but we are able to do things that require not having a pipe such as
- using debuggers. This option is set automatically by --debug-vm, but you
- still need it to debug gem5 Python scripts with pdb.
- '''
- )
- self.add_argument(
- '-T',
- '--trace',
- help='''\
- Set trace events to be enabled. If not given, gem5 tracing is completely
- disabled, while QEMU tracing is enabled but uses default traces that are very
- rare and don't affect performance, because `./configure
- --enable-trace-backends=simple` seems to enable some traces by default, e.g.
- `pr_manager_run`, and I don't know how to get rid of them.
- See: http://github.com/cirosantilli/linux-kernel-module-cheat#tracing
- '''
- )
- self.add_argument(
- '--trace-stdout',
- default=False,
- help='''\
- Output trace to stdout instead of a file. Only works for gem5 currently.
- '''
- )
- self.add_argument(
- '--trace-insts-stdout',
- default=False,
- help='''\
- Trace instructions run to stdout. Shortcut for --trace --trace-stdout.
- '''
- )
- self.add_argument(
- '-t',
- '--tmux',
- default=False,
- help='''\
- Create a tmux split the window. You must already be inside of a `tmux` session
- to use this option:
- * on the main window, run the emulator as usual
- * on the split:
- ** if on QEMU and `-d` is given, GDB
- ** if on gem5, the gem5 terminal
- See: https://github.com/cirosantilli/linux-kernel-module-cheat#tmux
- '''
- )
- self.add_argument(
- '--tmux-args',
- help='''\
- Parameters to pass to the program running on the tmux split. Implies --tmux.
- '''
- )
- self.add_argument(
- '--tmux-program',
- choices=('gdb', 'shell'),
- help='''\
- Which program to run in tmux. Implies --tmux. Defaults:
- * 'gdb' in qemu
- * 'shell' in gem5. 'shell' is only supported in gem5 currently.
- '''
- )
- self.add_argument(
- '--vnc',
- default=False,
- help='''\
- Run QEMU with VNC instead of the default SDL. Connect to it with:
- `vinagre localhost:5900`.
- '''
- )
- self.add_argument(
- 'extra_emulator_args',
- nargs='*',
- default=[],
- help='''\
- Extra options to append at the end of the emulator command line.
- '''
- )
- def timed_main(self):
- show_stdout = self.env['show_stdout']
- # Common qemu / gem5 logic.
- # nokaslr:
- # * https://unix.stackexchange.com/questions/397939/turning-off-kaslr-to-debug-linux-kernel-using-qemu-and-gdb
- # * https://stackoverflow.com/questions/44612822/unable-to-debug-kernel-with-qemu-gdb/49840927#49840927
- # Turned on by default since v4.12
- kernel_cli = 'console_msg_format=syslog nokaslr norandmaps panic=-1 printk.devkmsg=on printk.time=y rw'
- if self.env['kernel_cli'] is not None:
- kernel_cli += ' {}'.format(self.env['kernel_cli'])
- if self.env['quit_after_boot']:
- kernel_cli += ' {}'.format(self.env['quit_init'])
- kernel_cli_after_dash = ' lkmc_home={}'.format(self.env['guest_lkmc_home'])
- extra_emulator_args = []
- extra_qemu_args = []
- if not self.env['_args_given']['tmux_program']:
- if self.env['emulator'] == 'qemu':
- self.env['tmux_program'] = 'gdb'
- elif self.env['emulator'] == 'gem5':
- self.env['tmux_program'] = 'shell'
- if self.env['gdb']:
- if not self.env['_args_given']['gdb_wait']:
- self.env['gdb_wait'] = True
- if not self.env['_args_given']['tmux_args']:
- if self.env['userland'] is None and self.env['baremetal'] is None:
- self.env['tmux_args'] = 'start_kernel'
- else:
- self.env['tmux_args'] = 'main'
- if not self.env['_args_given']['tmux_program']:
- self.env['tmux_program'] = 'gdb'
- if self.env['tmux_args'] is not None or self.env['_args_given']['tmux_program']:
- self.env['tmux'] = True
- if self.env['debug_vm'] or self.env['debug_vm_args']:
- debug_vm = ['gdb', LF, '-q', LF] + self.sh.shlex_split(self.env['debug_vm_args']) + ['--args', LF]
- else:
- debug_vm = []
- if self.env['gdb_wait']:
- extra_qemu_args.extend(['-S', LF])
- if self.env['eval_after'] is not None:
- kernel_cli_after_dash += ' lkmc_eval_base64="{}"'.format(self.sh.base64_encode(self.env['eval_after']))
- if self.env['kernel_cli_after_dash'] is not None:
- kernel_cli_after_dash += ' {}'.format(self.env['kernel_cli_after_dash'])
- if self.env['vnc']:
- vnc = ['-vnc', ':0', LF]
- else:
- vnc = []
- if self.env['eval'] is not None:
- kernel_cli += ' {}=/lkmc/eval_base64.sh'.format(self.env['initarg'])
- kernel_cli_after_dash += ' lkmc_eval="{}"'.format(self.sh.base64_encode(self.env['eval']))
- if not self.env['graphic']:
- extra_qemu_args.extend(['-nographic', LF])
- console = None
- console_type = None
- console_count = 0
- if self.env['arch'] == 'x86_64':
- console_type = 'ttyS'
- elif self.env['is_arm']:
- console_type = 'ttyAMA'
- console = '{}{}'.format(console_type, console_count)
- console_count += 1
- if not (self.env['arch'] == 'x86_64' and self.env['graphic']):
- kernel_cli += ' console={}'.format(console)
- extra_console = '{}{}'.format(console_type, console_count)
- console_count += 1
- if self.env['kdb'] or self.env['kgdb']:
- kernel_cli += ' kgdbwait'
- if self.env['kdb']:
- if self.env['graphic']:
- kdb_cmd = 'kbd,'
- else:
- kdb_cmd = ''
- kernel_cli += ' kgdboc={}{},115200'.format(kdb_cmd, console)
- if self.env['kgdb']:
- kernel_cli += ' kgdboc={},115200'.format(extra_console)
- if kernel_cli_after_dash:
- kernel_cli += " -{}".format(kernel_cli_after_dash)
- extra_env = {}
- if self.env['trace_insts_stdout']:
- if self.env['emulator'] == 'qemu':
- extra_emulator_args.extend(['-d', 'in_asm', LF])
- elif self.env['emulator'] == 'gem5':
- self.env['trace_stdout'] = True
- self.env['trace'] = 'ExecAll'
- if self.env['trace'] is None:
- do_trace = False
- # A dummy value that is already turned on by default and does not produce large output,
- # just to prevent QEMU from emitting a warning that '' is not valid.
- trace_type = 'load_file'
- else:
- do_trace = True
- trace_type = self.env['trace']
- def raise_rootfs_not_found():
- if not self.env['dry_run']:
- raise Exception('Root filesystem not found. Did you build it? ' \
- 'Tried to use: ' + self.env['disk_image'])
- def raise_image_not_found(image):
- if not self.env['dry_run']:
- raise Exception('Executable image not found. Did you build it? ' \
- 'Tried to use: ' + image)
- if not os.path.exists(self.env['image']):
- raise_image_not_found(self.env['image'])
- cmd = debug_vm.copy()
- if self.env['emulator'] == 'gem5':
- if self.env['quiet']:
- show_stdout = False
- if not self.env['baremetal'] is None:
- if not os.path.exists(self.env['gem5_fake_iso']):
- os.makedirs(os.path.dirname(self.env['gem5_fake_iso']), exist_ok=True)
- self.sh.write_string_to_file(self.env['gem5_fake_iso'], 'a' * 512)
- elif self.env['userland'] is None:
- if not os.path.exists(self.env['rootfs_raw_file']):
- if not os.path.exists(self.env['qcow2_file']):
- raise_rootfs_not_found()
- self.raw_to_qcow2(qemu_which=self.env['qemu_which'], reverse=True)
- if not os.path.exists(self.env['image']):
- # This is to run gem5 from a prebuilt download.
- if (
- self.env['baremetal'] is None and
- self.env['userland'] is None
- ):
- if not os.path.exists(self.env['linux_image']):
- raise_image_not_found(self.env['image'])
- self.sh.run_cmd([os.path.join(self.env['extract_vmlinux'], self.env['linux_image'])])
- os.makedirs(os.path.dirname(self.env['gem5_readfile']), exist_ok=True)
- self.sh.write_string_to_file(self.env['gem5_readfile'], self.env['gem5_readfile'])
- memory = '{}B'.format(self.env['memory'])
- gem5_exe_args = self.sh.shlex_split(self.env['gem5_exe_args'])
- if do_trace:
- gem5_exe_args.extend(['--debug-flags', trace_type, LF])
- extra_env['M5_PATH'] = self.env['gem5_system_dir']
- # https://stackoverflow.com/questions/52312070/how-to-modify-a-file-under-src-python-and-run-it-without-rebuilding-in-gem5/52312071#52312071
- extra_env['M5_OVERRIDE_PY_SOURCE'] = 'true'
- if self.env['trace_stdout']:
- debug_file = 'cout'
- else:
- debug_file = 'trace.txt'
- cmd.extend(
- [
- self.env['executable'], LF,
- '--debug-file', debug_file, LF,
- '--listener-mode', 'on', LF,
- '--outdir', self.env['m5out_dir'], LF,
- ] +
- gem5_exe_args
- )
- if self.env['userland'] is not None:
- cmd.extend([
- self.env['gem5_se_file'], LF,
- '--cmd', self.env['image'], LF,
- '--param', 'system.cpu[:].workload[:].release = "{}"'.format(self.env['kernel_version']), LF,
- ])
- if self.env['userland_args'] is not None:
- cmd.extend(['--options', self.env['userland_args'], LF])
- else:
- if self.env['gem5_script'] == 'fs':
- # TODO port
- if self.env['gem5_restore'] is not None:
- cpt_dirs = self.gem5_list_checkpoint_dirs()
- cpt_dir = cpt_dirs[-self.env['gem5_restore']]
- extra_emulator_args.extend(['-r', str(sorted(cpt_dirs).index(cpt_dir) + 1)])
- cmd.extend([
- self.env['gem5_fs_file'], LF,
- '--disk-image', self.env['disk_image'], LF,
- '--kernel', self.env['image'], LF,
- '--mem-size', memory, LF,
- '--num-cpus', str(self.env['cpus']), LF,
- '--script', self.env['gem5_readfile'], LF,
- ])
- if self.env['arch'] == 'x86_64':
- if self.env['kvm']:
- cmd.extend(['--cpu-type', 'X86KvmCPU', LF])
- if self.env['baremetal'] is None:
- cmd.extend(['--command-line', 'earlyprintk={} lpj=7999923 root=/dev/sda {}'.format(console, kernel_cli), LF])
- elif self.env['is_arm']:
- if self.env['kvm']:
- cmd.extend(['--cpu-type', 'ArmV8KvmCPU', LF])
- if self.env['dp650']:
- dp650_cmd = 'dpu_'
- else:
- dp650_cmd = ''
- cmd.extend([
- # TODO why is it mandatory to pass mem= here? Not true for QEMU.
- # Anything smaller than physical blows up as expected, but why can't it auto-detect the right value?
- '--machine-type', self.env['machine'], LF,
- ])
- if self.env['baremetal'] is None:
- cmd.extend(['--command-line', 'earlyprintk=pl011,0x1c090000 lpj=19988480 rw loglevel=8 mem={} root=/dev/sda {}'.format(memory, kernel_cli), LF])
- dtb = None
- if self.env['dtb'] is not None:
- dtb = self.env['dtb']
- elif self.env['dp650']:
- dtb = os.path.join(
- self.env['gem5_system_dir'],
- 'arm',
- 'dt',
- 'armv{}_gem5_v1_{}{}cpu.dtb'.format(
- self.env['armv'],
- dp650_cmd,
- self.env['cpus']
- )
- )
- if dtb is not None:
- cmd.extend(['--dtb-filename', dtb, LF])
- if self.env['baremetal'] is None:
- cmd.extend(['--param', 'system.panic_on_panic = True', LF ])
- else:
- cmd.extend([
- '--bare-metal', LF,
- '--param', 'system.auto_reset_addr = True', LF,
- ])
- if self.env['arch'] == 'aarch64':
- # https://stackoverflow.com/questions/43682311/uart-communication-in-gem5-with-arm-bare-metal/50983650#50983650
- cmd.extend(['--param', 'system.highest_el_is_64 = True', LF])
- elif self.env['gem5_script'] == 'biglittle':
- if self.env['kvm']:
- cpu_type = 'kvm'
- else:
- cpu_type = 'atomic'
- if self.env['gem5_restore'] is not None:
- cpt_dir = self.gem5_list_checkpoint_dirs()[-self.env['gem5_restore']]
- extra_emulator_args.extend(['--restore-from', os.path.join(self.env['m5out_dir'], cpt_dir)])
- cmd.extend([
- os.path.join(
- self.env['gem5_source_dir'],
- 'configs',
- 'example',
- 'arm',
- 'fs_bigLITTLE.py'
- ), LF,
- '--big-cpus', '2', LF,
- '--cpu-type', cpu_type, LF,
- '--disk', self.env['disk_image'], LF,
- '--kernel', self.env['image'], LF,
- '--little-cpus', '2', LF,
- ])
- if self.env['dtb']:
- cmd.extend([
- '--dtb',
- os.path.join(self.env['gem5_system_dir'],
- 'arm',
- 'dt',
- 'armv8_gem5_v1_big_little_2_2.dtb'
- ),
- LF
- ])
- if self.env['gdb_wait']:
- # https://stackoverflow.com/questions/49296092/how-to-make-gem5-wait-for-gdb-to-connect-to-reliably-break-at-start-kernel-of-th
- cmd.extend(['--param', 'system.cpu[0].wait_for_remote_gdb = True', LF])
- elif self.env['emulator'] == 'qemu':
- qemu_user_and_system_options = [
- '-trace', 'enable={},file={}'.format(trace_type, self.env['qemu_trace_file']), LF,
- ]
- if self.env['userland'] is not None:
- if self.env['gdb_wait']:
- debug_args = ['-g', str(self.env['gdb_port']), LF]
- else:
- debug_args = []
- cmd.extend(
- [
- self.env['qemu_executable'], LF,
- '-L', self.env['userland_library_dir'], LF,
- '-r', self.env['kernel_version'], LF,
- '-seed', '0', LF,
- ] +
- qemu_user_and_system_options +
- debug_args
- )
- else:
- extra_emulator_args.extend(extra_qemu_args)
- self.make_run_dirs()
- if self.env['debug_vm']:
- serial_monitor = []
- else:
- if self.env['background']:
- serial_monitor = ['-serial', 'file:{}'.format(self.env['guest_terminal_file']), LF]
- if self.env['quiet']:
- show_stdout = False
- else:
- if self.env['ctrl_c_host']:
- serial = 'stdio'
- else:
- serial = 'mon:stdio'
- serial_monitor = ['-serial', serial, LF]
- if self.env['kvm']:
- extra_emulator_args.extend(['-enable-kvm', LF])
- extra_emulator_args.extend([
- '-serial',
- 'tcp::{},server,nowait'.format(self.env['extra_serial_port']), LF
- ])
- virtfs_data = [
- (self.env['p9_dir'], 'host_data'),
- (self.env['out_dir'], 'host_out'),
- (self.env['out_rootfs_overlay_dir'], 'host_out_rootfs_overlay'),
- (self.env['rootfs_overlay_dir'], 'host_rootfs_overlay'),
- ]
- virtfs_cmd = []
- for virtfs_dir, virtfs_tag in virtfs_data:
- if os.path.exists(virtfs_dir):
- virtfs_cmd.extend([
- '-virtfs',
- 'local,path={virtfs_dir},mount_tag={virtfs_tag},security_model=mapped,id={virtfs_tag}' \
- .format(virtfs_dir=virtfs_dir, virtfs_tag=virtfs_tag),
- LF,
- ])
- if self.env['machine2'] is not None:
- # Multiple -machine options can also be given comma separated in one -machine.
- # We use multiple because the machine is used as an identifier on baremetal tests
- # build paths, so better keep them clean.
- machine2 = ['-machine', self.env['machine2'], LF]
- else:
- machine2 = []
- cmd.extend(
- [
- self.env['qemu_executable'], LF,
- '-machine', self.env['machine'], LF,
- ] +
- machine2 +
- [
- '-device', 'rtl8139,netdev=net0', LF,
- '-gdb', 'tcp::{}'.format(self.env['gdb_port']), LF,
- '-kernel', self.env['image'], LF,
- '-m', self.env['memory'], LF,
- '-monitor', 'telnet::{},server,nowait'.format(self.env['qemu_monitor_port']), LF,
- '-netdev', 'user,hostfwd=tcp::{}-:{},hostfwd=tcp::{}-:22,id=net0'.format(
- self.env['qemu_hostfwd_generic_port'],
- self.env['qemu_hostfwd_generic_port'],
- self.env['qemu_hostfwd_ssh_port']
- ), LF,
- '-no-reboot', LF,
- '-smp', str(self.env['cpus']), LF,
- ] +
- virtfs_cmd +
- serial_monitor +
- vnc
- )
- if self.env['dtb'] is not None:
- cmd.extend(['-dtb', self.env['dtb'], LF])
- if not self.env['qemu_which'] == 'host':
- cmd.extend(qemu_user_and_system_options)
- if self.env['initrd']:
- extra_emulator_args.extend(['-initrd', self.env['buildroot_cpio'], LF])
- rr = self.env['record'] or self.env['replay']
- if self.env['ramfs']:
- # TODO why is this needed, and why any string works.
- root = 'root=/dev/anything'
- else:
- if rr:
- driveif = 'none'
- rrid = ',id=img-direct'
- root = 'root=/dev/sda'
- snapshot = ''
- else:
- driveif = 'virtio'
- root = 'root=/dev/vda'
- rrid = ''
- snapshot = ',snapshot'
- if self.env['baremetal'] is None:
- if not os.path.exists(self.env['qcow2_file']):
- if not os.path.exists(self.env['rootfs_raw_file']):
- raise_rootfs_not_found()
- self.raw_to_qcow2(qemu_which=self.env['qemu_which'])
- extra_emulator_args.extend([
- '-drive',
- 'file={},format=qcow2,if={}{}{}'.format(
- self.env['disk_image'],
- driveif,
- snapshot,
- rrid
- ),
- LF,
- ])
- if rr:
- extra_emulator_args.extend([
- '-drive', 'driver=blkreplay,if=none,image=img-direct,id=img-blkreplay', LF,
- '-device', 'ide-hd,drive=img-blkreplay', LF,
- ])
- if rr:
- extra_emulator_args.extend([
- '-object', 'filter-replay,id=replay,netdev=net0',
- '-icount', 'shift=7,rr={},rrfile={}'.format(
- 'record' if self.env['record'] else 'replay',
- self.env['qemu_rrfile']
- ),
- ])
- virtio_gpu_pci = []
- else:
- virtio_gpu_pci = ['-device', 'virtio-gpu-pci', LF]
- if self.env['arch'] == 'x86_64':
- append = ['-append', '{} nopat {}'.format(root, kernel_cli), LF]
- cmd.extend([
- '-device', 'edu', LF,
- ])
- elif self.env['is_arm']:
- extra_emulator_args.extend(['-semihosting', LF])
- if self.env['arch'] == 'arm':
- cpu = 'cortex-a15'
- else:
- cpu = 'cortex-a57'
- append = ['-append', '{} {}'.format(root, kernel_cli), LF]
- cmd.extend(
- [
- '-cpu', cpu, LF,
- ] +
- virtio_gpu_pci
- )
- if self.env['baremetal'] is None:
- cmd.extend(append)
- if self.env['tmux']:
- tmux_args = '--run-id {}'.format(self.env['run_id'])
- if self.env['tmux_program'] == 'shell':
- if self.env['emulator'] == 'gem5':
- tmux_cmd = './gem5-shell'
- else:
- raise Exception('--tmux-program is only supported in gem5 currently.')
- elif self.env['tmux_program'] == 'gdb':
- tmux_cmd = os.path.join(self.env['root_dir'], 'run-gdb')
- # TODO find a nicer way to forward all those args automatically.
- # Part of me wants to: https://github.com/jonathanslenders/pymux
- # but it cannot be used as a library properly it seems, and it is
- # slower than tmux.
- tmux_args += " --arch {} --emulator '{}' --gcc-which '{}' --linux-build-id '{}' --run-id '{}' --userland-build-id '{}'".format(
- self.env['arch'],
- self.env['emulator'],
- self.env['gcc_which'],
- self.env['linux_build_id'],
- self.env['run_id'],
- self.env['userland_build_id'],
- )
- if self.env['baremetal']:
- tmux_args += " --baremetal '{}'".format(self.env['baremetal'])
- if self.env['userland']:
- tmux_args += " --userland '{}'".format(self.env['userland'])
- if self.env['in_tree']:
- tmux_args += ' --in-tree'
- if self.env['tmux_args'] is not None:
- tmux_args += ' {}'.format(self.env['tmux_args'])
- tmux_cmd = [
- os.path.join(self.env['root_dir'], 'tmux-split'),
- "sleep 2;{} {}".format(tmux_cmd, tmux_args)
- ]
- self.log_info(self.sh.cmd_to_string(tmux_cmd))
- subprocess.Popen(tmux_cmd)
- cmd.extend(extra_emulator_args)
- cmd.extend(self.env['extra_emulator_args'])
- if self.env['userland'] and self.env['emulator'] in ('qemu', 'native'):
- # The program and arguments must come at the every end of the CLI.
- cmd.extend([self.env['image'], LF])
- if self.env['userland_args'] is not None:
- cmd.extend(self.sh.shlex_split(self.env['userland_args']))
- if debug_vm or self.env['terminal']:
- out_file = None
- else:
- out_file = self.env['termout_file']
- exit_status = self.sh.run_cmd(
- cmd,
- cmd_file=self.env['run_cmd_file'],
- extra_env=extra_env,
- out_file=out_file,
- raise_on_failure=False,
- show_stdout=show_stdout,
- )
- if exit_status == 0:
- error_string_found = False
- exit_status = 0
- if out_file is not None and not self.env['dry_run']:
- if self.env['emulator'] == 'gem5':
- with open(self.env['termout_file'], 'br') as logfile:
- # We have to do some parsing here because gem5 exits with status 0 even when panic happens.
- # Grepping for '^panic: ' does not work because some errors don't show that message...
- gem5_panic_re = re.compile(b'--- BEGIN LIBC BACKTRACE ---$')
- line = None
- for line in logfile:
- if gem5_panic_re.search(line):
- exit_status = 1
- if self.env['userland']:
- if line is not None:
- last_line = line.rstrip()
- match = re.search(b'Simulated exit code not 0! Exit code is (\d+)', last_line)
- if match:
- exit_status = int(match.group(1))
- if not self.env['userland']:
- if os.path.exists(self.env['guest_terminal_file']):
- with open(self.env['guest_terminal_file'], 'br') as logfile:
- linux_panic_re = re.compile(b'Kernel panic - not syncing')
- serial_magic_exit_status_regexp = re.compile(self.env['serial_magic_exit_status_regexp_string'])
- for line in logfile.readlines():
- line = line.rstrip()
- if not self.env['baremetal'] and linux_panic_re.search(line):
- exit_status = 1
- match = serial_magic_exit_status_regexp.match(line)
- if match:
- exit_status = int(match.group(1))
- if exit_status != 0 and self.env['show_stdout']:
- self.log_error('simulation error detected by parsing logs')
- return exit_status
- if __name__ == '__main__':
- Main().cli()
|