run kind of runs

This commit is contained in:
Ciro Santilli 六四事件 法轮功
2018-12-09 00:00:01 +00:00
parent 5e20ba833b
commit fa1e4ffa7d
34 changed files with 848 additions and 838 deletions

View File

@@ -960,7 +960,7 @@ This automatically clears the GDB pane, and starts a new one.
Pass extra GDB arguments with:
....
./run --wait-gdb --tmux=start_kernel
./run --wait-gdb --tmux --tmux-args start_kernel
....
See the tmux manual for further details:
@@ -2986,7 +2986,8 @@ Or alternatively, if you are using <<tmux>>, do everything in one go with:
./run \
--arch aarch64 \
--userland print_argv \
--tmux=main \
--tmux \
--tmux-args main \
--wait-gdb \
-- \
asdf qwer \
@@ -5056,7 +5057,7 @@ If `CONFIG_KALLSYMS=n`, then addresses are shown on traces instead of symbol plu
In v4.16 it does not seem possible to configure that at runtime. GDB step debugging with:
....
./run --eval-after 'insmod /dump_stack.ko' --wait-gdb --tmux=dump_stack
./run --eval-after 'insmod /dump_stack.ko' --wait-gdb --tmux --tmux-args dump_stack
....
shows that traces are printed at `arch/x86/kernel/dumpstack.c`:
@@ -8168,7 +8169,7 @@ And in QEMU:
Or for a faster development loop:
....
./run --debug-vm='-ex "break edu_mmio_read" -ex "run"'
./run --debug-vm --debug-vm-args '-ex "break edu_mmio_read" -ex "run"'
....
When in <<qemu-text-mode>>, using `--debug-vm` makes Ctrl-C not get passed to the QEMU guest anymore: it is instead captured by GDB itself, so allow breaking. So e.g. you won't be able to easily quit from a guest program like:
@@ -10259,13 +10260,13 @@ then on the second shell:
Or if you are a <<tmux,tmux pro>>, do everything in one go with:
....
./run --arch arm --baremetal interactive/prompt --wait-gdb --tmux=main
./run --arch arm --baremetal interactive/prompt --wait-gdb --tmux --tmux-args main
....
Alternatively, to start from the very first executed instruction of our tiny <<baremetal-bootloaders>>:
....
./run --arch arm --baremetal interactive/prompt --wait-gdb --tmux=--no-continue
./run --arch arm --baremetal interactive/prompt --wait-gdb --tmux --tmux-args --no-continue
....
Now you can just `stepi` to when jumping into main to go to the C code in link:baremetal/interactive/prompt.c[].
@@ -10273,7 +10274,7 @@ Now you can just `stepi` to when jumping into main to go to the C code in link:b
This is specially interesting for the executables that don't use the bootloader from under `baremetal/arch/<arch>/no_bootloader/*.S`, e.g.:
....
./run --arch arm --baremetal arch/arm/no_bootloader/semihost_exit --wait-gdb --tmux=--no-continue
./run --arch arm --baremetal arch/arm/no_bootloader/semihost_exit --wait-gdb --tmux --tmux-args --no-continue
....
The cool thing about those examples is that you start at the very first instruction of your program, which gives more control.

View File

@@ -9,7 +9,7 @@ import common
build_linux = imp.load_source('build-linux', os.path.join(kwargs['root_dir'], 'build_linux'))
run = imp.load_source('run', os.path.join(kwargs['root_dir'], 'run'))
parser = common.get_argparse(
parser = self.get_argparse(
argparse_args={
'description': '''Bisect the Linux kernel on gem5 boots.
@@ -20,11 +20,11 @@ More information at: https://github.com/cirosantilli/linux-kernel-module-cheat#b
'linux_build_id': 'bisect',
},
)
args = common.setup(parser)
args = self.setup(parser)
# We need a clean rebuild because rebuilds at different revisions:
# - may fail
# - may not actually rebuild all files, e.g. on header changes
common.rmrf(kwargs['linux_build_dir'])
self.rmrf(kwargs['linux_build_dir'])
build_linux.LinuxComponent().do_build(args)
status = run.main(args, {
'eval': 'm5 exit',

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env python3
import common
parser = common.get_argparse(
parser = self.get_argparse(
argparse_args={'description':'Convert a BST vs heap stat file into a gnuplot input'}
)
args = common.setup(parser)
stats = common.get_stats()
args = self.setup(parser)
stats = self.get_stats()
it = iter(stats)
i = 1
for stat in it:

4
build
View File

@@ -315,9 +315,9 @@ Extra args to pass to all scripts.
parser.add_argument('components', choices=list(name_to_component_map.keys()) + [[]], default=[], nargs='*', help='''\
Which components to build. Default: qemu-buildroot
'''.format(kwargs['default_arch']))
common.add_dry_run_argument(parser)
self.add_dry_run_argument(parser)
args = parser.parse_args()
common.setup_dry_run_arguments(args)
self.setup_dry_run_arguments(args)
# Decide archs.
if kwargs['arch'] == []:

View File

@@ -4,9 +4,9 @@ import os
import common
class BaremetalComponent(common.Component):
class BaremetalComponent(self.Component):
def do_build(self, args):
common.assert_crosstool_ng_supports_arch(kwargs['arch'])
self.assert_crosstool_ng_supports_arch(kwargs['arch'])
build_dir = self.get_build_dir(args)
bootloader_obj = os.path.join(kwargs['baremetal_build_lib_dir'], 'bootloader{}'.format(kwargs['obj_ext']))
common_basename_noext = 'common'
@@ -28,7 +28,7 @@ class BaremetalComponent(common.Component):
gcc = 'arm-none-eabi-gcc'
else:
os.environ['PATH'] = kwargs['crosstool_ng_bin_dir'] + os.environ['PATH']
gcc = common.get_toolchain_tool('gcc', allowed_toolchains=['crosstool-ng'])
gcc = self.get_toolchain_tool('gcc', allowed_toolchains=['crosstool-ng'])
if kwargs['emulator'] == 'gem5':
if kwargs['machine'] == 'VExpress_GEM5_V1':
entry_address = 0x80000000
@@ -45,7 +45,7 @@ class BaremetalComponent(common.Component):
os.makedirs(build_dir, exist_ok=True)
os.makedirs(kwargs['baremetal_build_lib_dir'], exist_ok=True)
src = os.path.join(kwargs['baremetal_src_lib_dir'], '{}{}'.format(kwargs['arch'], kwargs['asm_ext']))
if common.need_rebuild([src], bootloader_obj):
if self.need_rebuild([src], bootloader_obj):
self.sh.run_cmd(
[gcc, LF] +
cflags +
@@ -59,7 +59,7 @@ class BaremetalComponent(common.Component):
(common_src, common_obj),
(syscalls_src, syscalls_obj),
]:
if common.need_rebuild([src], obj):
if self.need_rebuild([src], obj):
self.sh.run_cmd(
[gcc, LF] +
cflags +
@@ -149,7 +149,7 @@ Build the baremetal examples with crosstool-NG.
in_name = os.path.splitext(in_basename)[0]
main_obj = os.path.join(kwargs['baremetal_build_dir'], subpath, '{}{}'.format(in_name, kwargs['obj_ext']))
src = os.path.join(kwargs['baremetal_src_dir'], in_path)
if common.need_rebuild([src], main_obj):
if self.need_rebuild([src], main_obj):
self.sh.run_cmd(
[gcc, LF] +
cflags +
@@ -162,7 +162,7 @@ Build the baremetal examples with crosstool-NG.
objs = common_objs + [main_obj]
out = os.path.join(kwargs['baremetal_build_dir'], subpath, in_name + kwargs['baremetal_build_ext'])
link_script = os.path.join(kwargs['baremetal_src_dir'], 'link.ld')
if common.need_rebuild(objs + [link_script], out):
if self.need_rebuild(objs + [link_script], out):
self.sh.run_cmd(
[gcc, LF] +
cflags +

View File

@@ -10,10 +10,10 @@ import re
import common
class BuildrootComponent(common.Component):
class BuildrootComponent(self.Component):
def add_parser_arguments(self, parser):
parser.add_argument(
'--build-linux', default=self._defaults['build_linux'], action='store_true',
'--build-linux', default=self._defaults['build_linux'],
help='''\
Enable building the Linux kernel with Buildroot. This is done mostly
to extract Buildroot's default kernel configurations when updating Buildroot.
@@ -22,7 +22,7 @@ not currently supported, juse use ./build-linux script if you want to do that.
'''
)
parser.add_argument(
'--baseline', default=self._defaults['baseline'], action='store_true',
'--baseline', default=self._defaults['baseline'],
help='''Do a default-ish Buildroot defconfig build, without any of our extra options.
Mostly to track how much slower we are than a basic build.
'''
@@ -42,14 +42,14 @@ Pass multiple times to use multiple fragment files.
'''
)
parser.add_argument(
'--no-all', default=self._defaults['no_all'], action='store_true',
'--no-all', default=self._defaults['no_all'],
help='''\
Don't build the all target which normally gets build by default.
That target builds the root filesystem and all its dependencies.
'''
)
parser.add_argument(
'--no-overlay', default=self._defaults['no_all'], action='store_true',
'--no-overlay', default=self._defaults['no_all'],
help='''\
Don't add our overlay which contains all files we build without going through Buildroot.
This prevents us from overwriting certain Buildroot files. Remember however that you must
@@ -140,7 +140,7 @@ usually extra Buildroot targets.
],
cwd=kwargs['buildroot_src_dir'],
)
common.make_build_dirs()
self.make_build_dirs()
if not kwargs['no_all']:
extra_make_args.extend(['all', LF])
self.sh.run_cmd(
@@ -161,7 +161,7 @@ usually extra Buildroot targets.
# Skip if qemu is not present, because gem5 does not need the qcow2.
# so we don't force a QEMU build for gem5.
if not kwargs['no_all'] and os.path.exists(kwargs['qemu_img_executable']):
common.raw_to_qcow2()
self.raw_to_qcow2()
def get_argparse_args(self):
return {

View File

@@ -4,9 +4,9 @@ import os
import common
class CrosstoolNgComponent(common.Component):
class CrosstoolNgComponent(self.Component):
def do_build(self, args):
common.assert_crosstool_ng_supports_arch(kwargs['arch'])
self.assert_crosstool_ng_supports_arch(kwargs['arch'])
build_dir = self.get_build_dir(args)
defconfig_dest = os.path.join(kwargs['crosstool_ng_util_dir'], 'defconfig')
os.makedirs(kwargs['crosstool_ng_util_dir'], exist_ok=True)
@@ -37,7 +37,7 @@ class CrosstoolNgComponent(common.Component):
os.path.join(kwargs['root_dir'], 'crosstool_ng_config', kwargs['arch']),
defconfig_dest
)
common.write_configs(
self.write_configs(
kwargs['crosstool_ng_defconfig'],
[
'CT_PREFIX_DIR="{}"'.format(kwargs['crosstool_ng_install_dir']),

View File

@@ -7,7 +7,7 @@ import tarfile
import common
class DockerComponent(common.Component):
class DockerComponent(self.Component):
def get_argparse_args(self):
return {
'description': '''\
@@ -68,7 +68,7 @@ See also:https://github.com/cirosantilli/linux-kernel-module-cheat#ubuntu-guest-
kwargs['docker_tar_dir'],
kwargs['docker_rootfs_raw_file'],
])
common.raw_to_qcow2(prebuilt=True)
self.raw_to_qcow2(prebuilt=True)
def get_build_dir(self, args):
return kwargs['docker_build_dir']

View File

@@ -37,7 +37,7 @@ Still uses options explicitly passed with `--config` and
'''
)
self.add_argument(
'--config-only', default=False, action='store_true',
'--config-only', default=False,
help='''\
Configure the kernel, but don't build it.
'''
@@ -91,7 +91,7 @@ Configure the kernel, but don't build it.
if self.env['config'] != []:
cli_config_fragment_path = os.path.join(build_dir, 'lkmc_cli_config_fragment')
cli_config_str = '\n'.join(self.env['config'])
common.write_string_to_file(cli_config_fragment_path, cli_config_str)
self.write_string_to_file(cli_config_fragment_path, cli_config_str)
config_fragments.append(cli_config_fragment_path)
self.sh.cp(
base_config_file,
@@ -133,7 +133,7 @@ Configure the kernel, but don't build it.
)
# TODO: remove build and source https://stackoverflow.com/questions/13578618/what-does-build-and-source-link-do-in-lib-modules-kernel-version
# TODO Basically all kernel modules also basically leak full host paths. Just terrible. Buildroot deals with that stuff nicely for us.
# common.rmrf()
# self.rmrf()
def get_build_dir(self):
return self.env['linux_build_dir']

View File

@@ -4,11 +4,11 @@ import os
import common
class M5Component(common.Component):
class M5Component(self.Component):
def get_make_cmd(self, args):
allowed_toolchains = ['buildroot']
cc = common.get_toolchain_tool('gcc', allowed_toolchains=allowed_toolchains)
ld = common.get_toolchain_tool('ld', allowed_toolchains=allowed_toolchains)
cc = self.get_toolchain_tool('gcc', allowed_toolchains=allowed_toolchains)
ld = self.get_toolchain_tool('ld', allowed_toolchains=allowed_toolchains)
if kwargs['arch'] == 'x86_64':
arch = 'x86'
else:

View File

@@ -7,7 +7,7 @@ import shutil
import common
class ModulesComponent(common.Component):
class ModulesComponent(self.Component):
def add_parser_arguments(self, parser):
parser.add_argument(
'--make-args',
@@ -15,7 +15,6 @@ class ModulesComponent(common.Component):
)
parser.add_argument(
'--host',
action='store_true',
default=False,
help='''\
Build the Linux kernel modules for the host instead of guest.
@@ -70,7 +69,7 @@ Use the host packaged cross toolchain.
else:
allowed_toolchains = None
build_subdir = kwargs['kernel_modules_build_subdir']
gcc = common.get_toolchain_tool(tool, allowed_toolchains=allowed_toolchains)
gcc = self.get_toolchain_tool(tool, allowed_toolchains=allowed_toolchains)
prefix = gcc[:-len(tool)]
ccache = shutil.which('ccache')
if ccache is not None:
@@ -97,13 +96,13 @@ Use the host packaged cross toolchain.
'M={}'.format(build_subdir), LF,
'OBJECT_FILES={}'.format(' '.join(object_files)), LF,
] +
common.shlex_split(kwargs['make_args']) +
self.sh.shlex_split(kwargs['make_args']) +
verbose
),
cwd=os.path.join(kwargs['kernel_modules_build_subdir']),
)
if not kwargs['host']:
common.copy_dir_if_update_non_recursive(
self.copy_dir_if_update_non_recursive(
srcdir=kwargs['kernel_modules_build_subdir'],
destdir=kwargs['out_rootfs_overlay_dir'],
filter_ext=kwargs['kernel_module_ext'],

View File

@@ -4,12 +4,11 @@ import os
import common
class QemuComponent(common.Component):
class QemuComponent(self.Component):
def add_parser_arguments(self, parser):
parser.add_argument(
'--userland',
default=False,
action='store_true',
help='Build QEMU user mode instead of system.',
)
parser.add_argument(

View File

@@ -8,7 +8,7 @@ import subprocess
import common
class UserlandComponent(common.Component):
class UserlandComponent(self.Component):
def add_parser_arguments(self, parser):
parser.add_argument(
'--has-package',
@@ -21,7 +21,6 @@ allows us to build examples that rely on it.
)
parser.add_argument(
'--host',
action='store_true',
default=False,
help='''\
Build the userland programs for the host instead of guest.
@@ -51,8 +50,8 @@ has the OpenBLAS libraries and headers installed.
allowed_toolchains = ['host']
else:
allowed_toolchains = ['buildroot']
cc = common.get_toolchain_tool('gcc', allowed_toolchains=allowed_toolchains)
cxx = common.get_toolchain_tool('g++', allowed_toolchains=allowed_toolchains)
cc = self.get_toolchain_tool('gcc', allowed_toolchains=allowed_toolchains)
cxx = self.get_toolchain_tool('g++', allowed_toolchains=allowed_toolchains)
self.sh.run_cmd(
(
[
@@ -74,7 +73,7 @@ has the OpenBLAS libraries and headers installed.
cwd=kwargs['userland_src_dir'],
extra_paths=[kwargs['ccache_dir']],
)
common.copy_dir_if_update_non_recursive(
self.copy_dir_if_update_non_recursive(
srcdir=build_dir,
destdir=kwargs['out_rootfs_overlay_dir'],
filter_ext=kwargs['userland_build_ext'],

73
cli_function.py Normal file → Executable file
View File

@@ -41,15 +41,17 @@ class Argument:
if nargs is not None:
self.kwargs['nargs'] = nargs
if default is True:
self.kwargs['action'] = 'store_false'
bool_action = 'store_false'
self.is_bool = True
elif default is False:
self.kwargs['action'] = 'store_true'
bool_action = 'store_true'
self.is_bool = True
else:
self.is_bool = False
if default is None and nargs in ('*', '+'):
default = []
if self.is_bool and not 'action' in kwargs:
self.kwargs['action'] = bool_action
if help is not None:
if not self.is_bool and default:
help += ' Default: {}'.format(default)
@@ -131,6 +133,9 @@ class CliFunction:
help='Path to the configuration file to use'
)
def __str__(self):
return '\n'.join(str(arg) for arg in self._arguments)
def add_argument(
self,
*args,
@@ -155,11 +160,10 @@ class CliFunction:
new_longname = '--no' + argument.longname[1:]
kwargs = argument.kwargs.copy()
kwargs['default'] = not argument.default
if argument.default:
action = 'store_true'
else:
action = 'store_false'
kwargs['action'] = action
if kwargs['action'] == 'store_false':
kwargs['action'] = 'store_true'
elif kwargs['action'] == 'store_true':
kwargs['action'] = 'store_false'
if 'help' in kwargs:
del kwargs['help']
parser.add_argument(new_longname, dest=argument.key, **kwargs)
@@ -188,6 +192,7 @@ amazing function!
self.add_argument('-q', '--qwer', default='Q', help='Help for qwer'),
self.add_argument('-b', '--bool', default=True, help='Help for bool'),
self.add_argument('--bool-cli', default=False, help='Help for bool'),
self.add_argument('--bool-nargs', default=False, nargs='?', action='store', const='')
self.add_argument('--no-default', help='Help for no-bool'),
self.add_argument('pos-mandatory', help='Help for pos-mandatory', type=int),
self.add_argument('pos-optional', default=0, help='Help for pos-optional', type=int),
@@ -196,32 +201,62 @@ amazing function!
del kwargs['config_file']
return kwargs
# Code calls.
default = OneCliFunction()(pos_mandatory=1)
assert default == {'asdf': 'A', 'qwer': 'Q', 'bool': True, 'bool_cli': True, 'no_default': None, 'pos_mandatory': 1, 'pos_optional': 0, 'args_star': []}
one_cli_function = OneCliFunction()
# Default code call.
default = one_cli_function(pos_mandatory=1)
assert default == {
'asdf': 'A',
'qwer': 'Q',
'bool': True,
'bool_nargs': False,
'bool_cli': True,
'no_default': None,
'pos_mandatory': 1,
'pos_optional': 0,
'args_star': []
}
# Default CLI call.
out = one_cli_function.cli(['1'])
assert out == default
# asdf
out = OneCliFunction()(pos_mandatory=1, asdf='B')
out = one_cli_function(pos_mandatory=1, asdf='B')
assert out['asdf'] == 'B'
out['asdf'] = default['asdf']
assert(out == default)
# asdf and qwer
out = OneCliFunction()(pos_mandatory=1, asdf='B', qwer='R')
out = one_cli_function(pos_mandatory=1, asdf='B', qwer='R')
assert out['asdf'] == 'B'
assert out['qwer'] == 'R'
out['asdf'] = default['asdf']
out['qwer'] = default['qwer']
assert(out == default)
# bool
out = OneCliFunction()(pos_mandatory=1, bool=False)
assert out['bool'] == False
out['bool'] = default['bool']
assert(out == default)
if '--bool':
out = one_cli_function(pos_mandatory=1, bool=False)
cli_out = one_cli_function.cli(['--bool', '1'])
assert out == cli_out
assert out['bool'] == False
out['bool'] = default['bool']
assert(out == default)
if '--bool-nargs':
out = one_cli_function(pos_mandatory=1, bool_nargs=True)
assert out['bool_nargs'] == True
out['bool_nargs'] = default['bool_nargs']
assert(out == default)
out = one_cli_function(pos_mandatory=1, bool_nargs='asdf')
assert out['bool_nargs'] == 'asdf'
out['bool_nargs'] = default['bool_nargs']
assert(out == default)
# Force a boolean value set on the config to be False on CLI.
assert OneCliFunction().cli(['--no-bool-cli', '1'])['bool_cli'] is False
assert one_cli_function.cli(['--no-bool-cli', '1'])['bool_cli'] is False
# CLI call.
print(OneCliFunction().cli())
print(one_cli_function.cli())

349
common.py
View File

@@ -13,7 +13,6 @@ import os
import re
import shutil
import signal
import stat
import subprocess
import sys
import time
@@ -120,7 +119,7 @@ mkdir are generally omitted since those are obvious
'''
)
self.add_argument(
'-v', '--verbose', default=False, action='store_true',
'-v', '--verbose', default=False,
help='Show full compilation commands when they are not shown by default.'
)
@@ -163,10 +162,10 @@ Linux build ID. Allows you to keep multiple separate Linux builds.
'''
)
self.add_argument(
'--initramfs', default=False, action='store_true',
'--initramfs', default=False,
)
self.add_argument(
'--initrd', default=False, action='store_true',
'--initrd', default=False,
)
# Baremetal.
@@ -189,7 +188,7 @@ inside baremetal/ and then try to use corresponding executable.
help='Buildroot build ID. Allows you to keep multiple separate gem5 builds.'
)
self.add_argument(
'--buildroot-linux', default=False, action='store_true',
'--buildroot-linux', default=False,
help='Boot with the Buildroot Linux kernel instead of our custom built one. Mostly for sanity checks.'
)
@@ -199,7 +198,7 @@ inside baremetal/ and then try to use corresponding executable.
help='Crosstool-NG build ID. Allows you to keep multiple separate crosstool-NG builds.'
)
self.add_argument(
'--docker', default=False, action='store_true',
'--docker', default=False,
help='''\
Use the docker download Ubuntu root filesystem instead of the default Buildroot one.
'''
@@ -234,7 +233,7 @@ and then inspect separate outputs later in different output directories.
'''
)
self.add_argument(
'-P', '--prebuilt', default=False, action='store_true',
'-P', '--prebuilt', default=False,
help='''\
Use prebuilt packaged host utilities as much as possible instead
of the ones we built ourselves. Saves build time, but decreases
@@ -251,11 +250,11 @@ instances in parallel. Default: the run ID (-n) if that is an integer, otherwise
# Misc.
self.add_argument(
'-g', '--gem5', default=False, action='store_true',
'-g', '--gem5', default=False,
help='Use gem5 instead of QEMU.'
)
self.add_argument(
'--qemu', default=False, action='store_true',
'--qemu', default=False,
help='''\
Use QEMU as the emulator. This option exists in addition to --gem5
to allow overriding configs from the CLI.
@@ -426,6 +425,7 @@ to allow overriding configs from the CLI.
env['executable'] = env['qemu_executable']
env['run_dir'] = env['qemu_run_dir']
env['termout_file'] = env['qemu_termout_file']
env['guest_terminal_file'] = env['qemu_termout_file']
env['trace_txt_file'] = env['qemu_trace_txt_file']
env['run_cmd_file'] = join(env['run_dir'], 'run.sh')
@@ -497,7 +497,7 @@ to allow overriding configs from the CLI.
if env['baremetal'] == 'all':
path = env['baremetal']
else:
path = resolve_executable(
path = self.resolve_executable(
env['baremetal'],
env['baremetal_src_dir'],
env['baremetal_build_dir'],
@@ -515,6 +515,23 @@ to allow overriding configs from the CLI.
env['image'] = path
self.env = env
def assert_crosstool_ng_supports_arch(self, arch):
if arch not in self.env['crosstool_ng_supported_archs']:
raise Exception('arch not yet supported: ' + arch)
@staticmethod
def base64_encode(string):
return base64.b64encode(string.encode()).decode()
def gem_list_checkpoint_dirs(self):
'''
List checkpoint directory, oldest first.
'''
prefix_re = re.compile(self.env['gem5_cpt_prefix'])
files = list(filter(lambda x: os.path.isdir(os.path.join(self.env['m5out_dir'], x)) and prefix_re.search(x), os.listdir(self.env['m5out_dir'])))
files.sort(key=lambda x: os.path.getmtime(os.path.join(self.env['m5out_dir'], x)))
return files
def get_elf_entry(self, elf_file_path):
readelf_header = subprocess.check_output([
self.get_toolchain_tool('readelf'),
@@ -528,6 +545,21 @@ to allow overriding configs from the CLI.
break
return int(addr, 0)
def get_stats(self, stat_re=None, stats_file=None):
if stat_re is None:
stat_re = '^system.cpu[0-9]*.numCycles$'
if stats_file is None:
stats_file = self.env['stats_file']
stat_re = re.compile(stat_re)
ret = []
with open(stats_file, 'r') as statfile:
for line in statfile:
if line[0] != '-':
cols = line.split()
if len(cols) > 1 and stat_re.search(cols[0]):
ret.append(cols[1])
return ret
def get_toolchain_prefix(self, tool, allowed_toolchains=None):
buildroot_full_prefix = os.path.join(self.env['host_bin_dir'], self.env['buildroot_toolchain_prefix'])
buildroot_exists = os.path.exists('{}-{}'.format(buildroot_full_prefix, tool))
@@ -562,6 +594,41 @@ to allow overriding configs from the CLI.
def get_toolchain_tool(self, tool, allowed_toolchains=None):
return '{}-{}'.format(self.get_toolchain_prefix(tool, allowed_toolchains), tool)
@staticmethod
def github_make_request(
authenticate=False,
data=None,
extra_headers=None,
path='',
subdomain='api',
url_params=None,
**extra_request_args
):
if extra_headers is None:
extra_headers = {}
headers = {'Accept': 'application/vnd.github.v3+json'}
headers.update(extra_headers)
if authenticate:
headers['Authorization'] = 'token ' + os.environ['LKMC_GITHUB_TOKEN']
if url_params is not None:
path += '?' + urllib.parse.urlencode(url_params)
request = urllib.request.Request(
'https://' + subdomain + '.github.com/repos/' + github_repo_id + path,
headers=headers,
data=data,
**extra_request_args
)
response_body = urllib.request.urlopen(request).read().decode()
if response_body:
_json = json.loads(response_body)
else:
_json = {}
return _json
@staticmethod
def log_error(msg):
print('error: {}'.format(msg), file=sys.stderr)
def main(self, **kwargs):
'''
Time the main of the derived class.
@@ -571,13 +638,107 @@ to allow overriding configs from the CLI.
kwargs.update(consts)
self._init_env(kwargs)
self.sh = shell_helpers.ShellHelpers(dry_run=self.env['dry_run'])
self.timed_main()
ret = self.timed_main()
if not kwargs['dry_run']:
end_time = time.time()
print_time(end_time - start_time)
self.print_time(end_time - start_time)
return ret
def run_cmd(self, *args, **kwargs):
self.sh.run_cmd(*args, **kwargs)
def make_build_dirs(self):
os.makedirs(self.env['buildroot_build_build_dir'], exist_ok=True)
os.makedirs(self.env['gem5_build_dir'], exist_ok=True)
os.makedirs(self.env['out_rootfs_overlay_dir'], exist_ok=True)
def make_run_dirs(self):
'''
Make directories required for the run.
The user could nuke those anytime between runs to try and clean things up.
'''
os.makedirs(self.env['gem5_run_dir'], exist_ok=True)
os.makedirs(self.env['p9_dir'], exist_ok=True)
os.makedirs(self.env['qemu_run_dir'], exist_ok=True)
@staticmethod
def need_rebuild(srcs, dst):
if not os.path.exists(dst):
return True
for src in srcs:
if os.path.getmtime(src) > os.path.getmtime(dst):
return True
return False
@staticmethod
def print_time(ellapsed_seconds):
hours, rem = divmod(ellapsed_seconds, 3600)
minutes, seconds = divmod(rem, 60)
print("time {:02}:{:02}:{:02}".format(int(hours), int(minutes), int(seconds)))
def raw_to_qcow2(eslf, prebuilt=False, reverse=False):
if prebuilt or not os.path.exists(self.env['qemu_img_executable']):
disable_trace = []
qemu_img_executable = self.env['qemu_img_basename']
else:
# Prevent qemu-img from generating trace files like QEMU. Disgusting.
disable_trace = ['-T', 'pr_manager_run,file=/dev/null', LF,]
qemu_img_executable = self.env['qemu_img_executable']
infmt = 'raw'
outfmt = 'qcow2'
infile = self.env['rootfs_raw_file']
outfile = self.env['qcow2_file']
if reverse:
tmp = infmt
infmt = outfmt
outfmt = tmp
tmp = infile
infile = outfile
outfile = tmp
self.sh.run_cmd(
[
qemu_img_executable, LF,
] +
disable_trace +
[
'convert', LF,
'-f', infmt, LF,
'-O', outfmt, LF,
infile, LF,
outfile, LF,
]
)
@staticmethod
def resolve_args(defaults, args, extra_args):
if extra_args is None:
extra_args = {}
argcopy = copy.copy(args)
argcopy.__dict__ = dict(list(defaults.items()) + list(argcopy.__dict__.items()) + list(extra_args.items()))
return argcopy
@staticmethod
def resolve_executable(in_path, magic_in_dir, magic_out_dir, out_ext):
if os.path.isabs(in_path):
return in_path
else:
paths = [
os.path.join(magic_out_dir, in_path),
os.path.join(
magic_out_dir,
os.path.relpath(in_path, magic_in_dir),
)
]
paths[:] = [os.path.splitext(path)[0] + out_ext for path in paths]
for path in paths:
if os.path.exists(path):
return path
raise Exception('Executable file not found. Tried:\n' + '\n'.join(paths))
def resolve_userland(self, path):
return self.resolve_executable(
path,
self.env['userland_src_dir'],
self.env['userland_build_dir'],
self.env['userland_build_ext'],
)
def timed_main(self):
'''
@@ -628,162 +789,6 @@ class BuildCliFunction(LkmcCliFunction):
The actual build work is done by do_build in implementing classes.
'''
if self.env['clean']:
self.clean()
return self.clean()
else:
self.build()
def assert_crosstool_ng_supports_arch(arch):
if arch not in kwargs['crosstool_ng_supported_archs']:
raise Exception('arch not yet supported: ' + arch)
def base64_encode(string):
return base64.b64encode(string.encode()).decode()
def gem_list_checkpoint_dirs():
'''
List checkpoint directory, oldest first.
'''
prefix_re = re.compile(kwargs['gem5_cpt_prefix'])
files = list(filter(lambda x: os.path.isdir(os.path.join(kwargs['m5out_dir'], x)) and prefix_re.search(x), os.listdir(kwargs['m5out_dir'])))
files.sort(key=lambda x: os.path.getmtime(os.path.join(kwargs['m5out_dir'], x)))
return files
def get_stats(stat_re=None, stats_file=None):
if stat_re is None:
stat_re = '^system.cpu[0-9]*.numCycles$'
if stats_file is None:
stats_file = kwargs['stats_file']
stat_re = re.compile(stat_re)
ret = []
with open(stats_file, 'r') as statfile:
for line in statfile:
if line[0] != '-':
cols = line.split()
if len(cols) > 1 and stat_re.search(cols[0]):
ret.append(cols[1])
return ret
def github_make_request(
authenticate=False,
data=None,
extra_headers=None,
path='',
subdomain='api',
url_params=None,
**extra_request_args
):
if extra_headers is None:
extra_headers = {}
headers = {'Accept': 'application/vnd.github.v3+json'}
headers.update(extra_headers)
if authenticate:
headers['Authorization'] = 'token ' + os.environ['LKMC_GITHUB_TOKEN']
if url_params is not None:
path += '?' + urllib.parse.urlencode(url_params)
request = urllib.request.Request(
'https://' + subdomain + '.github.com/repos/' + github_repo_id + path,
headers=headers,
data=data,
**extra_request_args
)
response_body = urllib.request.urlopen(request).read().decode()
if response_body:
_json = json.loads(response_body)
else:
_json = {}
return _json
def log_error(msg):
print('error: {}'.format(msg), file=sys.stderr)
def make_build_dirs():
os.makedirs(kwargs['buildroot_build_build_dir'], exist_ok=True)
os.makedirs(kwargs['gem5_build_dir'], exist_ok=True)
os.makedirs(kwargs['out_rootfs_overlay_dir'], exist_ok=True)
def make_run_dirs():
'''
Make directories required for the run.
The user could nuke those anytime between runs to try and clean things up.
'''
os.makedirs(kwargs['gem5_run_dir'], exist_ok=True)
os.makedirs(kwargs['p9_dir'], exist_ok=True)
os.makedirs(kwargs['qemu_run_dir'], exist_ok=True)
def need_rebuild(srcs, dst):
if not os.path.exists(dst):
return True
for src in srcs:
if os.path.getmtime(src) > os.path.getmtime(dst):
return True
return False
def print_time(ellapsed_seconds):
hours, rem = divmod(ellapsed_seconds, 3600)
minutes, seconds = divmod(rem, 60)
print("time {:02}:{:02}:{:02}".format(int(hours), int(minutes), int(seconds)))
def raw_to_qcow2(prebuilt=False, reverse=False):
if prebuilt or not os.path.exists(kwargs['qemu_img_executable']):
disable_trace = []
qemu_img_executable = kwargs['qemu_img_basename']
else:
# Prevent qemu-img from generating trace files like QEMU. Disgusting.
disable_trace = ['-T', 'pr_manager_run,file=/dev/null', LF,]
qemu_img_executable = kwargs['qemu_img_executable']
infmt = 'raw'
outfmt = 'qcow2'
infile = kwargs['rootfs_raw_file']
outfile = kwargs['qcow2_file']
if reverse:
tmp = infmt
infmt = outfmt
outfmt = tmp
tmp = infile
infile = outfile
outfile = tmp
self.sh.run_cmd(
[
qemu_img_executable, LF,
] +
disable_trace +
[
'convert', LF,
'-f', infmt, LF,
'-O', outfmt, LF,
infile, LF,
outfile, LF,
]
)
def resolve_args(defaults, args, extra_args):
if extra_args is None:
extra_args = {}
argcopy = copy.copy(args)
argcopy.__dict__ = dict(list(defaults.items()) + list(argcopy.__dict__.items()) + list(extra_args.items()))
return argcopy
def resolve_executable(in_path, magic_in_dir, magic_out_dir, out_ext):
if os.path.isabs(in_path):
return in_path
else:
paths = [
os.path.join(magic_out_dir, in_path),
os.path.join(
magic_out_dir,
os.path.relpath(in_path, magic_in_dir),
)
]
paths[:] = [os.path.splitext(path)[0] + out_ext for path in paths]
for path in paths:
if os.path.exists(path):
return path
raise Exception('Executable file not found. Tried:\n' + '\n'.join(paths))
def resolve_userland(path):
return resolve_executable(
path,
kwargs['userland_src_dir'],
kwargs['userland_build_dir'],
kwargs['userland_build_ext'],
)
return self.build()

View File

@@ -6,7 +6,7 @@ import shutil
import common
class CopyOverlayComponent(common.Component):
class CopyOverlayComponent(self.Component):
def do_build(self, args):
distutils.dir_util.copy_tree(
kwargs['rootfs_overlay_dir'],

View File

@@ -4,11 +4,11 @@ import sys
import common
parser = common.get_argparse(
parser = self.get_argparse(
default_args={'gem5':True},
argparse_args={'description':'Connect a terminal to a running gem5 instance'}
)
args = common.setup(parser)
args = self.setup(parser)
sys.exit(self.sh.run_cmd([
kwargs['gem5_m5term'], LF,
'localhost', LF,

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
import common
parser = common.get_argparse(
parser = self.get_argparse(
argparse_args={'description':'Get the value of a gem5 stat from the stats.txt file.'}
)
parser.add_argument(
@@ -9,6 +9,6 @@ parser.add_argument(
help='Python regexp matching the full stat name of interest',
nargs='?',
)
args = common.setup(parser)
stats = common.get_stats(kwargs['stat'])
args = self.setup(parser)
stats = self.get_stats(kwargs['stat'])
print('\n'.join(stats))

4
getvar
View File

@@ -4,7 +4,7 @@ import types
import common
parser = common.get_argparse(argparse_args={
parser = self.get_argparse(argparse_args={
'description': '''Print the value of a kwargs['py'] variable.
This is useful to:
@@ -27,7 +27,7 @@ List all available variables:
'''
})
parser.add_argument('variable', nargs='?')
args = common.setup(parser)
args = self.setup(parser)
if kwargs['variable']:
print(getattr(common, kwargs['variable']))
else:

View File

@@ -8,7 +8,7 @@ import common
prompt = b'\n(qemu) '
parser = common.get_argparse({
parser = self.get_argparse({
'description': '''\
Run a command on the QEMU monitor of a running QEMU instance
@@ -21,7 +21,7 @@ parser.add_argument(
help='If given, run this command and quit',
nargs='*',
)
args = common.setup(parser)
args = self.setup(parser)
def write_and_read(tn, cmd, prompt):
tn.write(cmd.encode('utf-8'))

View File

@@ -19,8 +19,8 @@ def main():
)
if __name__ == '__main__':
parser = common.get_argparse(argparse_args={
parser = self.get_argparse(argparse_args={
'description': 'Convert a QEMU `-trace exec_tb` to text form.'
})
args = common.setup(parser)
args = self.setup(parser)
sys.exit(main())

View File

@@ -29,4 +29,4 @@ release_zip.main()
subprocess.check_call(['git', 'push'])
release_upload.main()
end_time = time.time()
common.print_time(end_time - start_time)
self.print_time(end_time - start_time)

View File

@@ -11,6 +11,6 @@ import urllib.request
import common
_json = common.github_make_request(path='/releases')
_json = self.github_make_request(path='/releases')
asset = _json[0]['assets'][0]
urllib.request.urlretrieve(asset['browser_download_url'], asset['name'])

View File

@@ -24,7 +24,7 @@ def main():
# Check the release already exists.
try:
_json = common.github_make_request(path='/releases/tags/' + tag)
_json = self.github_make_request(path='/releases/tags/' + tag)
except urllib.error.HTTPError as e:
if e.code == 404:
release_exists = False
@@ -36,7 +36,7 @@ def main():
# Create release if not yet created.
if not release_exists:
_json = common.github_make_request(
_json = self.github_make_request(
authenticate=True,
data=json.dumps({
'tag_name': tag,
@@ -50,12 +50,12 @@ def main():
asset_name = os.path.split(upload_path)[1]
# Clear the prebuilts for a upload.
_json = common.github_make_request(
_json = self.github_make_request(
path=('/releases/' + str(release_id) + '/assets'),
)
for asset in _json:
if asset['name'] == asset_name:
_json = common.github_make_request(
_json = self.github_make_request(
authenticate=True,
path=('/releases/assets/' + str(asset['id'])),
method='DELETE',
@@ -65,7 +65,7 @@ def main():
# Upload the prebuilt.
with open(upload_path, 'br') as myfile:
content = myfile.read()
_json = common.github_make_request(
_json = self.github_make_request(
authenticate=True,
data=content,
extra_headers={'Content-Type': 'application/zip'},

View File

@@ -16,7 +16,7 @@ def main():
os.unlink(kwargs['release_zip_file'])
zipf = zipfile.ZipFile(kwargs['release_zip_file'], 'w', zipfile.ZIP_DEFLATED)
for arch in kwargs['all_archs']:
common.setup(common.get_argparse(default_args={'arch': arch}))
self.setup(common.get_argparse(default_args={'arch': arch}))
zipf.write(kwargs['qcow2_file'], arcname=os.path.relpath(kwargs['qcow2_file'], kwargs['root_dir']))
zipf.write(kwargs['linux_image'], arcname=os.path.relpath(kwargs['linux_image'], kwargs['root_dir']))
zipf.close()

1051
run

File diff suppressed because it is too large Load Diff

View File

@@ -58,7 +58,7 @@ cmd_action_map = {
parser = argparse.ArgumentParser()
parser.add_argument('cmd', choices=cmd_action_map)
parser.add_argument('args', nargs='*')
common.add_dry_run_argument(parser)
self.add_dry_run_argument(parser)
args = parser.parse_args()
common.setup_dry_run_arguments(args)
self.setup_dry_run_arguments(args)
cmd_action_map[kwargs['cmd']](kwargs['args'])

28
run-gdb
View File

@@ -34,8 +34,8 @@ class GdbTestcase:
'''
self.prompt = '\(gdb\) '
self.source_path = source_path
common.print_cmd(cmd)
cmd = common.strip_newlines(cmd)
self.print_cmd(cmd)
cmd = self.strip_newlines(cmd)
import pexpect
self.child = pexpect.spawn(
cmd[0],
@@ -97,9 +97,9 @@ def main(args, extra_args=None):
:rtype: int
'''
global defaults
args = common.resolve_args(defaults, args, extra_args)
after = common.shlex_split(kwargs['after'])
before = common.shlex_split(kwargs['before'])
args = self.resolve_args(defaults, args, extra_args)
after = self.sh.shlex_split(kwargs['after'])
before = self.sh.shlex_split(kwargs['before'])
no_continue = kwargs['no_continue']
if kwargs['test']:
no_continue = True
@@ -122,7 +122,7 @@ def main(args, extra_args=None):
break_at = []
linux_full_system = (kwargs['baremetal'] is None and kwargs['userland'] is None)
if kwargs['userland']:
image = common.resolve_userland(kwargs['userland'])
image = self.resolve_userland(kwargs['userland'])
elif kwargs['baremetal']:
image = kwargs['image']
test_script_path = os.path.splitext(kwargs['source_path'])[0] + '.py'
@@ -133,7 +133,7 @@ def main(args, extra_args=None):
else:
allowed_toolchains = ['buildroot', 'crosstool-ng', 'host']
cmd = (
[common.get_toolchain_tool('gdb', allowed_toolchains=allowed_toolchains), LF] +
[self.get_toolchain_tool('gdb', allowed_toolchains=allowed_toolchains), LF] +
before +
['-q', LF]
)
@@ -195,7 +195,7 @@ def main(args, extra_args=None):
)
if __name__ == '__main__':
parser = common.get_argparse(argparse_args={'description': 'Connect with GDB to an emulator to debug Linux itself'})
parser = self.get_argparse(argparse_args={'description': 'Connect with GDB to an emulator to debug Linux itself'})
parser.add_argument(
'-A', '--after', default=defaults['after'],
help='Pass extra arguments to GDB, to be appended after all other arguments'
@@ -205,23 +205,23 @@ if __name__ == '__main__':
help='Pass extra arguments to GDB to be prepended before any of the arguments passed by this script'
)
parser.add_argument(
'-C', '--no-continue', default=defaults['no_continue'], action='store_true',
'-C', '--no-continue', default=defaults['no_continue'],
help="Don't run continue after connecting"
)
parser.add_argument(
'-k', '--kgdb', default=defaults['kgdb'], action='store_true'
'-k', '--kgdb', default=defaults['kgdb'],
)
parser.add_argument(
'--sim', default=defaults['sim'], action='store_true',
'--sim', default=defaults['sim'],
help='''Use the built-in GDB CPU simulator
See: https://github.com/cirosantilli/linux-kernel-module-cheat#gdb-builtin-cpu-simulator
'''
)
parser.add_argument(
'-X', '--no-lxsymbols', default=defaults['no_lxsymbols'], action='store_true'
'-X', '--no-lxsymbols', default=defaults['no_lxsymbols'],
)
parser.add_argument(
'--test', default=defaults['test'], action='store_true',
'--test', default=defaults['test'],
help='''\
Run an expect test case instead of interactive usage. For baremetal and userland,
the script is a .py file next to the source code.
@@ -234,5 +234,5 @@ the script is a .py file next to the source code.
'break_at', nargs='?',
help='Extra options to append at the end of the emulator command line'
)
args = common.setup(parser)
args = self.setup(parser)
sys.exit(main(args))

View File

@@ -7,7 +7,7 @@ import sys
import common
rungdb = imp.load_source('rungdb', os.path.join(kwargs['root_dir'], 'run-gdb'))
parser = common.get_argparse(argparse_args={
parser = self.get_argparse(argparse_args={
'description': '''GDB step debug guest userland processes without gdbserver.
More information at: https://github.com/cirosantilli/linux-kernel-module-cheat#gdb-step-debug-userland-processes
@@ -23,9 +23,9 @@ parser.add_argument(
help='Break at this point, e.g. main.',
nargs='?'
)
args = common.setup(parser)
executable = common.resolve_userland(kwargs['executable'])
addr = common.get_elf_entry(os.path.join(kwargs['buildroot_build_build_dir'], executable))
args = self.setup(parser)
executable = self.resolve_userland(kwargs['executable'])
addr = self.get_elf_entry(os.path.join(kwargs['buildroot_build_build_dir'], executable))
extra_args = {}
extra_args['before'] = '-ex \"add-symbol-file {} {}\"'.format(executable, hex(addr))
# Or else lx-symbols throws for arm:

View File

@@ -6,7 +6,7 @@ import sys
import common
parser = common.get_argparse(argparse_args={
parser = self.get_argparse(argparse_args={
'description':'Connect to gdbserver running on the guest.'
})
parser.add_argument(
@@ -16,13 +16,13 @@ parser.add_argument(
parser.add_argument(
'break_at', default='main', nargs='?'
)
args = common.setup(parser)
args = self.setup(parser)
sys.exit(subprocess.Popen([
common.get_toolchain_tool('gdb'),
self.get_toolchain_tool('gdb'),
'-q',
'-ex', 'set sysroot {}'.format(kwargs['buildroot_staging_dir']),
'-ex', 'target remote localhost:{}'.format(kwargs['qemu_hostfwd_generic_port']),
'-ex', 'tbreak {}'.format(kwargs['break_at']),
'-ex', 'continue',
os.path.join(kwargs['buildroot_build_build_dir'], common.resolve_userland(kwargs['executable'])),
os.path.join(kwargs['buildroot_build_build_dir'], self.resolve_userland(kwargs['executable'])),
]).wait())

View File

@@ -5,7 +5,7 @@ import sys
import common
parser = common.get_argparse(argparse_args={
parser = self.get_argparse(argparse_args={
'description': '''Run a Buildroot ToolChain tool like readelf or objdump.
For example, to get some information about the arm vmlinux:
@@ -24,7 +24,6 @@ ls "$(./getvar -a arm host_bin_dir)"
parser.add_argument(
'--dry',
help='Just output the tool path to stdout but actually run it',
action='store_true',
)
parser.add_argument('tool', help='Which tool to run.')
parser.add_argument(
@@ -34,12 +33,12 @@ parser.add_argument(
metavar='extra-args',
nargs='*'
)
args = common.setup(parser)
args = self.setup(parser)
if kwargs['baremetal'] is None:
image = kwargs['vmlinux']
else:
image = kwargs['image']
tool= common.get_toolchain_tool(kwargs['tool'])
tool= self.get_toolchain_tool(kwargs['tool'])
if kwargs['dry']:
print(tool)
else:

View File

@@ -6,7 +6,9 @@ import os
import shlex
import shutil
import signal
import stat
import subprocess
import sys
class LF:
'''

View File

@@ -9,7 +9,7 @@ import common
run = imp.load_source('run', os.path.join(kwargs['root_dir'], 'run'))
qemu_trace2txt = imp.load_source('qemu_trace2txt', os.path.join(kwargs['root_dir'], 'qemu-trace2txt'))
parser = common.get_argparse(argparse_args={
parser = self.get_argparse(argparse_args={
'description': '''Trace the PIC addresses executed on a Linux kernel boot.
More information at: https://github.com/cirosantilli/linux-kernel-module-cheat#tracing
@@ -19,7 +19,7 @@ parser.add_argument(
'extra_emulator_args', nargs='*',
help='Extra options to append at the end of the emulator command line'
)
args = common.setup(parser)
args = self.setup(parser)
extra_args = {
'extra_emulator_args': kwargs['extra_emulator_args'],
}
@@ -39,7 +39,7 @@ else:
# Instruction count.
# We could put this on a separate script, but it just adds more arch boilerplate to a new script.
# So let's just leave it here for now since it did not add a significant processing time.
kernel_entry_addr = hex(common.get_elf_entry(kwargs['vmlinux']))
kernel_entry_addr = hex(self.get_elf_entry(kwargs['vmlinux']))
nlines = 0
nlines_firmware = 0
with open(kwargs['qemu_trace_txt_file'], 'r') as trace_file:

View File

@@ -14,15 +14,15 @@ import sys
import common
parser = common.get_argparse(argparse_args={
parser = self.get_argparse(argparse_args={
'description': 'Convert an execution trace containing PC values into the Linux kernel linex executed'
})
args = common.setup(parser)
args = self.setup(parser)
sys.exit(subprocess.Popen([
os.path.join(kwargs['root_dir'], 'trace2line.sh'),
'true' if kwargs['emulator'] == 'gem5' else 'false',
kwargs['trace_txt_file'],
common.get_toolchain_tool('addr2line'),
self.get_toolchain_tool('addr2line'),
kwargs['vmlinux'],
kwargs['run_dir'],
]).wait())
@@ -40,7 +40,7 @@ sys.exit(subprocess.Popen([
# with \
# subprocess.Popen(
# [
# common.get_toolchain_tool('addr2line'),
# self.get_toolchain_tool('addr2line'),
# '-e',
# kwargs['vmlinux'],
# '-f',