mirror of
https://github.com/cirosantilli/linux-kernel-module-cheat.git
synced 2026-01-27 12:04:27 +01:00
test-user-mode: make perfect like build-userland
Multithreading and target selection.
This commit is contained in:
52
README.adoc
52
README.adoc
@@ -3506,7 +3506,20 @@ The gem5 tests require building statically with build id `static`, see also: <<g
|
|||||||
|
|
||||||
See: <<test-this-repo>> for more useful testing tips.
|
See: <<test-this-repo>> for more useful testing tips.
|
||||||
|
|
||||||
==== User mode with host toolchain and QEMU
|
=== User mode Buildroot executables
|
||||||
|
|
||||||
|
If you followed <<qemu-buildroot-setup>>, you can now run the executables created by Buildroot directly as:
|
||||||
|
|
||||||
|
....
|
||||||
|
./run \
|
||||||
|
--userland "$(./getvar buildroot_target_dir)/bin/echo" \
|
||||||
|
--userland-args='asdf' \
|
||||||
|
;
|
||||||
|
....
|
||||||
|
|
||||||
|
Here is an interesting examples of this: <<linux-test-project>>
|
||||||
|
|
||||||
|
=== User mode with host toolchain and QEMU
|
||||||
|
|
||||||
If you are lazy to built the Buildroot toolchain and QEMU, you can get away on Ubuntu 18.04 with just:
|
If you are lazy to built the Buildroot toolchain and QEMU, you can get away on Ubuntu 18.04 with just:
|
||||||
|
|
||||||
@@ -3537,11 +3550,11 @@ This present the usual trade-offs of using prebuilts as mentioned at: <<prebuilt
|
|||||||
|
|
||||||
When you build with the native host toolchain, you can also execute many of the executables directly natively on the host: <<userland-setup-getting-started-natively>>.
|
When you build with the native host toolchain, you can also execute many of the executables directly natively on the host: <<userland-setup-getting-started-natively>>.
|
||||||
|
|
||||||
==== User mode simulation with glibc
|
=== User mode simulation with glibc
|
||||||
|
|
||||||
At 125d14805f769104f93c510bedaa685a52ec025d we <<libc-choice,moved Buildroot from uClibc to glibc>>, and caused some user mode pain, which we document here.
|
At 125d14805f769104f93c510bedaa685a52ec025d we <<libc-choice,moved Buildroot from uClibc to glibc>>, and caused some user mode pain, which we document here.
|
||||||
|
|
||||||
===== FATAL: kernel too old
|
==== FATAL: kernel too old
|
||||||
|
|
||||||
Happens on all gem5 <<user-mode-simulation>> setups, but not on QEMU on Ubuntu 18.04 host.
|
Happens on all gem5 <<user-mode-simulation>> setups, but not on QEMU on Ubuntu 18.04 host.
|
||||||
|
|
||||||
@@ -3572,7 +3585,7 @@ In gem5, there are tons of missing syscalls, and that number currently just gets
|
|||||||
|
|
||||||
The ID is just hardcoded on the source:
|
The ID is just hardcoded on the source:
|
||||||
|
|
||||||
===== stack smashing detected
|
==== stack smashing detected
|
||||||
|
|
||||||
For some reason QEMU / glibc x86_64 picks up the host libc, which breaks things.
|
For some reason QEMU / glibc x86_64 picks up the host libc, which breaks things.
|
||||||
|
|
||||||
@@ -3611,7 +3624,7 @@ A non-QEMU-specific example of stack smashing is shown at: https://stackoverflow
|
|||||||
|
|
||||||
Tested at: 2e32389ebf1bedd89c682aa7b8fe42c3c0cf96e5 + 1.
|
Tested at: 2e32389ebf1bedd89c682aa7b8fe42c3c0cf96e5 + 1.
|
||||||
|
|
||||||
==== User mode static executables
|
=== User mode static executables
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@@ -3637,7 +3650,7 @@ However, in case something goes wrong, you can also try statically linked execut
|
|||||||
* gem5 user mode currently only supports static executables: <<gem5-syscall-emulation-mode>>
|
* gem5 user mode currently only supports static executables: <<gem5-syscall-emulation-mode>>
|
||||||
* QEMU x86_64 guest on x86_64 host was failing with <<stack-smashing-detected>>, but we found a workaround
|
* QEMU x86_64 guest on x86_64 host was failing with <<stack-smashing-detected>>, but we found a workaround
|
||||||
|
|
||||||
===== User mode static executables with dynamic libraries
|
==== User mode static executables with dynamic libraries
|
||||||
|
|
||||||
One limitation of static executables is that Buildroot mostly only builds dynamic versions of libraries (the libc is an exception).
|
One limitation of static executables is that Buildroot mostly only builds dynamic versions of libraries (the libc is an exception).
|
||||||
|
|
||||||
@@ -3742,6 +3755,8 @@ which we parse in link:run[] and then exit with the correct result ourselves...
|
|||||||
|
|
||||||
Let's see if user mode runs considerably faster than full system or not.
|
Let's see if user mode runs considerably faster than full system or not.
|
||||||
|
|
||||||
|
First we build Dhrystone manually statically since dynamic linking is broken in gem5: <<gem5-syscall-emulation-mode>>.
|
||||||
|
|
||||||
gem5 user mode:
|
gem5 user mode:
|
||||||
|
|
||||||
....
|
....
|
||||||
@@ -3796,6 +3811,28 @@ Result on <<p51>> at bad30f513c46c1b0995d3a10c0d9bc2a33dc4fa0:
|
|||||||
* QEMU user: 45 seconds
|
* QEMU user: 45 seconds
|
||||||
* QEMU full system: 223 seconds
|
* QEMU full system: 223 seconds
|
||||||
|
|
||||||
|
=== QEMU user mode does not show stdout immediately
|
||||||
|
|
||||||
|
At 8d8307ac0710164701f6e14c99a69ee172ccbb70 + 1, I noticed that if you run link:userland/posix/count.c[]:
|
||||||
|
|
||||||
|
....
|
||||||
|
./run --userland userland/posix/count.c --userland-args 3
|
||||||
|
....
|
||||||
|
|
||||||
|
it first waits for 3 seconds, and then dumps all the output at once, instead of counting once every second as expected.
|
||||||
|
|
||||||
|
The same can be reproduced by copying the raw QEMU command and piping it through `tee`, so I don't think it is a bug in our setup:
|
||||||
|
|
||||||
|
....
|
||||||
|
/path/to/linux-kernel-module-cheat/out/qemu/default/x86_64-linux-user/qemu-x86_64 \
|
||||||
|
-L /path/to/linux-kernel-module-cheat/out/buildroot/build/default/x86_64/target \
|
||||||
|
/path/to/linux-kernel-module-cheat/out/userland/default/x86_64/posix/count.out \
|
||||||
|
3 \
|
||||||
|
| tee
|
||||||
|
....
|
||||||
|
|
||||||
|
TODO: investigate further and then possibly post on QEMU mailing list.
|
||||||
|
|
||||||
== Kernel module utilities
|
== Kernel module utilities
|
||||||
|
|
||||||
=== insmod
|
=== insmod
|
||||||
@@ -8349,7 +8386,7 @@ exit01 1 TPASS : exit() test PASSED
|
|||||||
|
|
||||||
and has source code at: https://github.com/linux-test-project/ltp/blob/20190115/testcases/kernel/syscalls/exit/exit01.c
|
and has source code at: https://github.com/linux-test-project/ltp/blob/20190115/testcases/kernel/syscalls/exit/exit01.c
|
||||||
|
|
||||||
Besides testing any kernel modifications you make, LTP can also be used to the system call implementation of <<user-mode-simulation>>:
|
Besides testing any kernel modifications you make, LTP can also be used to the system call implementation of <<user-mode-simulation>> as shown at <<user-mode-buildroot-executables>>:
|
||||||
|
|
||||||
....
|
....
|
||||||
./run --userland "$(./getvar buildroot_target_dir)/usr/lib/ltp-testsuite/testcases/bin/exit01"
|
./run --userland "$(./getvar buildroot_target_dir)/usr/lib/ltp-testsuite/testcases/bin/exit01"
|
||||||
@@ -9924,6 +9961,7 @@ Usage:
|
|||||||
--emulator gem5 \
|
--emulator gem5 \
|
||||||
--static \
|
--static \
|
||||||
--userland cpp/bst_vs_heap \
|
--userland cpp/bst_vs_heap \
|
||||||
|
--userland-args='1000' \
|
||||||
;
|
;
|
||||||
./bst-vs-heap --arch aarch64 > bst_vs_heap.dat
|
./bst-vs-heap --arch aarch64 > bst_vs_heap.dat
|
||||||
./bst-vs-heap.gnuplot
|
./bst-vs-heap.gnuplot
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class Main(common.LkmcCliFunction):
|
|||||||
super().__init__(
|
super().__init__(
|
||||||
defaults={
|
defaults={
|
||||||
'emulator': 'gem5',
|
'emulator': 'gem5',
|
||||||
'print_time': False,
|
'show_time': False,
|
||||||
},
|
},
|
||||||
description='''\
|
description='''\
|
||||||
Convert a BST vs heap stat file into a gnuplot input
|
Convert a BST vs heap stat file into a gnuplot input
|
||||||
|
|||||||
2
build
2
build
@@ -402,7 +402,7 @@ Which components to build. Default: qemu-buildroot
|
|||||||
def f():
|
def f():
|
||||||
args = self.get_common_args()
|
args = self.get_common_args()
|
||||||
args.update(extra_args)
|
args.update(extra_args)
|
||||||
args['print_time'] = False
|
args['show_time'] = False
|
||||||
self.import_path_main(component_file)(**args)
|
self.import_path_main(component_file)(**args)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class Main(common.LkmcCliFunction):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
defaults = {
|
defaults = {
|
||||||
'print_time': False,
|
'show_time': False,
|
||||||
},
|
},
|
||||||
description='''\
|
description='''\
|
||||||
https://github.com/cirosantilli/linux-kernel-module-cheat#build-the-documentation
|
https://github.com/cirosantilli/linux-kernel-module-cheat#build-the-documentation
|
||||||
|
|||||||
115
common.py
115
common.py
@@ -1,23 +1,28 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import bisect
|
||||||
import collections
|
import collections
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import enum
|
import enum
|
||||||
|
import functools
|
||||||
import glob
|
import glob
|
||||||
import imp
|
import imp
|
||||||
import inspect
|
import inspect
|
||||||
|
import itertools
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import queue
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
import urllib
|
import urllib
|
||||||
import urllib.request
|
import urllib.request
|
||||||
@@ -152,6 +157,7 @@ class LkmcCliFunction(cli_function.CliFunction):
|
|||||||
self._common_args = set()
|
self._common_args = set()
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.supported_archs = supported_archs
|
self.supported_archs = supported_archs
|
||||||
|
self.print_lock = threading.Lock()
|
||||||
|
|
||||||
# Args for all scripts.
|
# Args for all scripts.
|
||||||
arches = consts['arch_short_to_long_dict']
|
arches = consts['arch_short_to_long_dict']
|
||||||
@@ -219,12 +225,14 @@ Which toolchain binaries to use:
|
|||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
self.add_argument(
|
self.add_argument(
|
||||||
'--print-time',
|
'-j',
|
||||||
default=True,
|
'--nproc',
|
||||||
help='''\
|
default=len(os.sched_getaffinity(0)),
|
||||||
Print how long it took to run the command at the end.
|
type=int,
|
||||||
Implied by --quiet.
|
help='''Number of processors to use for the action.
|
||||||
'''
|
This is currently only implemented for the following scripts:
|
||||||
|
all ./build-* scripts, test-user-mode.
|
||||||
|
''',
|
||||||
)
|
)
|
||||||
self.add_argument(
|
self.add_argument(
|
||||||
'-q',
|
'-q',
|
||||||
@@ -240,6 +248,14 @@ TODO: implement fully, some stuff is escaping it currently.
|
|||||||
default=True,
|
default=True,
|
||||||
help='''\
|
help='''\
|
||||||
Stop running at the first failed test.
|
Stop running at the first failed test.
|
||||||
|
'''
|
||||||
|
)
|
||||||
|
self.add_argument(
|
||||||
|
'--show-time',
|
||||||
|
default=True,
|
||||||
|
help='''\
|
||||||
|
Print how long it took to run the command at the end.
|
||||||
|
Implied by --quiet.
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
self.add_argument(
|
self.add_argument(
|
||||||
@@ -985,13 +1001,15 @@ lunch aosp_{}-eng
|
|||||||
return self.supported_archs is None or arch in self.supported_archs
|
return self.supported_archs is None or arch in self.supported_archs
|
||||||
|
|
||||||
def log_error(self, msg):
|
def log_error(self, msg):
|
||||||
print('error: {}'.format(msg), file=sys.stdout)
|
with self.print_lock:
|
||||||
|
print('error: {}'.format(msg), file=sys.stdout)
|
||||||
|
|
||||||
def log_info(self, msg='', flush=False, **kwargs):
|
def log_info(self, msg='', flush=False, **kwargs):
|
||||||
if not self.env['quiet']:
|
with self.print_lock:
|
||||||
print('{}'.format(msg), **kwargs)
|
if not self.env['quiet']:
|
||||||
if flush:
|
print('{}'.format(msg), **kwargs)
|
||||||
sys.stdout.flush()
|
if flush:
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
def main(self, *args, **kwargs):
|
def main(self, *args, **kwargs):
|
||||||
'''
|
'''
|
||||||
@@ -1087,7 +1105,7 @@ lunch aosp_{}-eng
|
|||||||
return '{:02}:{:02}:{:02}'.format(int(hours), int(minutes), int(seconds))
|
return '{:02}:{:02}:{:02}'.format(int(hours), int(minutes), int(seconds))
|
||||||
|
|
||||||
def print_time(self, ellapsed_seconds):
|
def print_time(self, ellapsed_seconds):
|
||||||
if self.env['print_time'] and not self.env['quiet']:
|
if self.env['show_time'] and not self.env['quiet']:
|
||||||
print('time {}'.format(self.seconds_to_hms(ellapsed_seconds)))
|
print('time {}'.format(self.seconds_to_hms(ellapsed_seconds)))
|
||||||
|
|
||||||
def raw_to_qcow2(self, qemu_which=False, reverse=False):
|
def raw_to_qcow2(self, qemu_which=False, reverse=False):
|
||||||
@@ -1280,14 +1298,6 @@ class BuildCliFunction(LkmcCliFunction):
|
|||||||
default=False,
|
default=False,
|
||||||
help='Clean the build instead of building.',
|
help='Clean the build instead of building.',
|
||||||
),
|
),
|
||||||
self.add_argument(
|
|
||||||
'-j',
|
|
||||||
'--nproc',
|
|
||||||
default=len(os.sched_getaffinity(0)),
|
|
||||||
type=int,
|
|
||||||
help='Number of processors to use for the build.',
|
|
||||||
)
|
|
||||||
self.test_results = []
|
|
||||||
self._build_arguments = {
|
self._build_arguments = {
|
||||||
'--ccflags': {
|
'--ccflags': {
|
||||||
'default': '',
|
'default': '',
|
||||||
@@ -1354,23 +1364,30 @@ https://github.com/cirosantilli/linux-kernel-module-cheat#gem5-debug-build
|
|||||||
else:
|
else:
|
||||||
return self.build()
|
return self.build()
|
||||||
|
|
||||||
# from aenum import Enum # for the aenum version
|
TestStatus = enum.Enum('TestStatus', ['PASS', 'FAIL'])
|
||||||
TestResult = enum.Enum('TestResult', ['PASS', 'FAIL'])
|
|
||||||
|
|
||||||
class Test:
|
@functools.total_ordering
|
||||||
|
class TestResult:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
test_id: str,
|
test_id: str ='',
|
||||||
result : TestResult =None,
|
status : TestStatus =TestStatus.PASS,
|
||||||
ellapsed_seconds : float =None
|
ellapsed_seconds : float =0
|
||||||
):
|
):
|
||||||
self.test_id = test_id
|
self.test_id = test_id
|
||||||
self.result = result
|
self.status = status
|
||||||
self.ellapsed_seconds = ellapsed_seconds
|
self.ellapsed_seconds = ellapsed_seconds
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.test_id == other.test_id
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.test_id < other.test_id
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
out = []
|
out = []
|
||||||
if self.result is not None:
|
if self.status is not None:
|
||||||
out.append(self.result.name)
|
out.append(self.status.name)
|
||||||
if self.ellapsed_seconds is not None:
|
if self.ellapsed_seconds is not None:
|
||||||
out.append(LkmcCliFunction.seconds_to_hms(self.ellapsed_seconds))
|
out.append(LkmcCliFunction.seconds_to_hms(self.ellapsed_seconds))
|
||||||
out.append(self.test_id)
|
out.append(self.test_id)
|
||||||
@@ -1385,13 +1402,13 @@ class TestCliFunction(LkmcCliFunction):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
defaults = {
|
defaults = {
|
||||||
'print_time': False,
|
'show_time': False,
|
||||||
}
|
}
|
||||||
if 'defaults' in kwargs:
|
if 'defaults' in kwargs:
|
||||||
defaults.update(kwargs['defaults'])
|
defaults.update(kwargs['defaults'])
|
||||||
kwargs['defaults'] = defaults
|
kwargs['defaults'] = defaults
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.tests = []
|
self.test_results = queue.Queue()
|
||||||
|
|
||||||
def run_test(
|
def run_test(
|
||||||
self,
|
self,
|
||||||
@@ -1440,36 +1457,38 @@ class TestCliFunction(LkmcCliFunction):
|
|||||||
expected_exit_status = 0
|
expected_exit_status = 0
|
||||||
if not self.env['dry_run']:
|
if not self.env['dry_run']:
|
||||||
if exit_status == expected_exit_status:
|
if exit_status == expected_exit_status:
|
||||||
test_result = TestResult.PASS
|
test_result = TestStatus.PASS
|
||||||
else:
|
else:
|
||||||
test_result = TestResult.FAIL
|
test_result = TestStatus.FAIL
|
||||||
self.log_info('test_result {}'.format(test_result.name))
|
|
||||||
ellapsed_seconds = run_obj.ellapsed_seconds
|
ellapsed_seconds = run_obj.ellapsed_seconds
|
||||||
else:
|
else:
|
||||||
test_result = None
|
test_result = TestStatus.PASS
|
||||||
ellapsed_seconds = None
|
ellapsed_seconds = 0
|
||||||
self.log_info()
|
test_result = TestResult(
|
||||||
self.tests.append(Test(test_id_string, test_result, ellapsed_seconds))
|
test_id_string,
|
||||||
|
test_result,
|
||||||
|
ellapsed_seconds
|
||||||
|
)
|
||||||
|
self.log_info(test_result)
|
||||||
|
self.test_results.put(test_result)
|
||||||
return test_result
|
return test_result
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
'''
|
'''
|
||||||
:return: 1 if any test failed, 0 otherwise
|
:return: 1 if any test failed, 0 otherwise
|
||||||
'''
|
'''
|
||||||
self.log_info('Test result summary')
|
self.log_info('\nTest result summary')
|
||||||
passes = []
|
passes = []
|
||||||
fails = []
|
fails = []
|
||||||
for test in self.tests:
|
while not self.test_results.empty():
|
||||||
if test.result in (TestResult.PASS, None):
|
test = self.test_results.get()
|
||||||
passes.append(test)
|
if test.status in (TestStatus.PASS, None):
|
||||||
|
bisect.insort(passes, test)
|
||||||
else:
|
else:
|
||||||
fails.append(test)
|
bisect.insort(fails, test)
|
||||||
if passes:
|
for test in itertools.chain(passes, fails):
|
||||||
for test in passes:
|
self.log_info(test)
|
||||||
self.log_info(test)
|
|
||||||
if fails:
|
if fails:
|
||||||
for test in fails:
|
|
||||||
self.log_info(test)
|
|
||||||
self.log_error('A test failed')
|
self.log_error('A test failed')
|
||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -22,8 +22,12 @@ class ExecutableProperties:
|
|||||||
not self.more_than_1s
|
not self.more_than_1s
|
||||||
|
|
||||||
executable_properties = {
|
executable_properties = {
|
||||||
'c/assert_fail.c': ExecutableProperties(exit_status=0),
|
'c/assert_fail.c': ExecutableProperties(exit_status=1),
|
||||||
'c/false.c': ExecutableProperties(exit_status=0),
|
'c/false.c': ExecutableProperties(exit_status=1),
|
||||||
|
'c/infinite_loop.c': ExecutableProperties(more_than_1s=True),
|
||||||
|
'posix/count.c': ExecutableProperties(more_than_1s=True),
|
||||||
|
'posix/sleep_forever.c': ExecutableProperties(more_than_1s=True),
|
||||||
|
'posix/virt_to_phys_test.c': ExecutableProperties(more_than_1s=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
def get(test_path):
|
def get(test_path):
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ class Main(common.LkmcCliFunction):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
defaults={
|
defaults={
|
||||||
'print_time': False,
|
'show_time': False,
|
||||||
},
|
},
|
||||||
description='''\
|
description='''\
|
||||||
Get the value of a gem5 stat from the stats.txt file.
|
Get the value of a gem5 stat from the stats.txt file.
|
||||||
|
|||||||
2
getvar
2
getvar
@@ -6,7 +6,7 @@ class Main(common.LkmcCliFunction):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
defaults = {
|
defaults = {
|
||||||
'print_time': False,
|
'show_time': False,
|
||||||
},
|
},
|
||||||
description='''\
|
description='''\
|
||||||
Print the value of a self.env['py'] variable.
|
Print the value of a self.env['py'] variable.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class Main(common.LkmcCliFunction):
|
|||||||
https://github.com/cirosantilli/linux-kernel-module-cheat#release-zip
|
https://github.com/cirosantilli/linux-kernel-module-cheat#release-zip
|
||||||
''',
|
''',
|
||||||
defaults = {
|
defaults = {
|
||||||
'print_time': False,
|
'show_time': False,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.zip_files = []
|
self.zip_files = []
|
||||||
|
|||||||
9
run
9
run
@@ -20,7 +20,7 @@ Run some content on an emulator.
|
|||||||
self.add_argument(
|
self.add_argument(
|
||||||
'--background', default=False,
|
'--background', default=False,
|
||||||
help='''\
|
help='''\
|
||||||
Send QEMU output to a file instead of the terminal so it does not require a
|
Send QEMU serial 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.
|
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
|
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
|
places, both to a port and a file? We use the file currently to be able to have
|
||||||
@@ -166,6 +166,10 @@ Setup a kernel init parameter that makes the emulator quit immediately after boo
|
|||||||
'-r', '--record', default=False,
|
'-r', '--record', default=False,
|
||||||
help='Record a QEMU run record for later replay with `-R`'
|
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(
|
self.add_argument(
|
||||||
'--terminal', default=False,
|
'--terminal', default=False,
|
||||||
help='''\
|
help='''\
|
||||||
@@ -238,7 +242,7 @@ Run QEMU with VNC instead of the default SDL. Connect to it with:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def timed_main(self):
|
def timed_main(self):
|
||||||
show_stdout = True
|
show_stdout = self.env['show_stdout']
|
||||||
# Common qemu / gem5 logic.
|
# Common qemu / gem5 logic.
|
||||||
# nokaslr:
|
# nokaslr:
|
||||||
# * https://unix.stackexchange.com/questions/397939/turning-off-kaslr-to-debug-linux-kernel-using-qemu-and-gdb
|
# * https://unix.stackexchange.com/questions/397939/turning-off-kaslr-to-debug-linux-kernel-using-qemu-and-gdb
|
||||||
@@ -455,6 +459,7 @@ Run QEMU with VNC instead of the default SDL. Connect to it with:
|
|||||||
os.path.join(self.env['qemu_build_dir'], '{}-linux-user'.format(self.env['arch']), 'qemu-{}'.format(self.env['arch'])), LF,
|
os.path.join(self.env['qemu_build_dir'], '{}-linux-user'.format(self.env['arch']), 'qemu-{}'.format(self.env['arch'])), LF,
|
||||||
'-L', self.env['userland_library_dir'], LF,
|
'-L', self.env['userland_library_dir'], LF,
|
||||||
'-r', self.env['kernel_version'], LF,
|
'-r', self.env['kernel_version'], LF,
|
||||||
|
'-seed', '0', LF,
|
||||||
] +
|
] +
|
||||||
qemu_user_and_system_options +
|
qemu_user_and_system_options +
|
||||||
debug_args
|
debug_args
|
||||||
|
|||||||
@@ -52,10 +52,9 @@ class ShellHelpers:
|
|||||||
https://stackoverflow.com/questions/3029816/how-do-i-get-a-thread-safe-print-in-python-2-6
|
https://stackoverflow.com/questions/3029816/how-do-i-get-a-thread-safe-print-in-python-2-6
|
||||||
The initial use case was test-gdb which must create a thread for GDB to run the program in parallel.
|
The initial use case was test-gdb which must create a thread for GDB to run the program in parallel.
|
||||||
'''
|
'''
|
||||||
cls._print_lock.acquire()
|
with cls._print_lock:
|
||||||
sys.stdout.write(string + '\n')
|
sys.stdout.write(string + '\n')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
cls._print_lock.release()
|
|
||||||
|
|
||||||
def add_newlines(self, cmd):
|
def add_newlines(self, cmd):
|
||||||
out = []
|
out = []
|
||||||
@@ -217,16 +216,16 @@ class ShellHelpers:
|
|||||||
:return: exit status of the command
|
:return: exit status of the command
|
||||||
:rtype: int
|
:rtype: int
|
||||||
'''
|
'''
|
||||||
if out_file is not None:
|
if out_file is None:
|
||||||
stdout = subprocess.PIPE
|
|
||||||
stderr = subprocess.STDOUT
|
|
||||||
else:
|
|
||||||
if show_stdout:
|
if show_stdout:
|
||||||
stdout = None
|
stdout = None
|
||||||
stderr = None
|
stderr = None
|
||||||
else:
|
else:
|
||||||
stdout = subprocess.DEVNULL
|
stdout = subprocess.DEVNULL
|
||||||
stderr = subprocess.DEVNULL
|
stderr = subprocess.DEVNULL
|
||||||
|
else:
|
||||||
|
stdout = subprocess.PIPE
|
||||||
|
stderr = subprocess.STDOUT
|
||||||
if extra_env is None:
|
if extra_env is None:
|
||||||
extra_env = {}
|
extra_env = {}
|
||||||
if delete_env is None:
|
if delete_env is None:
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ import sys
|
|||||||
|
|
||||||
import common
|
import common
|
||||||
import example_properties
|
import example_properties
|
||||||
|
from thread_pool import ThreadPool
|
||||||
|
|
||||||
class Main(common.TestCliFunction):
|
class Main(common.TestCliFunction):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
description='''\
|
description='''\
|
||||||
https://github.com/cirosantilli/linux-kernel-module-cheat#user-mode-tests
|
https://github.com/cirosantilli/linux-kernel-module-cheat#user-mode-tests
|
||||||
'''
|
''',
|
||||||
)
|
)
|
||||||
self.add_argument(
|
self.add_argument(
|
||||||
'tests',
|
'tests',
|
||||||
@@ -25,36 +26,46 @@ If given, run only the given tests. Otherwise, run all tests.
|
|||||||
run = self.import_path_main('run')
|
run = self.import_path_main('run')
|
||||||
run_args = self.get_common_args()
|
run_args = self.get_common_args()
|
||||||
run_args['ctrl_c_host'] = True
|
run_args['ctrl_c_host'] = True
|
||||||
|
run_args['show_stdout'] = False
|
||||||
|
run_args['show_time'] = False
|
||||||
if self.env['emulator'] == 'gem5':
|
if self.env['emulator'] == 'gem5':
|
||||||
run_args['userland_build_id'] = 'static'
|
run_args['userland_build_id'] = 'static'
|
||||||
if self.env['tests'] == []:
|
|
||||||
test_paths = [
|
|
||||||
'c/add.c',
|
|
||||||
'c/false.c',
|
|
||||||
'c/hello.c',
|
|
||||||
'c/print_argv.c',
|
|
||||||
'cpp/hello.cpp',
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
test_paths = self.env['tests']
|
|
||||||
had_failure = False
|
had_failure = False
|
||||||
for test_path in test_paths:
|
rootdir_abs_len = len(self.env['userland_source_dir'])
|
||||||
test = example_properties.get(test_path)
|
with ThreadPool(
|
||||||
if test.should_be_tested():
|
self.run_test,
|
||||||
# for test in self.sh.walk(self.resolve_userland_source(test_dir_or_file)):
|
nthreads=self.env['nproc'],
|
||||||
run_args['userland'] = test_path
|
) as thread_pool:
|
||||||
test_result = self.run_test(
|
try:
|
||||||
run,
|
for path, in_dirnames, in_filenames in self.walk_source_targets(
|
||||||
run_args,
|
self.env['tests'],
|
||||||
test_id=test_path,
|
self.env['userland_in_exts']
|
||||||
expected_exit_status=test.exit_status
|
):
|
||||||
)
|
path_abs = os.path.abspath(path)
|
||||||
if test_result != common.TestResult.PASS:
|
dirpath_relative_root = path_abs[rootdir_abs_len + 1:]
|
||||||
if self.env['quit_on_fail']:
|
for in_filename in in_filenames:
|
||||||
return 1
|
path_relative_root = os.path.join(dirpath_relative_root, in_filename)
|
||||||
else:
|
test = example_properties.get(path_relative_root)
|
||||||
had_failure = True
|
if test.should_be_tested():
|
||||||
if had_failure:
|
cur_run_args = run_args.copy()
|
||||||
|
cur_run_args.update({
|
||||||
|
'background': True,
|
||||||
|
'userland': path_relative_root,
|
||||||
|
})
|
||||||
|
error = thread_pool.submit({
|
||||||
|
'expected_exit_status': test.exit_status,
|
||||||
|
'run_args': cur_run_args,
|
||||||
|
'run_obj': run,
|
||||||
|
'test_id': path_relative_root,
|
||||||
|
})
|
||||||
|
if error is not None:
|
||||||
|
if self.env['quit_on_fail']:
|
||||||
|
raise common.ExitLoop()
|
||||||
|
except common.ExitLoop:
|
||||||
|
pass
|
||||||
|
error = thread_pool.get_error()
|
||||||
|
if error is not None:
|
||||||
|
print(error)
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from typing import Any, Callable, Dict, Iterable, Union
|
from typing import Any, Callable, Dict, Iterable, Union
|
||||||
|
import os
|
||||||
import queue
|
import queue
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
@@ -13,10 +14,10 @@ class ThreadPool:
|
|||||||
This is similar to the stdlib concurrent, but I could not find
|
This is similar to the stdlib concurrent, but I could not find
|
||||||
how to reach all my design goals with that implementation:
|
how to reach all my design goals with that implementation:
|
||||||
|
|
||||||
- the input function does not need to be modified
|
* the input function does not need to be modified
|
||||||
- limit the number of threads
|
* limit the number of threads
|
||||||
- queue sizes closely follow number of threads
|
* queue sizes closely follow number of threads
|
||||||
- if an exception happens, optionally stop soon afterwards
|
* if an exception happens, optionally stop soon afterwards
|
||||||
|
|
||||||
Functional form and further discussion at:
|
Functional form and further discussion at:
|
||||||
https://stackoverflow.com/questions/19369724/the-right-way-to-limit-maximum-number-of-threads-running-at-once/55263676#55263676
|
https://stackoverflow.com/questions/19369724/the-right-way-to-limit-maximum-number-of-threads-running-at-once/55263676#55263676
|
||||||
@@ -25,10 +26,12 @@ class ThreadPool:
|
|||||||
|
|
||||||
Quick test with:
|
Quick test with:
|
||||||
|
|
||||||
./thread_limit.py 2 -10 20 0
|
....
|
||||||
./thread_limit.py 2 -10 20 1
|
python3 thread_pool.py 2 -10 20 0
|
||||||
./thread_limit.py 2 -10 20 2
|
python3 thread_pool.py 2 -10 20 1
|
||||||
./thread_limit.py 2 -10 20 3
|
python3 thread_pool.py 2 -10 20 2
|
||||||
|
python3 thread_pool.py 2 -10 20 3
|
||||||
|
....
|
||||||
|
|
||||||
These ensure that execution stops neatly on error.
|
These ensure that execution stops neatly on error.
|
||||||
'''
|
'''
|
||||||
@@ -49,9 +52,9 @@ class ThreadPool:
|
|||||||
|
|
||||||
Signature is: handle_output(input, output, exception) where:
|
Signature is: handle_output(input, output, exception) where:
|
||||||
|
|
||||||
- input: input given to func
|
* input: input given to func
|
||||||
- output: return value of func
|
* output: return value of func
|
||||||
- exception: the exception that func raised, or None otherwise
|
* exception: the exception that func raised, or None otherwise
|
||||||
|
|
||||||
If this function returns non-None or raises, stop feeding
|
If this function returns non-None or raises, stop feeding
|
||||||
new input and exit ASAP when all currently running threads
|
new input and exit ASAP when all currently running threads
|
||||||
@@ -79,6 +82,16 @@ class ThreadPool:
|
|||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
|
'''
|
||||||
|
__exit__ automatically calls join() for you.
|
||||||
|
|
||||||
|
This is cool because it automatically ends the loop if an exception occurs.
|
||||||
|
|
||||||
|
But don't forget that errors may happen after the last submit is called, so you
|
||||||
|
likely want to check for that with get_error after the with.
|
||||||
|
|
||||||
|
get_error() returns the same as the explicit join().
|
||||||
|
'''
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, type, value, traceback):
|
def __exit__(self, type, value, traceback):
|
||||||
@@ -124,14 +137,12 @@ class ThreadPool:
|
|||||||
try:
|
try:
|
||||||
handle_output_return = self.handle_output(work, out, exception)
|
handle_output_return = self.handle_output(work, out, exception)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.error_output_lock.acquire()
|
with self.error_output_lock:
|
||||||
self.error_output = (work, out, e)
|
self.error_output = (work, out, e)
|
||||||
self.error_output_lock.release()
|
|
||||||
else:
|
else:
|
||||||
if handle_output_return is not None:
|
if handle_output_return is not None:
|
||||||
self.error_output_lock.acquire()
|
with self.error_output_lock:
|
||||||
self.error_output = handle_output_return
|
self.error_output = handle_output_return
|
||||||
self.error_output_lock.release()
|
|
||||||
finally:
|
finally:
|
||||||
self.in_queue.task_done()
|
self.in_queue.task_done()
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/* Exit with status 1.
|
/* Exit with status 1 like the POSIX false utility:
|
||||||
|
* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/false.html
|
||||||
*
|
*
|
||||||
* Can be uesd to test that emulators forward the exit status properly.
|
* Can be uesd to test that emulators forward the exit status properly.
|
||||||
* https://github.com/cirosantilli/linux-kernel-module-cheat#gem5-syscall-emulation-exit-status
|
* https://github.com/cirosantilli/linux-kernel-module-cheat#gem5-syscall-emulation-exit-status
|
||||||
|
|||||||
29
userland/c/infinite_loop.c
Normal file
29
userland/c/infinite_loop.c
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/* Loop infinitely. Print an integer whenever a period is reached:
|
||||||
|
*
|
||||||
|
* ....
|
||||||
|
* ./infinite_loop [period]
|
||||||
|
* ....
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
uintmax_t i, j, period;
|
||||||
|
if (argc > 1) {
|
||||||
|
period = strtoumax(argv[1], NULL, 10);
|
||||||
|
} else {
|
||||||
|
period = 100000000;
|
||||||
|
}
|
||||||
|
i = 0;
|
||||||
|
j = 0;
|
||||||
|
while (1) {
|
||||||
|
i++;
|
||||||
|
if (i % period == 0) {
|
||||||
|
printf("%ju\n", j);
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
userland/c/stderr.c
Normal file
7
userland/c/stderr.c
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/* Print hello to stderr. */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
fputs("hello\n", stderr);
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ int main(int argc, char **argv) {
|
|||||||
if (argc > 1) {
|
if (argc > 1) {
|
||||||
n = std::stoi(argv[1]);
|
n = std::stoi(argv[1]);
|
||||||
} else {
|
} else {
|
||||||
n = 1000;
|
n = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action.
|
// Action.
|
||||||
|
|||||||
Reference in New Issue
Block a user