mirror of
https://github.com/cirosantilli/linux-kernel-module-cheat.git
synced 2026-01-13 20:12:26 +00:00
--quit-after-boot: fix for gem5, update path to gem5.sh Improve the printing of results and errors: - remove newlines from IDs at the end for ./test-boot - remove newlines from progress for __call__ commands and don't print executed commands at all, otherwise there are too many lines per test and it is hard to tell what is going on - print backtraces for any exception in the threads (bugs while developing this code) Tests across different archs and emulators are still not running in parallel, which is a huge loss. TODO. thread_pool: introduce with API. This was motivate by test-boot, I've had enough of doing separate error handling for each loop type! Greatly dries up the code, awesome. common: make --all-emulators work properly with native hopefully for the last time, ./test-baremetal was still failing. gem5: don't pass --command-line for baremetal. Maybe later we can use it to actually pass command line arguments to main()? To be seen.
809 lines
34 KiB
Python
Executable File
809 lines
34 KiB
Python
Executable File
#!/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='5.0',
|
|
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,
|
|
])
|
|
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(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()
|