123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014 |
- #!/usr/bin/env python3
- import os
- import re
- import shlex
- import shutil
- import struct
- 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.
- See also: https://cirosantilli.com/linux-kernel-module-cheat#debug-the-emulator
- '''
- )
- self.add_argument(
- '--debug-vm-args',
- default='',
- help='''\
- Pass arguments to GDB. Implies --debug-vm. If --debug-vm-rr is used,
- pass the given arguments to GDB on the replay.
- '''
- )
- self.add_argument(
- '--debug-vm-file',
- help='''\
- Like --debug-vm, but also source this file. Equivalent to
- --debug-vm-args='-ex "source $file"'.
- '''
- )
- self.add_argument(
- '--debug-vm-ex',
- default='',
- help='''\
- Like --debug-vm-args, but takes as argument a semicolon separated list of
- -ex commands.
- '''
- )
- self.add_argument(
- '--debug-vm-rr',
- default=False,
- help='''
- Run the emulator through Mozilla RR, and then start replay with reverse debugging enabled:
- https://cirosantilli.com/linux-kernel-module-cheat#reverse-debug-the-emulator
- '''
- )
- 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://cirosantilli.com/linux-kernel-module-cheat#replace-init
- chdir into lkmc_home before running the command:
- https://cirosantilli.com/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. After this string is evaled, you are left
- inside a shell. See: https://cirosantilli.com/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://cirosantilli.com/linux-kernel-module-cheat#gdb
- '''
- )
- self.add_argument(
- '--gem5-script',
- default='fs',
- choices=['fs', 'biglittle'],
- help='''\
- Which gem5 script to use.
- See e.g.: https://cirosantilli.com/linux-kernel-module-cheat#gem5-fs-biglittle
- '''
- )
- self.add_argument(
- '--gem5-readfile',
- default='',
- help='''\
- Set the contents of m5 readfile to this string.
- https://cirosantilli.com/linux-kernel-module-cheat#gem5-restore-new-script
- '''
- )
- 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'`
- See also: https://cirosantilli.com/linux-kernel-module-cheat#init-environment
- '''
- )
- 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'`
- See also: https://cirosantilli.com/linux-kernel-module-cheat#init-environment
- '''
- )
- 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://cirosantilli.com/linux-kernel-module-cheat#init-busybox
- chdir into lkmc_home before running the command:
- https://cirosantilli.com/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.: `--memory 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(
- '--replay',
- default=False,
- help='Replay a QEMU run record deterministically'
- )
- self.add_argument(
- '--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(
- '--stdin-file',
- help='''\
- Set the given file as the stdin source of the emulator. QEMU and gem5 then
- forward this to the guest in user mode simulation.
- https://cirosantilli.com/linux-kernel-module-cheat#syscall-emulation-mode-program-stdin
- '''
- )
- 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://cirosantilli.com/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 not self.env['userland'] and not self.env['baremetal']:
- 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_rr']:
- debug_vm = ['rr', 'record']
- elif self.env['debug_vm'] or self.env['debug_vm_args'] or self.env['debug_vm_file'] or self.env['debug_vm_ex']:
- debug_vm = ['gdb', LF, '-q', LF] + self.sh.shlex_split(self.env['debug_vm_args'])
- for cmd in self.env['debug_vm_ex'].split(';'):
- debug_vm.extend(['-ex', cmd, LF]);
- if self.env['debug_vm_file'] is not None:
- debug_vm.extend(['-ex', 'source {}'.format(self.env['debug_vm_file']), LF])
- debug_vm.extend(['--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():
- if not self.env['dry_run']:
- raise Exception('Executable image not found. Did you build it? ' \
- 'Tried to use: ' + self.env['image'])
- cmd = debug_vm.copy()
- if not os.path.exists(self.env['image']):
- if self.env['emulator'] == 'gem5':
- if (
- not self.env['baremetal'] and
- not self.env['userland']
- ):
- # This is an attempte to run gem5 from a prebuilt download
- # but it is not working:
- # https://github.com/cirosantilli/linux-kernel-module-cheat/issues/79
- self.sh.check_output(
- [
- self.env['extract_vmlinux'],
- self.env['linux_image']
- ],
- out_file=self.env['image'],
- show_cmd=True,
- show_stdout=False
- )
- else:
- raise_image_not_found()
- else:
- raise_image_not_found()
- if self.env['baremetal']:
- # Setup CLI arguments into a single raw binary file to be loaded into memory.
- # The memory setup of that file is:
- # argc
- # argv[0] pointer
- # argv[1] pointer
- # ...
- # argv[N] pointer
- # argv[0][0] data
- # argv[0][1] data
- # ...
- # argv[1][0] data
- # argv[1][1] data
- # ...
- cli_args = [os.path.basename(self.env['image'])]
- if self.env['cli_args'] is not None:
- cli_args.extend(shlex.split(self.env['cli_args']))
- argc_addr = self.env['entry_address'] + self.env['baremetal_max_text_size'] + self.env['baremetal_memory_size']
- argv_addr = argc_addr + self.env['int_size']
- argv_data_addr = argv_addr + len(cli_args) * self.env['address_size']
- argv_addr_data = []
- argv_addr_cur = argv_data_addr
- for arg in cli_args:
- argv_addr_data.append(struct.pack(
- '<{}'.format(
- self.python_struct_int_format(self.env['address_size'])
- ),
- argv_addr_cur
- ))
- argv_addr_cur += len(arg) + 1
- baremetal_cli_path = os.path.join(self.env['run_dir'], 'baremetal_cli.raw')
- if self.env['emulator'] == 'qemu':
- self.make_run_dirs()
- with open(baremetal_cli_path, 'wb') as f:
- f.write(struct.pack('<{}'.format(self.python_struct_int_format(self.env['int_size'])), len(cli_args)))
- f.write(b''.join(argv_addr_data))
- f.write(b'\0'.join(arg.encode() for arg in cli_args) + b'\0')
- use_disk_image = self.env['disk_image'] is not None and \
- os.path.exists(self.env['disk_image']) or \
- not self.env['baremetal']
- if self.env['_args_given']['disk_image'] and not os.path.exists(self.env['disk_image']) :
- raise_rootfs_not_found()
- if self.env['emulator'] == 'gem5':
- if self.env['quiet']:
- show_stdout = False
- if not self.env['baremetal'] and not self.env['userland']:
- 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)
- os.makedirs(os.path.dirname(self.env['gem5_readfile_file']), exist_ok=True)
- self.sh.write_string_to_file(self.env['gem5_readfile_file'], 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])
- # https://cirosantilli.com/linux-kernel-module-cheat#m5-override-py-source
- 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['gem5_restore'] is not None:
- # https://cirosantilli.com/linux-kernel-module-cheat#gem5-checkpoint-internals
- cpt_dirs = self.gem5_list_checkpoint_dirs()
- cpt_dir = cpt_dirs[-self.env['gem5_restore']]
- cpt_dirs_sorted_by_tick = sorted(cpt_dirs, key=lambda x: int(x.split('.')[1]))
- extra_emulator_args.extend(['-r', str(cpt_dirs_sorted_by_tick.index(cpt_dir) + 1)])
- if self.env['userland']:
- cmd_opt = self.env['image']
- for u in self.env['userland'][1:]:
- cmd_opt += ';' + self.resolve_userland_executable(u)
- if len(self.env['userland']) > 1:
- workload_cpus = ':'
- else:
- workload_cpus = '0'
- cmd.extend([
- self.env['gem5_se_file'], LF,
- '--cmd', cmd_opt, LF,
- '--num-cpus', str(self.env['cpus']), LF,
- # We have to use cpu[0] here because on multi-cpu workloads,
- # cpu[1] and higher use workload as a proxy to cpu[0].workload.
- # as can be seen from the config.ini.
- # If system.cpu[:].workload[:] were used instead, we would get the error:
- # "KeyError: 'workload'"
- '--param', 'system.cpu[{}].workload[:].release = "{}"'.format(workload_cpus,self.env['kernel_version']), LF,
- ])
- if self.env['cli_args'] is not None:
- cmd.extend(['--options', self.env['cli_args'], LF])
- if not self.env['static']:
- for path in self.env['userland_library_redirects']:
- cmd.extend([
- '--redirects',
- '{}={}'.format(
- os.sep + path,
- os.path.join(self.env['userland_library_dir'], path)
- ),
- LF
- ])
- cmd.extend(['--interp-dir', self.env['userland_library_dir'], LF])
- else:
- if self.env['is_arm']:
- arm_kernel_cli = 'earlycon=pl011,0x1c090000 earlyprintk=pl011,0x1c090000 lpj=19988480 rw loglevel=8 mem={}'.format(memory)
- if self.env['gem5_script'] == 'fs':
- cmd.extend([
- self.env['gem5_fs_file'], LF,
- '--kernel', self.env['image'], LF,
- '--num-cpus', str(self.env['cpus']), LF,
- '--script', self.env['gem5_readfile_file'], LF,
- ])
- if use_disk_image:
- cmd.extend(['--disk-image', self.env['disk_image'], LF])
- if os.path.exists(self.env['disk_image_2']):
- cmd.extend(['--disk-image', self.env['disk_image_2'], LF])
- if self.env['baremetal']:
- cmd.extend([
- '--param', 'system.workload.extras = "{}"'.format(self.python_escape_double_quotes(baremetal_cli_path)), LF,
- '--param', 'system.workload.extras_addrs = {}'.format(hex(argc_addr)), LF,
- ])
- if self.env['arch'] == 'x86_64':
- if self.env['kvm']:
- cmd.extend(['--cpu-type', 'X86KvmCPU', LF])
- if not self.env['baremetal']:
- cmd.extend(['--command-line', 'earlycon={} earlyprintk={} lpj=7999923 root=/dev/sda {}'.format(console, 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([
- '--machine-type', self.env['machine'], LF,
- ])
- if not self.env['baremetal']:
- cmd.extend([
- '--command-line',
- # 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?
- 'root=/dev/sda {} {}'.format(arm_kernel_cli, kernel_cli), LF
- ])
- cmd.extend(['--param', 'system.workload.panic_on_panic = True', 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']:
- 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), LF])
- cmd.extend([
- os.path.join(
- self.env['gem5_source_dir'],
- 'configs',
- 'example',
- 'arm',
- 'fs_bigLITTLE.py'
- ), LF,
- '--bootscript', self.env['gem5_readfile_file'], LF,
- '--big-cpus', str((self.env['cpus'] + 1) // 2), LF,
- '--cpu-type', cpu_type, LF,
- '--kernel', self.env['image'], LF,
- '--kernel-cmd', 'root=/dev/vda {} {}'.format(arm_kernel_cli, kernel_cli), LF,
- '--little-cpus', str(self.env['cpus'] // 2), LF,
- ])
- if use_disk_image:
- cmd.extend(['--disk', self.env['disk_image'], LF])
- if os.path.exists(self.env['disk_image_2']):
- cmd.extend(['--disk', self.env['disk_image_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['gem5_script'] == 'fs' or self.env['gem5_script'] == 'biglittle':
- if self.env['gem5_bootloader'] is not None:
- cmd.extend(['--bootloader', self.env['gem5_bootloader'], LF])
- cmd.extend(['--mem-size', memory, 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']:
- 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
- )
- cpu = 'max'
- else:
- extra_emulator_args.extend(extra_qemu_args)
- if 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,
- ])
- cpu = 'host'
- else:
- cpu = 'max'
- 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,
- ])
- machines = [self.env['machine']]
- if self.env['arch'] == 'arm':
- # Needed since v3.0.0 due to:
- # http://lists.nongnu.org/archive/html/qemu-discuss/2018-08/msg00034.html
- machines.append('highmem=off')
- if self.env['is_arm'] and self.env['kvm']:
- # Otherwise "PMU: KVM_SET_DEVICE_ATTR: Invalid argument"
- # https://bugzilla.redhat.com/show_bug.cgi?format=multiple&id=1661976
- machines.append('gic-version=host')
- machines_cli = []
- for machine in machines:
- machines_cli.extend(['-machine', machine, LF])
- cmd.extend(
- [
- self.env['qemu_executable'], LF,
- ] +
- machines_cli +
- [
- '-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 self.env['baremetal']:
- cmd.extend([
- '-device', 'loader,addr={},file={},force-raw=on'.format(
- hex(argc_addr),
- baremetal_cli_path,
- ), 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'
- rrid2 = ',id=img-direct2'
- if self.env['is_arm']:
- root = 'root=/dev/vda'
- else:
- root = 'root=/dev/sda'
- snapshot = ',snapshot'
- if self.env['is_arm']:
- hd_dev = 'virtio-blk-device'
- else:
- hd_dev = 'ide-hd'
- else:
- driveif = 'virtio'
- root = 'root=/dev/vda'
- rrid = ''
- rrid2 = ''
- snapshot = ',snapshot'
- if not self.env['baremetal']:
- 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'])
- if use_disk_image:
- if os.path.splitext(self.env['disk_image'])[1] == '.qcow2':
- disk_format = 'qcow2'
- else:
- disk_format = 'raw'
- extra_emulator_args.extend([
- '-drive',
- 'file={},format={},if={}{}{}'.format(
- self.env['disk_image'],
- disk_format,
- driveif,
- snapshot,
- rrid
- ),
- LF,
- ])
- if rr:
- extra_emulator_args.extend([
- '-drive', 'driver=blkreplay,if=none,image=img-direct,id=img-blkreplay', LF,
- '-device', '{},drive=img-blkreplay'.format(hd_dev), LF,
- ])
- if os.path.exists(self.env['disk_image_2']):
- extra_emulator_args.extend([
- '-drive',
- 'file={},format={},if={}{}{}'.format(
- self.env['disk_image_2'],
- 'raw',
- driveif,
- snapshot,
- rrid2
- ),
- LF,
- ])
- if rr:
- extra_emulator_args.extend([
- '-drive', 'driver=blkreplay,if=none,image=img-direct2,id=img-blkreplay', LF,
- '-device', '{},drive=img-blkreplay2'.format(hd_dev), LF,
- ])
- if rr:
- extra_emulator_args.extend([
- '-object', 'filter-replay,id=replay,netdev=net0', LF,
- '-icount', 'shift=7,rr={},rrfile={}'.format(
- 'record' if self.env['record'] else 'replay',
- self.env['qemu_rrfile']
- ), LF,
- ])
- 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])
- append = ['-append', '{} {}'.format(root, kernel_cli), LF]
- cmd.extend(
- virtio_gpu_pci
- )
- if not self.env['baremetal']:
- cmd.extend(append)
- extra_emulator_args.extend([
- '-cpu', cpu, LF,
- ])
- if self.env['tmux']:
- tmux_args = "--gem5-build-id '{}' --run-id {}".format(
- self.env['gem5_build_id'],
- self.env['run_id']
- )
- if self.env['tmux_program'] == 'shell':
- if self.env['emulator'] == 'gem5':
- tmux_cmd = os.path.join(self.env['root_dir'], '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 '{}' --userland-build-id '{}'".format(
- self.env['arch'],
- self.env['emulator'],
- self.env['gcc_which'],
- self.env['linux_build_id'],
- self.env['userland_build_id'],
- )
- if self.env['baremetal']:
- tmux_args += " --baremetal '{}'".format(self.env['baremetal'][0])
- if self.env['userland']:
- tmux_args += " --userland '{}'".format(self.env['userland'][0])
- 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'):
- if len(self.env['userland']) > 1:
- raise Exception('qemu and native machines only support a single executable')
- # The program and arguments must come at the every end of the CLI.
- cmd.extend([self.env['image'], LF])
- if self.env['cli_args'] is not None:
- cmd.extend(self.sh.shlex_split(self.env['cli_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_files=[self.env['run_cmd_file'], os.path.join(self.env['out_dir'], self.env['run_cmd_file_basename'])],
- extra_env=extra_env,
- out_file=out_file,
- raise_on_failure=False,
- show_stdout=show_stdout,
- stdin_path=self.env['stdin_file'],
- )
- if self.env['debug_vm_rr']:
- rr_cmd = ['rr', 'replay', LF, '-o', '-q', LF]
- for arg in shlex.split(self.env['debug_vm_args']):
- rr_cmd.extend(['-o', arg, LF]);
- exit_status = self.sh.run_cmd(
- rr_cmd,
- 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:
- line = line.rstrip()
- if gem5_panic_re.search(line):
- exit_status = 1
- last_line = line
- if last_line is not None:
- if self.env['userland']:
- match = re.search(b'Simulated exit code not 0! Exit code is (\d+)', last_line)
- if match is not None:
- exit_status = int(match.group(1))
- if re.search(b'Exiting @ tick \d+ because simulate\(\) limit reached', last_line) is not None:
- exit_status = 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()
|