Files
Ciro Santilli 六四事件 法轮功 da900a579c LKMC v3.0
This is a squash commit, the unsquashed development went through many
unstable phases which would break bisects. The unsquashed branch is:
https://github.com/cirosantilli/linux-kernel-module-cheat/tree/v3.0-unsquash

The main improvement of this release was to greatly generalize the testing system.

The key addition was cli_function.py, which allows scripts such as ./run to
be transparently called either from Python or from the command line.

New tests scripts were created using this improved framework: test-baremetal
and test-user-mode.

We were lazy to port some of less important tests to the new setup, TODO's were
added, and we need comes they will be fixed. Getting started is however sacred
as usual and should work.

Other changes include:

-   gem5: update to 7fa4c946386e7207ad5859e8ade0bbfc14000d91

-   run: --tmux-args implies --tmux

-   run: add --userland-args to make userland arguments across QEMU and gem5

    Get rid of --userland-before as a consequence.

-   bring initrd and initramfs back to life

-   build-userland: create --static to make build a bit easier

-   gem5: --gem5-worktree also set --gem5-build-id

-   remove --gem5, use --emulator gem5 everywhere

    Allow passing --emulator multiple times for transparent tests selection
    just like --arch.

-   test-userland: allow selecting just a few tests

-   linux: update to v4.20

-   buildroot: update to 2018.08

    The main motivation for this was to fix the build for Ubuntu 18.10, which
    has glibc 2.28, which broke the 2018.05 build at the m4-host package with:

        #error "Please port gnulib fseeko.c to your platform!

-   getvar --type input

-   failed xen attempt, refactor timer, failed svc attempt, aarch64 use gicv3

-   build-doc: exit 1 on error, add to release testing

-   build: add --apt option to make things easier on other distros

-   build-linux: --no-modules-install
2019-01-22 00:00:00 +00:00

646 lines
29 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(
'--background', default=False,
help='''\
Send QEMU output to a file instead of the terminal so it does not require a
terminal attached to run on the background. Interactive input cannot be given.
TODO: use a port instead. If only there was a way to redirect a serial to multiple
places, both to a port and a file? We use the file currently to be able to have
any output at all.
https://superuser.com/questions/1373226/how-to-redirect-qemu-serial-output-to-both-a-file-and-the-terminal-or-a-port
'''
)
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.'
)
self.add_argument(
'--debug-vm-args', default='',
help='Pass arguments to GDB.'
)
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 string.
See: https://github.com/cirosantilli/linux-kernel-module-cheat#replace-init
'''
)
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=/poweroff.out'`
'''
)
self.add_argument(
'-F', '--eval-after',
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
'''
)
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(
'-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(
'--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(
'-K', '--kvm', default=False,
help='Use KVM. Only works if guest arch == host arch'
)
self.add_argument(
'--kgdb', default=False,
)
self.add_argument(
'--kdb', default=False,
)
self.add_argument(
'--gem5-restore', type=int,
help='''\
Restore the nth most recently taken gem5 checkpoint according to directory
timestamps.
'''
)
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(
'-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.
'''
)
self.add_argument(
'--trace-stdout', default=False,
help='''\
Output trace to stdout instead of a file. Only works for gem5 currently.
'''
)
self.add_argument(
'--terminal', default=False,
help='''\
Output to the terminal, don't pipe to tee as the default.
Does not save the output to a file, but allows you to use debuggers.
Set automatically by --debug-vm, but you still need this option to debug
gem5 Python scripts with pdb.
'''
)
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
'''
)
self.add_argument(
'--tmux-args',
help='''\
Parameters to pass to the program running on the tmux split. Implies --tmux.
'''
)
self.add_argument(
'-w', '--wait-gdb', default=False,
help='Wait for GDB to connect before starting execution'
)
self.add_argument(
'-x', '--graphic', default=False,
help='Run in graphic mode. Mnemonic: X11'
)
self.add_argument(
'-V', '--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 = True
# 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 = ''
extra_emulator_args = []
extra_qemu_args = []
if self.env['tmux_args'] is not None:
self.env['tmux'] = True
if self.env['debug_vm']:
debug_vm = ['gdb', LF, '-q', LF] + self.sh.shlex_split(self.env['debug_vm_args']) + ['--args', LF]
else:
debug_vm = []
if self.env['wait_gdb']:
extra_qemu_args.extend(['-S', LF])
if self.env['eval_after'] is not None:
kernel_cli_after_dash += ' lkmc_eval_base64="{}"'.format(self.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 += ' {}=/eval_base64.sh'.format(self.env['initarg'])
kernel_cli_after_dash += ' lkmc_eval="{}"'.format(self.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'] 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'])
if self.env['image'] is None:
raise Exception('Baremetal ELF file not found. Tried:\n' + '\n'.join(paths))
cmd = debug_vm.copy()
if self.env['emulator'] == 'gem5':
if self.env['quiet']:
show_stdout = False
if self.env['baremetal'] 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(prebuilt=self.env['prebuilt'], reverse=True)
else:
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)
if not os.path.exists(self.env['image']):
# This is to run gem5 from a prebuilt download.
if (not self.env['baremetal'] is None) or (not os.path.exists(self.env['linux_image'])):
raise_image_not_found()
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={}'.format(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.resolve_userland(self.env['userland']), 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])
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?
'--command-line', 'earlyprintk=pl011,0x1c090000 lpj=19988480 rw loglevel=8 mem={} root=/dev/sda {}'.format(memory, kernel_cli), LF,
'--machine-type', self.env['machine'], 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 None:
if not self.env['baremetal']:
cmd.extend(['--generate-dtb', LF])
else:
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'), NL])
if self.env['wait_gdb']:
# 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['wait_gdb']:
debug_args = ['-g', str(self.env['gdb_port']), LF]
else:
debug_args = []
cmd.extend(
[
os.path.join(self.env['qemu_build_dir'], '{}-linux-user'.format(self.env['arch']), 'qemu-{}'.format(self.env['arch'])), LF,
'-L', self.env['target_dir'], LF
] +
qemu_user_and_system_options +
debug_args
)
else:
if not os.path.exists(self.env['image']):
raise_image_not_found()
extra_emulator_args.extend(extra_qemu_args)
self.make_run_dirs()
if self.env['prebuilt'] or not os.path.exists(self.env['qemu_executable']):
qemu_executable = self.env['qemu_executable_basename']
qemu_executable_prebuilt = True
else:
qemu_executable = self.env['qemu_executable']
qemu_executable_prebuilt = False
qemu_executable = shutil.which(qemu_executable)
if qemu_executable is None:
raise Exception('QEMU executable not found, did you forget to build or install it?\n' \
'Tried to use: ' + qemu_executable)
if self.env['debug_vm']:
serial_monitor = []
else:
if self.env['background']:
serial_monitor = ['-serial', 'file:{}'.format(self.env['qemu_background_serial_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(
[
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 qemu_executable_prebuilt:
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(prebuilt=self.env['prebuilt'])
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['emulator'] == 'gem5':
tmux_cmd = './gem5-shell'
else:
tmux_cmd = './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 {} --linux-build-id '{}' --run-id '{}'".format(
self.env['arch'],
self.env['linux_build_id'],
self.env['run_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['tmux_args'] is not None:
tmux_args += ' {}'.format(self.env['tmux_args'])
subprocess.Popen([
os.path.join(self.env['root_dir'], 'tmu'),
"sleep 2;{} {}".format(tmux_cmd, tmux_args)
])
cmd.extend(extra_emulator_args)
cmd.extend(self.env['extra_emulator_args'])
if self.env['emulator'] == 'qemu' and self.env['userland']:
# The program and arguments must come at the every end of the CLI.
cmd.extend([self.resolve_userland(self.env['userland']), 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:
# Check if guest panicked.
if self.env['emulator'] == 'gem5':
# 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.
panic_msg = b'--- BEGIN LIBC BACKTRACE ---$'
else:
panic_msg = b'Kernel panic - not syncing'
panic_re = re.compile(panic_msg)
error_string_found = False
exit_status = 0
if out_file is not None and not self.env['dry_run']:
with open(self.env['termout_file'], 'br') as logfile:
line = None
for line in logfile:
if panic_re.search(line):
exit_status = 1
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:
for line in logfile.readlines():
if line.rstrip() == self.env['magic_fail_string']:
exit_status = 1
break
if exit_status != 0:
self.log_error('simulation error detected by parsing logs')
return exit_status
if __name__ == '__main__':
Main().cli()